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.nio.netty;
17 
18 import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
19 import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS;
20 import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS;
21 import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_TIMEOUT_SECONDS;
22 import static software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils.runAndLogError;
23 import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
24 
25 import io.netty.channel.ChannelOption;
26 import io.netty.channel.EventLoopGroup;
27 import io.netty.handler.ssl.SslContext;
28 import io.netty.handler.ssl.SslProvider;
29 import java.net.SocketOptions;
30 import java.net.URI;
31 import java.time.Duration;
32 import java.util.concurrent.CompletableFuture;
33 import java.util.concurrent.ExecutionException;
34 import java.util.concurrent.TimeUnit;
35 import java.util.concurrent.TimeoutException;
36 import java.util.function.Consumer;
37 import software.amazon.awssdk.annotations.SdkPublicApi;
38 import software.amazon.awssdk.annotations.SdkTestInternalApi;
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.SystemPropertyTlsKeyManagersProvider;
43 import software.amazon.awssdk.http.TlsKeyManagersProvider;
44 import software.amazon.awssdk.http.TlsTrustManagersProvider;
45 import software.amazon.awssdk.http.async.AsyncExecuteRequest;
46 import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
47 import software.amazon.awssdk.http.nio.netty.internal.AwaitCloseChannelPoolMap;
48 import software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration;
49 import software.amazon.awssdk.http.nio.netty.internal.NettyRequestExecutor;
50 import software.amazon.awssdk.http.nio.netty.internal.NonManagedEventLoopGroup;
51 import software.amazon.awssdk.http.nio.netty.internal.RequestContext;
52 import software.amazon.awssdk.http.nio.netty.internal.SdkChannelOptions;
53 import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPool;
54 import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPoolMap;
55 import software.amazon.awssdk.http.nio.netty.internal.SharedSdkEventLoopGroup;
56 import software.amazon.awssdk.http.nio.netty.internal.utils.NettyClientLogger;
57 import software.amazon.awssdk.utils.AttributeMap;
58 import software.amazon.awssdk.utils.Either;
59 import software.amazon.awssdk.utils.Validate;
60 
61 /**
62  * An implementation of {@link SdkAsyncHttpClient} that uses a Netty non-blocking HTTP client to communicate with the service.
63  *
64  * <p>This can be created via {@link #builder()}</p>
65  */
66 @SdkPublicApi
67 public final class NettyNioAsyncHttpClient implements SdkAsyncHttpClient {
68 
69     private static final String CLIENT_NAME = "NettyNio";
70 
71     private static final NettyClientLogger log = NettyClientLogger.getLogger(NettyNioAsyncHttpClient.class);
72     private static final long MAX_STREAMS_ALLOWED = 4294967295L; // unsigned 32-bit, 2^32 -1
73     private static final int DEFAULT_INITIAL_WINDOW_SIZE = 1_048_576; // 1MiB
74 
75     // Override connection idle timeout for Netty http client to reduce the frequency of "server failed to complete the
76     // response error". see https://github.com/aws/aws-sdk-java-v2/issues/1122
77     private static final AttributeMap NETTY_HTTP_DEFAULTS =
78         AttributeMap.builder()
79                     .put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, Duration.ofSeconds(5))
80                     .build();
81 
82     private final SdkEventLoopGroup sdkEventLoopGroup;
83     private final SdkChannelPoolMap<URI, ? extends SdkChannelPool> pools;
84     private final NettyConfiguration configuration;
85 
NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefaultsMap)86     private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefaultsMap) {
87         this.configuration = new NettyConfiguration(serviceDefaultsMap);
88         Protocol protocol = serviceDefaultsMap.get(SdkHttpConfigurationOption.PROTOCOL);
89         this.sdkEventLoopGroup = eventLoopGroup(builder);
90 
91         Http2Configuration http2Configuration = builder.http2Configuration;
92 
93         long maxStreams = resolveMaxHttp2Streams(builder.maxHttp2Streams, http2Configuration);
94         int initialWindowSize = resolveInitialWindowSize(http2Configuration);
95 
96         this.pools = AwaitCloseChannelPoolMap.builder()
97                                              .sdkChannelOptions(builder.sdkChannelOptions)
98                                              .configuration(configuration)
99                                              .protocol(protocol)
100                                              .maxStreams(maxStreams)
101                                              .initialWindowSize(initialWindowSize)
102                                              .healthCheckPingPeriod(resolveHealthCheckPingPeriod(http2Configuration))
103                                              .sdkEventLoopGroup(sdkEventLoopGroup)
104                                              .sslProvider(resolveSslProvider(builder))
105                                              .proxyConfiguration(builder.proxyConfiguration)
106                                              .useNonBlockingDnsResolver(builder.useNonBlockingDnsResolver)
107                                              .build();
108     }
109 
110     @SdkTestInternalApi
NettyNioAsyncHttpClient(SdkEventLoopGroup sdkEventLoopGroup, SdkChannelPoolMap<URI, ? extends SdkChannelPool> pools, NettyConfiguration configuration)111     NettyNioAsyncHttpClient(SdkEventLoopGroup sdkEventLoopGroup,
112                             SdkChannelPoolMap<URI, ? extends SdkChannelPool> pools,
113                             NettyConfiguration configuration) {
114         this.sdkEventLoopGroup = sdkEventLoopGroup;
115         this.pools = pools;
116         this.configuration = configuration;
117     }
118 
119     @Override
execute(AsyncExecuteRequest request)120     public CompletableFuture<Void> execute(AsyncExecuteRequest request) {
121         RequestContext ctx = createRequestContext(request);
122         ctx.metricCollector().reportMetric(HTTP_CLIENT_NAME, clientName()); // TODO: Can't this be done in core?
123         return new NettyRequestExecutor(ctx).execute();
124     }
125 
builder()126     public static Builder builder() {
127         return new DefaultBuilder();
128     }
129 
130     /**
131      * Create a {@link NettyNioAsyncHttpClient} with the default properties
132      *
133      * @return an {@link NettyNioAsyncHttpClient}
134      */
create()135     public static SdkAsyncHttpClient create() {
136         return new DefaultBuilder().build();
137     }
138 
createRequestContext(AsyncExecuteRequest request)139     private RequestContext createRequestContext(AsyncExecuteRequest request) {
140         SdkChannelPool pool = pools.get(poolKey(request.request()));
141         return new RequestContext(pool, sdkEventLoopGroup.eventLoopGroup(), request, configuration);
142     }
143 
eventLoopGroup(DefaultBuilder builder)144     private SdkEventLoopGroup eventLoopGroup(DefaultBuilder builder) {
145         Validate.isTrue(builder.eventLoopGroup == null || builder.eventLoopGroupBuilder == null,
146                         "The eventLoopGroup and the eventLoopGroupFactory can't both be configured.");
147         return Either.fromNullable(builder.eventLoopGroup, builder.eventLoopGroupBuilder)
148                      .map(e -> e.map(this::nonManagedEventLoopGroup, SdkEventLoopGroup.Builder::build))
149                      .orElseGet(SharedSdkEventLoopGroup::get);
150     }
151 
poolKey(SdkHttpRequest sdkRequest)152     private static URI poolKey(SdkHttpRequest sdkRequest) {
153         return invokeSafely(() -> new URI(sdkRequest.protocol(), null, sdkRequest.host(),
154                                           sdkRequest.port(), null, null, null));
155     }
156 
resolveSslProvider(DefaultBuilder builder)157     private SslProvider resolveSslProvider(DefaultBuilder builder) {
158         if (builder.sslProvider != null) {
159             return builder.sslProvider;
160         }
161 
162         return SslContext.defaultClientProvider();
163     }
164 
resolveMaxHttp2Streams(Integer topLevelValue, Http2Configuration http2Configuration)165     private long resolveMaxHttp2Streams(Integer topLevelValue, Http2Configuration http2Configuration) {
166         if (topLevelValue != null) {
167             return topLevelValue;
168         }
169 
170         if (http2Configuration == null || http2Configuration.maxStreams() == null) {
171             return MAX_STREAMS_ALLOWED;
172         }
173 
174         return Math.min(http2Configuration.maxStreams(), MAX_STREAMS_ALLOWED);
175     }
176 
resolveInitialWindowSize(Http2Configuration http2Configuration)177     private int resolveInitialWindowSize(Http2Configuration http2Configuration) {
178         if (http2Configuration == null || http2Configuration.initialWindowSize() == null) {
179             return DEFAULT_INITIAL_WINDOW_SIZE;
180         }
181         return http2Configuration.initialWindowSize();
182     }
183 
resolveHealthCheckPingPeriod(Http2Configuration http2Configuration)184     private Duration resolveHealthCheckPingPeriod(Http2Configuration http2Configuration) {
185         if (http2Configuration != null) {
186             return http2Configuration.healthCheckPingPeriod();
187         }
188         return null;
189     }
190 
nonManagedEventLoopGroup(SdkEventLoopGroup eventLoopGroup)191     private SdkEventLoopGroup nonManagedEventLoopGroup(SdkEventLoopGroup eventLoopGroup) {
192         return SdkEventLoopGroup.create(new NonManagedEventLoopGroup(eventLoopGroup.eventLoopGroup()),
193                                         eventLoopGroup.channelFactory());
194     }
195 
196     @Override
close()197     public void close() {
198         runAndLogError(log, "Unable to close channel pools", pools::close);
199         runAndLogError(log, "Unable to shutdown event loop", () ->
200             closeEventLoopUninterruptibly(sdkEventLoopGroup.eventLoopGroup()));
201     }
202 
closeEventLoopUninterruptibly(EventLoopGroup eventLoopGroup)203     private void closeEventLoopUninterruptibly(EventLoopGroup eventLoopGroup) throws ExecutionException {
204         try {
205             eventLoopGroup.shutdownGracefully(EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS,
206                                               EVENTLOOP_SHUTDOWN_TIMEOUT_SECONDS,
207                                               TimeUnit.SECONDS)
208                           .get(EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
209         } catch (InterruptedException e) {
210             Thread.currentThread().interrupt();
211             throw new RuntimeException(e);
212         } catch (TimeoutException e) {
213             log.error(null, () -> String.format("Shutting down Netty EventLoopGroup did not complete within %s seconds",
214                                     EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS));
215         }
216     }
217 
218     @Override
clientName()219     public String clientName() {
220         return CLIENT_NAME;
221     }
222 
223     @SdkTestInternalApi
configuration()224     NettyConfiguration configuration() {
225         return configuration;
226     }
227 
228     /**
229      * Builder that allows configuration of the Netty NIO HTTP implementation. Use {@link #builder()} to configure and construct
230      * a Netty HTTP client.
231      */
232     public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpClient.Builder> {
233 
234         /**
235          * Maximum number of allowed concurrent requests. For HTTP/1.1 this is the same as max connections. For HTTP/2
236          * the number of connections that will be used depends on the max streams allowed per connection.
237          *
238          * <p>
239          * If the maximum number of concurrent requests is exceeded they may be queued in the HTTP client (see
240          * {@link #maxPendingConnectionAcquires(Integer)}</p>) and can cause increased latencies. If the client is overloaded
241          * enough such that the pending connection queue fills up, subsequent requests may be rejected or time out
242          * (see {@link #connectionAcquisitionTimeout(Duration)}).
243          *
244          * @param maxConcurrency New value for max concurrency.
245          * @return This builder for method chaining.
246          */
maxConcurrency(Integer maxConcurrency)247         Builder maxConcurrency(Integer maxConcurrency);
248 
249         /**
250          * The maximum number of pending acquires allowed. Once this exceeds, acquire tries will be failed.
251          *
252          * @param maxPendingAcquires Max number of pending acquires
253          * @return This builder for method chaining.
254          */
maxPendingConnectionAcquires(Integer maxPendingAcquires)255         Builder maxPendingConnectionAcquires(Integer maxPendingAcquires);
256 
257         /**
258          * The amount of time to wait for a read on a socket before an exception is thrown.
259          * Specify {@code Duration.ZERO} to disable.
260          *
261          * @param readTimeout timeout duration
262          * @return this builder for method chaining.
263          */
readTimeout(Duration readTimeout)264         Builder readTimeout(Duration readTimeout);
265 
266         /**
267          * The amount of time to wait for a write on a socket before an exception is thrown.
268          * Specify {@code Duration.ZERO} to disable.
269          *
270          * @param writeTimeout timeout duration
271          * @return this builder for method chaining.
272          */
writeTimeout(Duration writeTimeout)273         Builder writeTimeout(Duration writeTimeout);
274 
275         /**
276          * The amount of time to wait when initially establishing a connection before giving up and timing out.
277          *
278          * @param timeout the timeout duration
279          * @return this builder for method chaining.
280          */
connectionTimeout(Duration timeout)281         Builder connectionTimeout(Duration timeout);
282 
283         /**
284          * The amount of time to wait when acquiring a connection from the pool before giving up and timing out.
285          * @param connectionAcquisitionTimeout the timeout duration
286          * @return this builder for method chaining.
287          */
connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout)288         Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout);
289 
290         /**
291          * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency.
292          *
293          * Unlike {@link #readTimeout(Duration)} and {@link #writeTimeout(Duration)}, this will never close a connection that
294          * is currently in use, so long-lived connections may remain open longer than this time. In particular, an HTTP/2
295          * connection won't be closed as long as there is at least one stream active on the connection.
296          */
connectionTimeToLive(Duration connectionTimeToLive)297         Builder connectionTimeToLive(Duration connectionTimeToLive);
298 
299         /**
300          * Configure the maximum amount of time that a connection should be allowed to remain open while idle. Currently has no
301          * effect if {@link #useIdleConnectionReaper(Boolean)} is false.
302          *
303          * Unlike {@link #readTimeout(Duration)} and {@link #writeTimeout(Duration)}, this will never close a connection that
304          * is currently in use, so long-lived connections may remain open longer than this time.
305          */
connectionMaxIdleTime(Duration maxIdleConnectionTimeout)306         Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout);
307 
308         /**
309          * Configure the maximum amount of time that a TLS handshake is allowed to take from the time the CLIENT HELLO
310          * message is sent to the time the client and server have fully negotiated ciphers and exchanged keys.
311          * @param tlsNegotiationTimeout the timeout duration
312          *
313          * <p>
314          * By default, it's 10 seconds.
315          *
316          * @return this builder for method chaining.
317          */
tlsNegotiationTimeout(Duration tlsNegotiationTimeout)318         Builder tlsNegotiationTimeout(Duration tlsNegotiationTimeout);
319 
320         /**
321          * Configure whether the idle connections in the connection pool should be closed.
322          * <p>
323          * When enabled, connections left idling for longer than {@link #connectionMaxIdleTime(Duration)} will be
324          * closed. This will not close connections currently in use. By default, this is enabled.
325          */
useIdleConnectionReaper(Boolean useConnectionReaper)326         Builder useIdleConnectionReaper(Boolean useConnectionReaper);
327 
328         /**
329          * Sets the {@link SdkEventLoopGroup} to use for the Netty HTTP client. This event loop group may be shared
330          * across multiple HTTP clients for better resource and thread utilization. The preferred way to create
331          * an {@link EventLoopGroup} is by using the {@link SdkEventLoopGroup#builder()} method which will choose the
332          * optimal implementation per the platform.
333          *
334          * <p>The {@link EventLoopGroup} <b>MUST</b> be closed by the caller when it is ready to
335          * be disposed. The SDK will not close the {@link EventLoopGroup} when the HTTP client is closed. See
336          * {@link EventLoopGroup#shutdownGracefully()} to properly close the event loop group.</p>
337          *
338          * <p>This configuration method is only recommended when you wish to share an {@link EventLoopGroup}
339          * with multiple clients. If you do not need to share the group it is recommended to use
340          * {@link #eventLoopGroupBuilder(SdkEventLoopGroup.Builder)} as the SDK will handle its cleanup when
341          * the HTTP client is closed.</p>
342          *
343          * @param eventLoopGroup Netty {@link SdkEventLoopGroup} to use.
344          * @return This builder for method chaining.
345          * @see SdkEventLoopGroup
346          */
eventLoopGroup(SdkEventLoopGroup eventLoopGroup)347         Builder eventLoopGroup(SdkEventLoopGroup eventLoopGroup);
348 
349         /**
350          * Sets the {@link SdkEventLoopGroup.Builder} which will be used to create the {@link SdkEventLoopGroup} for the Netty
351          * HTTP client. This allows for custom configuration of the Netty {@link EventLoopGroup}.
352          *
353          * <p>The {@link EventLoopGroup} created by the builder is managed by the SDK and will be shutdown
354          * when the HTTP client is closed.</p>
355          *
356          * <p>This is the preferred configuration method when you just want to customize the {@link EventLoopGroup}
357          * but not share it across multiple HTTP clients. If you do wish to share an {@link EventLoopGroup}, see
358          * {@link #eventLoopGroup(SdkEventLoopGroup)}</p>
359          *
360          * @param eventLoopGroupBuilder {@link SdkEventLoopGroup.Builder} to use.
361          * @return This builder for method chaining.
362          * @see SdkEventLoopGroup.Builder
363          */
eventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder)364         Builder eventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder);
365 
366         /**
367          * Sets the HTTP protocol to use (i.e. HTTP/1.1 or HTTP/2). Not all services support HTTP/2.
368          *
369          * @param protocol Protocol to use.
370          * @return This builder for method chaining.
371          */
protocol(Protocol protocol)372         Builder protocol(Protocol protocol);
373 
374         /**
375          * Configure whether to enable or disable TCP KeepAlive.
376          * The configuration will be passed to the socket option {@link SocketOptions#SO_KEEPALIVE}.
377          * <p>
378          * By default, this is disabled.
379          * <p>
380          * When enabled, the actual KeepAlive mechanism is dependent on the Operating System and therefore additional TCP
381          * KeepAlive values (like timeout, number of packets, etc) must be configured via the Operating System (sysctl on
382          * Linux/Mac, and Registry values on Windows).
383          */
tcpKeepAlive(Boolean keepConnectionAlive)384         Builder tcpKeepAlive(Boolean keepConnectionAlive);
385 
386         /**
387          * Configures additional {@link ChannelOption} which will be used to create Netty Http client. This allows custom
388          * configuration for Netty.
389          *
390          * <p>
391          * If a {@link ChannelOption} was previously configured, the old value is replaced.
392          *
393          * @param channelOption {@link ChannelOption} to set
394          * @param value See {@link ChannelOption} to find the type of value for each option
395          * @return This builder for method chaining.
396          */
putChannelOption(ChannelOption channelOption, Object value)397         Builder putChannelOption(ChannelOption channelOption, Object value);
398 
399         /**
400          * Sets the max number of concurrent streams for an HTTP/2 connection. This setting is only respected when the HTTP/2
401          * protocol is used.
402          *
403          * <p>Note that this cannot exceed the value of the MAX_CONCURRENT_STREAMS setting returned by the service. If it
404          * does the service setting is used instead.</p>
405          *
406          * @param maxHttp2Streams Max concurrent HTTP/2 streams per connection.
407          * @return This builder for method chaining.
408          *
409          * @deprecated Use {@link #http2Configuration(Http2Configuration)} along with
410          * {@link Http2Configuration.Builder#maxStreams(Long)} instead.
411          */
maxHttp2Streams(Integer maxHttp2Streams)412         Builder maxHttp2Streams(Integer maxHttp2Streams);
413 
414         /**
415          * Sets the {@link SslProvider} to be used in the Netty client.
416          *
417          * <p>If not configured, {@link SslContext#defaultClientProvider()} will be used to determine the SslProvider.
418          *
419          * <p>Note that you might need to add other dependencies if not using JDK's default Ssl Provider.
420          * See https://netty.io/wiki/requirements-for-4.x.html#transport-security-tls
421          *
422          * @param sslProvider the SslProvider
423          * @return the builder of the method chaining.
424          */
sslProvider(SslProvider sslProvider)425         Builder sslProvider(SslProvider sslProvider);
426 
427         /**
428          * Set the proxy configuration for this client. The configured proxy will be used to proxy any HTTP request
429          * destined for any host that does not match any of the hosts in configured non proxy hosts.
430          *
431          * @param proxyConfiguration The proxy configuration.
432          * @return The builder for method chaining.
433          * @see ProxyConfiguration#nonProxyHosts()
434          */
proxyConfiguration(ProxyConfiguration proxyConfiguration)435         Builder proxyConfiguration(ProxyConfiguration proxyConfiguration);
436 
437         /**
438          * Set the {@link TlsKeyManagersProvider} for this client. The {@code KeyManager}s will be used by the client to
439          * authenticate itself with the remote server if necessary when establishing the TLS connection.
440          * <p>
441          * If no provider is configured, the client will default to {@link SystemPropertyTlsKeyManagersProvider}. To
442          * disable any automatic resolution via the system properties, use {@link TlsKeyManagersProvider#noneProvider()}.
443          *
444          * @param keyManagersProvider The {@code TlsKeyManagersProvider}.
445          * @return The builder for method chaining.
446          */
tlsKeyManagersProvider(TlsKeyManagersProvider keyManagersProvider)447         Builder tlsKeyManagersProvider(TlsKeyManagersProvider keyManagersProvider);
448 
449         /**
450          * Configure the {@link TlsTrustManagersProvider} that will provide the {@link javax.net.ssl.TrustManager}s to use
451          * when constructing the SSL context.
452          *
453          * @param trustManagersProvider The {@code TlsKeyManagersProvider}.
454          * @return The builder for method chaining.
455          */
tlsTrustManagersProvider(TlsTrustManagersProvider trustManagersProvider)456         Builder tlsTrustManagersProvider(TlsTrustManagersProvider trustManagersProvider);
457 
458         /**
459          * Set the HTTP/2 specific configuration for this client.
460          * <p>
461          * <b>Note:</b>If {@link #maxHttp2Streams(Integer)} and {@link Http2Configuration#maxStreams()} are both set,
462          * the value set using {@link #maxHttp2Streams(Integer)} takes precedence.
463          *
464          * @param http2Configuration The HTTP/2 configuration object.
465          * @return the builder for method chaining.
466          */
http2Configuration(Http2Configuration http2Configuration)467         Builder http2Configuration(Http2Configuration http2Configuration);
468 
469         /**
470          * Set the HTTP/2 specific configuration for this client.
471          * <p>
472          * <b>Note:</b>If {@link #maxHttp2Streams(Integer)} and {@link Http2Configuration#maxStreams()} are both set,
473          * the value set using {@link #maxHttp2Streams(Integer)} takes precedence.
474          *
475          * @param http2ConfigurationBuilderConsumer The consumer of the HTTP/2 configuration builder object.
476          * @return the builder for method chaining.
477          */
http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer)478         Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer);
479 
480         /**
481          * Configure whether to use a non-blocking dns resolver or not. False by default, as netty's default dns resolver is
482          * blocking; it namely calls java.net.InetAddress.getByName.
483          * <p>
484          * When enabled, a non-blocking dns resolver will be used instead, by modifying netty's bootstrap configuration.
485          * See https://netty.io/news/2016/05/26/4-1-0-Final.html
486          */
useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver)487         Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver);
488     }
489 
490     /**
491      * Factory that allows more advanced configuration of the Netty NIO HTTP implementation. Use {@link #builder()} to
492      * configure and construct an immutable instance of the factory.
493      */
494     private static final class DefaultBuilder implements Builder {
495         private final AttributeMap.Builder standardOptions = AttributeMap.builder();
496 
497         private SdkChannelOptions sdkChannelOptions = new SdkChannelOptions();
498 
499         private SdkEventLoopGroup eventLoopGroup;
500         private SdkEventLoopGroup.Builder eventLoopGroupBuilder;
501         private Integer maxHttp2Streams;
502         private Http2Configuration http2Configuration;
503         private SslProvider sslProvider;
504         private ProxyConfiguration proxyConfiguration;
505         private Boolean useNonBlockingDnsResolver;
506 
DefaultBuilder()507         private DefaultBuilder() {
508         }
509 
510         @Override
maxConcurrency(Integer maxConcurrency)511         public Builder maxConcurrency(Integer maxConcurrency) {
512             standardOptions.put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConcurrency);
513             return this;
514         }
515 
setMaxConcurrency(Integer maxConnectionsPerEndpoint)516         public void setMaxConcurrency(Integer maxConnectionsPerEndpoint) {
517             maxConcurrency(maxConnectionsPerEndpoint);
518         }
519 
520         @Override
maxPendingConnectionAcquires(Integer maxPendingAcquires)521         public Builder maxPendingConnectionAcquires(Integer maxPendingAcquires) {
522             standardOptions.put(SdkHttpConfigurationOption.MAX_PENDING_CONNECTION_ACQUIRES, maxPendingAcquires);
523             return this;
524         }
525 
setMaxPendingConnectionAcquires(Integer maxPendingAcquires)526         public void setMaxPendingConnectionAcquires(Integer maxPendingAcquires) {
527             maxPendingConnectionAcquires(maxPendingAcquires);
528         }
529 
530         @Override
readTimeout(Duration readTimeout)531         public Builder readTimeout(Duration readTimeout) {
532             Validate.isNotNegative(readTimeout, "readTimeout");
533             standardOptions.put(SdkHttpConfigurationOption.READ_TIMEOUT, readTimeout);
534             return this;
535         }
536 
setReadTimeout(Duration readTimeout)537         public void setReadTimeout(Duration readTimeout) {
538             readTimeout(readTimeout);
539         }
540 
541         @Override
writeTimeout(Duration writeTimeout)542         public Builder writeTimeout(Duration writeTimeout) {
543             Validate.isNotNegative(writeTimeout, "writeTimeout");
544             standardOptions.put(SdkHttpConfigurationOption.WRITE_TIMEOUT, writeTimeout);
545             return this;
546         }
547 
setWriteTimeout(Duration writeTimeout)548         public void setWriteTimeout(Duration writeTimeout) {
549             writeTimeout(writeTimeout);
550         }
551 
552         @Override
connectionTimeout(Duration timeout)553         public Builder connectionTimeout(Duration timeout) {
554             Validate.isPositive(timeout, "connectionTimeout");
555             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, timeout);
556             return this;
557         }
558 
setConnectionTimeout(Duration connectionTimeout)559         public void setConnectionTimeout(Duration connectionTimeout) {
560             connectionTimeout(connectionTimeout);
561         }
562 
563         @Override
connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout)564         public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
565             Validate.isPositive(connectionAcquisitionTimeout, "connectionAcquisitionTimeout");
566             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT, connectionAcquisitionTimeout);
567             return this;
568         }
569 
setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout)570         public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
571             connectionAcquisitionTimeout(connectionAcquisitionTimeout);
572         }
573 
574         @Override
connectionTimeToLive(Duration connectionTimeToLive)575         public Builder connectionTimeToLive(Duration connectionTimeToLive) {
576             Validate.isNotNegative(connectionTimeToLive, "connectionTimeToLive");
577             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, connectionTimeToLive);
578             return this;
579         }
580 
setConnectionTimeToLive(Duration connectionTimeToLive)581         public void setConnectionTimeToLive(Duration connectionTimeToLive) {
582             connectionTimeToLive(connectionTimeToLive);
583         }
584 
585         @Override
connectionMaxIdleTime(Duration connectionMaxIdleTime)586         public Builder connectionMaxIdleTime(Duration connectionMaxIdleTime) {
587             Validate.isPositive(connectionMaxIdleTime, "connectionMaxIdleTime");
588             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, connectionMaxIdleTime);
589             return this;
590         }
591 
setConnectionMaxIdleTime(Duration connectionMaxIdleTime)592         public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) {
593             connectionMaxIdleTime(connectionMaxIdleTime);
594         }
595 
596         @Override
useIdleConnectionReaper(Boolean useIdleConnectionReaper)597         public Builder useIdleConnectionReaper(Boolean useIdleConnectionReaper) {
598             standardOptions.put(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS, useIdleConnectionReaper);
599             return this;
600         }
601 
setUseIdleConnectionReaper(Boolean useIdleConnectionReaper)602         public void setUseIdleConnectionReaper(Boolean useIdleConnectionReaper) {
603             useIdleConnectionReaper(useIdleConnectionReaper);
604         }
605 
606         @Override
tlsNegotiationTimeout(Duration tlsNegotiationTimeout)607         public Builder tlsNegotiationTimeout(Duration tlsNegotiationTimeout) {
608             Validate.isPositive(tlsNegotiationTimeout, "tlsNegotiationTimeout");
609             standardOptions.put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT, tlsNegotiationTimeout);
610             return this;
611         }
612 
setTlsNegotiationTimeout(Duration tlsNegotiationTimeout)613         public void setTlsNegotiationTimeout(Duration tlsNegotiationTimeout) {
614             tlsNegotiationTimeout(tlsNegotiationTimeout);
615         }
616 
617         @Override
eventLoopGroup(SdkEventLoopGroup eventLoopGroup)618         public Builder eventLoopGroup(SdkEventLoopGroup eventLoopGroup) {
619             this.eventLoopGroup = eventLoopGroup;
620             return this;
621         }
622 
setEventLoopGroup(SdkEventLoopGroup eventLoopGroup)623         public void setEventLoopGroup(SdkEventLoopGroup eventLoopGroup) {
624             eventLoopGroup(eventLoopGroup);
625         }
626 
627         @Override
eventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder)628         public Builder eventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder) {
629             this.eventLoopGroupBuilder = eventLoopGroupBuilder;
630             return this;
631         }
632 
setEventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder)633         public void setEventLoopGroupBuilder(SdkEventLoopGroup.Builder eventLoopGroupBuilder) {
634             eventLoopGroupBuilder(eventLoopGroupBuilder);
635         }
636 
637         @Override
protocol(Protocol protocol)638         public Builder protocol(Protocol protocol) {
639             standardOptions.put(SdkHttpConfigurationOption.PROTOCOL, protocol);
640             return this;
641         }
642 
setProtocol(Protocol protocol)643         public void setProtocol(Protocol protocol) {
644             protocol(protocol);
645         }
646 
647         @Override
tcpKeepAlive(Boolean keepConnectionAlive)648         public Builder tcpKeepAlive(Boolean keepConnectionAlive) {
649             standardOptions.put(SdkHttpConfigurationOption.TCP_KEEPALIVE, keepConnectionAlive);
650             return this;
651         }
652 
setTcpKeepAlive(Boolean keepConnectionAlive)653         public void setTcpKeepAlive(Boolean keepConnectionAlive) {
654             tcpKeepAlive(keepConnectionAlive);
655         }
656 
657         @Override
putChannelOption(ChannelOption channelOption, Object value)658         public Builder putChannelOption(ChannelOption channelOption, Object value) {
659             this.sdkChannelOptions.putOption(channelOption, value);
660             return this;
661         }
662 
663         @Override
maxHttp2Streams(Integer maxHttp2Streams)664         public Builder maxHttp2Streams(Integer maxHttp2Streams) {
665             this.maxHttp2Streams = maxHttp2Streams;
666             return this;
667         }
668 
setMaxHttp2Streams(Integer maxHttp2Streams)669         public void setMaxHttp2Streams(Integer maxHttp2Streams) {
670             maxHttp2Streams(maxHttp2Streams);
671         }
672 
673         @Override
sslProvider(SslProvider sslProvider)674         public Builder sslProvider(SslProvider sslProvider) {
675             this.sslProvider = sslProvider;
676             return this;
677         }
678 
setSslProvider(SslProvider sslProvider)679         public void setSslProvider(SslProvider sslProvider) {
680             sslProvider(sslProvider);
681         }
682 
683         @Override
proxyConfiguration(ProxyConfiguration proxyConfiguration)684         public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
685             this.proxyConfiguration = proxyConfiguration;
686             return this;
687         }
688 
setProxyConfiguration(ProxyConfiguration proxyConfiguration)689         public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
690             proxyConfiguration(proxyConfiguration);
691         }
692 
693         @Override
tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider)694         public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
695             this.standardOptions.put(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
696             return this;
697         }
698 
setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider)699         public void setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
700             tlsKeyManagersProvider(tlsKeyManagersProvider);
701         }
702 
703         @Override
tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider)704         public Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
705             standardOptions.put(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER, tlsTrustManagersProvider);
706             return this;
707         }
708 
setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider)709         public void setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
710             tlsTrustManagersProvider(tlsTrustManagersProvider);
711         }
712 
713         @Override
http2Configuration(Http2Configuration http2Configuration)714         public Builder http2Configuration(Http2Configuration http2Configuration) {
715             this.http2Configuration = http2Configuration;
716             return this;
717         }
718 
719         @Override
http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer)720         public Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer) {
721             Http2Configuration.Builder builder = Http2Configuration.builder();
722             http2ConfigurationBuilderConsumer.accept(builder);
723             return http2Configuration(builder.build());
724         }
725 
setHttp2Configuration(Http2Configuration http2Configuration)726         public void setHttp2Configuration(Http2Configuration http2Configuration) {
727             http2Configuration(http2Configuration);
728         }
729 
730         @Override
useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver)731         public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
732             this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
733             return this;
734         }
735 
setUseNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver)736         public void setUseNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
737             useNonBlockingDnsResolver(useNonBlockingDnsResolver);
738         }
739 
740         @Override
buildWithDefaults(AttributeMap serviceDefaults)741         public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
742             if (standardOptions.get(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT) == null) {
743                 standardOptions.put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT,
744                                     standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
745             }
746 
747             return new NettyNioAsyncHttpClient(this, standardOptions.build()
748                                                                     .merge(serviceDefaults)
749                                                                     .merge(NETTY_HTTP_DEFAULTS)
750                                                                     .merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
751 
752         }
753     }
754 }
755