1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.http.crt; 17 18 import static software.amazon.awssdk.crtcore.CrtConfigurationUtils.resolveHttpMonitoringOptions; 19 import static software.amazon.awssdk.crtcore.CrtConfigurationUtils.resolveProxy; 20 import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL; 21 import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildSocketOptions; 22 import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveCipherPreference; 23 import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; 24 25 import java.net.URI; 26 import java.util.LinkedList; 27 import java.util.Map; 28 import java.util.concurrent.ConcurrentHashMap; 29 import software.amazon.awssdk.annotations.SdkProtectedApi; 30 import software.amazon.awssdk.crt.CrtResource; 31 import software.amazon.awssdk.crt.http.HttpClientConnectionManager; 32 import software.amazon.awssdk.crt.http.HttpClientConnectionManagerOptions; 33 import software.amazon.awssdk.crt.http.HttpMonitoringOptions; 34 import software.amazon.awssdk.crt.http.HttpProxyOptions; 35 import software.amazon.awssdk.crt.io.ClientBootstrap; 36 import software.amazon.awssdk.crt.io.SocketOptions; 37 import software.amazon.awssdk.crt.io.TlsContext; 38 import software.amazon.awssdk.crt.io.TlsContextOptions; 39 import software.amazon.awssdk.http.Protocol; 40 import software.amazon.awssdk.http.SdkHttpConfigurationOption; 41 import software.amazon.awssdk.http.SdkHttpRequest; 42 import software.amazon.awssdk.http.crt.internal.AwsCrtClientBuilderBase; 43 import software.amazon.awssdk.utils.AttributeMap; 44 import software.amazon.awssdk.utils.IoUtils; 45 import software.amazon.awssdk.utils.Logger; 46 import software.amazon.awssdk.utils.SdkAutoCloseable; 47 48 /** 49 * Common functionality and configuration for the CRT Http clients. 50 */ 51 @SdkProtectedApi 52 abstract class AwsCrtHttpClientBase implements SdkAutoCloseable { 53 private static final Logger log = Logger.loggerFor(AwsCrtHttpClientBase.class); 54 55 private static final String AWS_COMMON_RUNTIME = "AwsCommonRuntime"; 56 private static final long DEFAULT_STREAM_WINDOW_SIZE = 16L * 1024L * 1024L; // 16 MB 57 58 protected final long readBufferSize; 59 private final Map<URI, HttpClientConnectionManager> connectionPools = new ConcurrentHashMap<>(); 60 private final LinkedList<CrtResource> ownedSubResources = new LinkedList<>(); 61 private final ClientBootstrap bootstrap; 62 private final SocketOptions socketOptions; 63 private final TlsContext tlsContext; 64 private final HttpProxyOptions proxyOptions; 65 private final HttpMonitoringOptions monitoringOptions; 66 private final long maxConnectionIdleInMilliseconds; 67 private final int maxConnectionsPerEndpoint; 68 private boolean isClosed = false; 69 AwsCrtHttpClientBase(AwsCrtClientBuilderBase builder, AttributeMap config)70 AwsCrtHttpClientBase(AwsCrtClientBuilderBase builder, AttributeMap config) { 71 if (config.get(PROTOCOL) == Protocol.HTTP2) { 72 throw new UnsupportedOperationException("HTTP/2 is not supported in AwsCrtHttpClient yet. Use " 73 + "NettyNioAsyncHttpClient instead."); 74 } 75 76 try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null); 77 SocketOptions clientSocketOptions = buildSocketOptions(builder.getTcpKeepAliveConfiguration(), 78 config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT)); 79 TlsContextOptions clientTlsContextOptions = 80 TlsContextOptions.createDefaultClient() 81 .withCipherPreference(resolveCipherPreference(builder.getPostQuantumTlsEnabled())) 82 .withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)); 83 TlsContext clientTlsContext = new TlsContext(clientTlsContextOptions)) { 84 85 this.bootstrap = registerOwnedResource(clientBootstrap); 86 this.socketOptions = registerOwnedResource(clientSocketOptions); 87 this.tlsContext = registerOwnedResource(clientTlsContext); 88 this.readBufferSize = builder.getReadBufferSizeInBytes() == null ? 89 DEFAULT_STREAM_WINDOW_SIZE : builder.getReadBufferSizeInBytes(); 90 this.maxConnectionsPerEndpoint = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS); 91 this.monitoringOptions = resolveHttpMonitoringOptions(builder.getConnectionHealthConfiguration()).orElse(null); 92 this.maxConnectionIdleInMilliseconds = config.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis(); 93 this.proxyOptions = resolveProxy(builder.getProxyConfiguration(), tlsContext).orElse(null); 94 } 95 } 96 97 /** 98 * Marks a Native CrtResource as owned by the current Java Object. 99 * 100 * @param subresource The Resource to own. 101 * @param <T> The CrtResource Type 102 * @return The CrtResource passed in 103 */ registerOwnedResource(T subresource)104 private <T extends CrtResource> T registerOwnedResource(T subresource) { 105 if (subresource != null) { 106 subresource.addRef(); 107 ownedSubResources.push(subresource); 108 } 109 return subresource; 110 } 111 clientName()112 String clientName() { 113 return AWS_COMMON_RUNTIME; 114 } 115 createConnectionPool(URI uri)116 private HttpClientConnectionManager createConnectionPool(URI uri) { 117 log.debug(() -> "Creating ConnectionPool for: URI:" + uri + ", MaxConns: " + maxConnectionsPerEndpoint); 118 119 HttpClientConnectionManagerOptions options = new HttpClientConnectionManagerOptions() 120 .withClientBootstrap(bootstrap) 121 .withSocketOptions(socketOptions) 122 .withTlsContext(tlsContext) 123 .withUri(uri) 124 .withWindowSize(readBufferSize) 125 .withMaxConnections(maxConnectionsPerEndpoint) 126 .withManualWindowManagement(true) 127 .withProxyOptions(proxyOptions) 128 .withMonitoringOptions(monitoringOptions) 129 .withMaxConnectionIdleInMilliseconds(maxConnectionIdleInMilliseconds); 130 131 return HttpClientConnectionManager.create(options); 132 } 133 134 /* 135 * Callers of this function MUST account for the addRef() on the pool before returning. 136 * Every execution path consuming the return value must guarantee an associated close(). 137 * Currently, this function is only used by execute(), which guarantees a matching close 138 * via the try-with-resources block. 139 * 140 * This guarantees that a returned pool will not get closed (by closing the http client) during 141 * the time it takes to submit a request to the pool. Acquisition requests submitted to the pool will 142 * be properly failed if the http client is closed before the acquisition completes. 143 * 144 * This additional complexity means we only have to keep a lock for the scope of this function, as opposed to 145 * the scope of calling execute(). This function will almost always just be a hash lookup and the return of an 146 * existing pool. If we add all of execute() to the scope, we include, at minimum a JNI call to the native 147 * pool implementation. 148 */ getOrCreateConnectionPool(URI uri)149 HttpClientConnectionManager getOrCreateConnectionPool(URI uri) { 150 synchronized (this) { 151 if (isClosed) { 152 throw new IllegalStateException("Client is closed. No more requests can be made with this client."); 153 } 154 155 HttpClientConnectionManager connPool = connectionPools.computeIfAbsent(uri, this::createConnectionPool); 156 connPool.addRef(); 157 return connPool; 158 } 159 } 160 poolKey(SdkHttpRequest sdkRequest)161 URI poolKey(SdkHttpRequest sdkRequest) { 162 return invokeSafely(() -> new URI(sdkRequest.protocol(), null, sdkRequest.host(), 163 sdkRequest.port(), null, null, null)); 164 } 165 166 @Override close()167 public void close() { 168 synchronized (this) { 169 170 if (isClosed) { 171 return; 172 } 173 174 connectionPools.values().forEach(pool -> IoUtils.closeQuietly(pool, log.logger())); 175 ownedSubResources.forEach(r -> IoUtils.closeQuietly(r, log.logger())); 176 ownedSubResources.clear(); 177 178 isClosed = true; 179 } 180 } 181 } 182