• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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