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.apache;
17 
18 import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY;
19 import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
20 import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY;
21 import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY;
22 import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES;
23 import static software.amazon.awssdk.http.apache.internal.conn.ClientConnectionRequestFactory.THREAD_LOCAL_REQUEST_METRIC_COLLECTOR;
24 import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;
25 
26 import java.io.IOException;
27 import java.net.InetAddress;
28 import java.security.KeyManagementException;
29 import java.security.NoSuchAlgorithmException;
30 import java.security.cert.CertificateException;
31 import java.security.cert.X509Certificate;
32 import java.time.Duration;
33 import java.util.Optional;
34 import java.util.concurrent.TimeUnit;
35 import javax.net.ssl.HostnameVerifier;
36 import javax.net.ssl.KeyManager;
37 import javax.net.ssl.SSLContext;
38 import javax.net.ssl.TrustManager;
39 import javax.net.ssl.X509TrustManager;
40 import org.apache.http.Header;
41 import org.apache.http.HeaderIterator;
42 import org.apache.http.HttpResponse;
43 import org.apache.http.client.CredentialsProvider;
44 import org.apache.http.client.methods.HttpRequestBase;
45 import org.apache.http.client.protocol.HttpClientContext;
46 import org.apache.http.config.Registry;
47 import org.apache.http.config.RegistryBuilder;
48 import org.apache.http.config.SocketConfig;
49 import org.apache.http.conn.ConnectionKeepAliveStrategy;
50 import org.apache.http.conn.DnsResolver;
51 import org.apache.http.conn.HttpClientConnectionManager;
52 import org.apache.http.conn.routing.HttpRoutePlanner;
53 import org.apache.http.conn.socket.ConnectionSocketFactory;
54 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
55 import org.apache.http.conn.ssl.NoopHostnameVerifier;
56 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
57 import org.apache.http.conn.ssl.SSLInitializationException;
58 import org.apache.http.impl.client.HttpClientBuilder;
59 import org.apache.http.impl.client.HttpClients;
60 import org.apache.http.impl.conn.DefaultSchemePortResolver;
61 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
62 import org.apache.http.pool.PoolStats;
63 import org.apache.http.protocol.HttpRequestExecutor;
64 import software.amazon.awssdk.annotations.SdkPublicApi;
65 import software.amazon.awssdk.annotations.SdkTestInternalApi;
66 import software.amazon.awssdk.http.AbortableInputStream;
67 import software.amazon.awssdk.http.ExecutableHttpRequest;
68 import software.amazon.awssdk.http.HttpExecuteRequest;
69 import software.amazon.awssdk.http.HttpExecuteResponse;
70 import software.amazon.awssdk.http.SdkHttpClient;
71 import software.amazon.awssdk.http.SdkHttpConfigurationOption;
72 import software.amazon.awssdk.http.SdkHttpResponse;
73 import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider;
74 import software.amazon.awssdk.http.TlsKeyManagersProvider;
75 import software.amazon.awssdk.http.TlsTrustManagersProvider;
76 import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
77 import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
78 import software.amazon.awssdk.http.apache.internal.SdkConnectionReuseStrategy;
79 import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner;
80 import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory;
81 import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper;
82 import software.amazon.awssdk.http.apache.internal.conn.SdkConnectionKeepAliveStrategy;
83 import software.amazon.awssdk.http.apache.internal.conn.SdkTlsSocketFactory;
84 import software.amazon.awssdk.http.apache.internal.impl.ApacheHttpRequestFactory;
85 import software.amazon.awssdk.http.apache.internal.impl.ApacheSdkHttpClient;
86 import software.amazon.awssdk.http.apache.internal.impl.ConnectionManagerAwareHttpClient;
87 import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils;
88 import software.amazon.awssdk.metrics.MetricCollector;
89 import software.amazon.awssdk.metrics.NoOpMetricCollector;
90 import software.amazon.awssdk.utils.AttributeMap;
91 import software.amazon.awssdk.utils.Logger;
92 import software.amazon.awssdk.utils.Validate;
93 
94 /**
95  * An implementation of {@link SdkHttpClient} that uses Apache HTTP client to communicate with the service. This is the most
96  * powerful synchronous client that adds an extra dependency and additional startup latency in exchange for more functionality,
97  * like support for HTTP proxies.
98  *
99  * <p>See software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient for an alternative implementation.</p>
100  *
101  * <p>This can be created via {@link #builder()}</p>
102  */
103 @SdkPublicApi
104 public final class ApacheHttpClient implements SdkHttpClient {
105 
106     public static final String CLIENT_NAME = "Apache";
107 
108     private static final Logger log = Logger.loggerFor(ApacheHttpClient.class);
109 
110     private final ApacheHttpRequestFactory apacheHttpRequestFactory = new ApacheHttpRequestFactory();
111     private final ConnectionManagerAwareHttpClient httpClient;
112     private final ApacheHttpRequestConfig requestConfig;
113     private final AttributeMap resolvedOptions;
114 
115     @SdkTestInternalApi
ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient, ApacheHttpRequestConfig requestConfig, AttributeMap resolvedOptions)116     ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient,
117                      ApacheHttpRequestConfig requestConfig,
118                      AttributeMap resolvedOptions) {
119         this.httpClient = httpClient;
120         this.requestConfig = requestConfig;
121         this.resolvedOptions = resolvedOptions;
122     }
123 
ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions)124     private ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions) {
125         this.httpClient = createClient(builder, resolvedOptions);
126         this.requestConfig = createRequestConfig(builder, resolvedOptions);
127         this.resolvedOptions = resolvedOptions;
128     }
129 
builder()130     public static Builder builder() {
131         return new DefaultBuilder();
132     }
133 
134     /**
135      * Create a {@link ApacheHttpClient} with the default properties
136      *
137      * @return an {@link ApacheHttpClient}
138      */
create()139     public static SdkHttpClient create() {
140         return new DefaultBuilder().build();
141     }
142 
createClient(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions)143     private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBuilder configuration,
144                                                           AttributeMap standardOptions) {
145         ApacheConnectionManagerFactory cmFactory = new ApacheConnectionManagerFactory();
146 
147         HttpClientBuilder builder = HttpClients.custom();
148         // Note that it is important we register the original connection manager with the
149         // IdleConnectionReaper as it's required for the successful deregistration of managers
150         // from the reaper. See https://github.com/aws/aws-sdk-java/issues/722.
151         HttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions);
152 
153         builder.setRequestExecutor(new HttpRequestExecutor())
154                // SDK handles decompression
155                .disableContentCompression()
156                .setKeepAliveStrategy(buildKeepAliveStrategy(standardOptions))
157                .disableRedirectHandling()
158                .disableAutomaticRetries()
159                .setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time
160                .setConnectionReuseStrategy(new SdkConnectionReuseStrategy())
161                .setConnectionManager(ClientConnectionManagerFactory.wrap(cm));
162 
163         addProxyConfig(builder, configuration);
164 
165         if (useIdleConnectionReaper(standardOptions)) {
166             IdleConnectionReaper.getInstance().registerConnectionManager(
167                     cm, standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis());
168         }
169 
170         return new ApacheSdkHttpClient(builder.build(), cm);
171     }
172 
addProxyConfig(HttpClientBuilder builder, DefaultBuilder configuration)173     private void addProxyConfig(HttpClientBuilder builder,
174                                 DefaultBuilder configuration) {
175         ProxyConfiguration proxyConfiguration = configuration.proxyConfiguration;
176 
177         Validate.isTrue(configuration.httpRoutePlanner == null || !isProxyEnabled(proxyConfiguration),
178                         "The httpRoutePlanner and proxyConfiguration can't both be configured.");
179         Validate.isTrue(configuration.credentialsProvider == null || !isAuthenticatedProxy(proxyConfiguration),
180                         "The credentialsProvider and proxyConfiguration username/password can't both be configured.");
181 
182         HttpRoutePlanner routePlanner = configuration.httpRoutePlanner;
183         if (isProxyEnabled(proxyConfiguration)) {
184             log.debug(() -> "Configuring Proxy. Proxy Host: " + proxyConfiguration.host());
185             routePlanner = new SdkProxyRoutePlanner(proxyConfiguration.host(),
186                                                     proxyConfiguration.port(),
187                                                     proxyConfiguration.scheme(),
188                                                     proxyConfiguration.nonProxyHosts());
189         }
190 
191         CredentialsProvider credentialsProvider = configuration.credentialsProvider;
192         if (isAuthenticatedProxy(proxyConfiguration)) {
193             credentialsProvider = ApacheUtils.newProxyCredentialsProvider(proxyConfiguration);
194         }
195 
196         if (routePlanner != null) {
197             builder.setRoutePlanner(routePlanner);
198         }
199 
200         if (credentialsProvider != null) {
201             builder.setDefaultCredentialsProvider(credentialsProvider);
202         }
203     }
204 
buildKeepAliveStrategy(AttributeMap standardOptions)205     private ConnectionKeepAliveStrategy buildKeepAliveStrategy(AttributeMap standardOptions) {
206         long maxIdle = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis();
207         return maxIdle > 0 ? new SdkConnectionKeepAliveStrategy(maxIdle) : null;
208     }
209 
useIdleConnectionReaper(AttributeMap standardOptions)210     private boolean useIdleConnectionReaper(AttributeMap standardOptions) {
211         return Boolean.TRUE.equals(standardOptions.get(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS));
212     }
213 
isAuthenticatedProxy(ProxyConfiguration proxyConfiguration)214     private boolean isAuthenticatedProxy(ProxyConfiguration proxyConfiguration) {
215         return proxyConfiguration.username() != null && proxyConfiguration.password() != null;
216     }
217 
isProxyEnabled(ProxyConfiguration proxyConfiguration)218     private boolean isProxyEnabled(ProxyConfiguration proxyConfiguration) {
219         return proxyConfiguration.host() != null
220                && proxyConfiguration.port() > 0;
221     }
222 
223     @Override
prepareRequest(HttpExecuteRequest request)224     public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
225         MetricCollector metricCollector = request.metricCollector().orElseGet(NoOpMetricCollector::create);
226         metricCollector.reportMetric(HTTP_CLIENT_NAME, clientName());
227         HttpRequestBase apacheRequest = toApacheRequest(request);
228         return new ExecutableHttpRequest() {
229             @Override
230             public HttpExecuteResponse call() throws IOException {
231                 HttpExecuteResponse executeResponse = execute(apacheRequest, metricCollector);
232                 collectPoolMetric(metricCollector);
233                 return executeResponse;
234             }
235 
236             @Override
237             public void abort() {
238                 apacheRequest.abort();
239             }
240         };
241     }
242 
243     @Override
244     public void close() {
245         HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager();
246         IdleConnectionReaper.getInstance().deregisterConnectionManager(cm);
247         cm.shutdown();
248     }
249 
250     private HttpExecuteResponse execute(HttpRequestBase apacheRequest, MetricCollector metricCollector) throws IOException {
251         HttpClientContext localRequestContext = ApacheUtils.newClientContext(requestConfig.proxyConfiguration());
252         THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.set(metricCollector);
253         try {
254             HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext);
255             return createResponse(httpResponse, apacheRequest);
256         } finally {
257             THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.remove();
258         }
259     }
260 
261     private HttpRequestBase toApacheRequest(HttpExecuteRequest request) {
262         return apacheHttpRequestFactory.create(request, requestConfig);
263     }
264 
265     /**
266      * Creates and initializes an HttpResponse object suitable to be passed to an HTTP response
267      * handler object.
268      *
269      * @return The new, initialized HttpResponse object ready to be passed to an HTTP response handler object.
270      * @throws IOException If there were any problems getting any response information from the
271      *                     HttpClient method object.
272      */
273     private HttpExecuteResponse createResponse(org.apache.http.HttpResponse apacheHttpResponse,
274                                                HttpRequestBase apacheRequest) throws IOException {
275         SdkHttpResponse.Builder responseBuilder =
276             SdkHttpResponse.builder()
277                            .statusCode(apacheHttpResponse.getStatusLine().getStatusCode())
278                            .statusText(apacheHttpResponse.getStatusLine().getReasonPhrase());
279 
280         HeaderIterator headerIterator = apacheHttpResponse.headerIterator();
281         while (headerIterator.hasNext()) {
282             Header header = headerIterator.nextHeader();
283             responseBuilder.appendHeader(header.getName(), header.getValue());
284         }
285 
286         AbortableInputStream responseBody = apacheHttpResponse.getEntity() != null ?
287                                    toAbortableInputStream(apacheHttpResponse, apacheRequest) : null;
288 
289         return HttpExecuteResponse.builder().response(responseBuilder.build()).responseBody(responseBody).build();
290 
291     }
292 
293     private AbortableInputStream toAbortableInputStream(HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest)
294             throws IOException {
295         return AbortableInputStream.create(apacheHttpResponse.getEntity().getContent(), apacheRequest::abort);
296     }
297 
298     private ApacheHttpRequestConfig createRequestConfig(DefaultBuilder builder,
299                                                         AttributeMap resolvedOptions) {
300         return ApacheHttpRequestConfig.builder()
301                                       .socketTimeout(resolvedOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT))
302                                       .connectionTimeout(resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT))
303                                       .connectionAcquireTimeout(
304                                           resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT))
305                                       .proxyConfiguration(builder.proxyConfiguration)
306                                       .localAddress(Optional.ofNullable(builder.localAddress).orElse(null))
307                                       .expectContinueEnabled(Optional.ofNullable(builder.expectContinueEnabled)
308                                                                      .orElse(DefaultConfiguration.EXPECT_CONTINUE_ENABLED))
309                                       .build();
310     }
311 
312     private void collectPoolMetric(MetricCollector metricCollector) {
313         HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager();
314         if (cm instanceof PoolingHttpClientConnectionManager && !(metricCollector instanceof NoOpMetricCollector)) {
315             PoolingHttpClientConnectionManager poolingCm = (PoolingHttpClientConnectionManager) cm;
316             PoolStats totalStats = poolingCm.getTotalStats();
317             metricCollector.reportMetric(MAX_CONCURRENCY, totalStats.getMax());
318             metricCollector.reportMetric(AVAILABLE_CONCURRENCY, totalStats.getAvailable());
319             metricCollector.reportMetric(LEASED_CONCURRENCY, totalStats.getLeased());
320             metricCollector.reportMetric(PENDING_CONCURRENCY_ACQUIRES, totalStats.getPending());
321         }
322     }
323 
324     @Override
325     public String clientName() {
326         return CLIENT_NAME;
327     }
328 
329     /**
330      * Builder for creating an instance of {@link SdkHttpClient}. The factory can be configured through the builder {@link
331      * #builder()}, once built it can create a {@link SdkHttpClient} via {@link #build()} or can be passed to the SDK
332      * client builders directly to have the SDK create and manage the HTTP client. See documentation on the service's respective
333      * client builder for more information on configuring the HTTP layer.
334      *
335      * <pre class="brush: java">
336      * SdkHttpClient httpClient =
337      *     ApacheHttpClient.builder()
338      *                     .socketTimeout(Duration.ofSeconds(10))
339      *                     .build();
340      * </pre>
341      */
342     public interface Builder extends SdkHttpClient.Builder<ApacheHttpClient.Builder> {
343 
344         /**
345          * The amount of time to wait for data to be transferred over an established, open connection before the connection is
346          * timed out. A duration of 0 means infinity, and is not recommended.
347          */
348         Builder socketTimeout(Duration socketTimeout);
349 
350         /**
351          * The amount of time to wait when initially establishing a connection before giving up and timing out. A duration of 0
352          * means infinity, and is not recommended.
353          */
354         Builder connectionTimeout(Duration connectionTimeout);
355 
356         /**
357          * The amount of time to wait when acquiring a connection from the pool before giving up and timing out.
358          * @param connectionAcquisitionTimeout the timeout duration
359          * @return this builder for method chaining.
360          */
361         Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout);
362 
363         /**
364          * The maximum number of connections allowed in the connection pool. Each built HTTP client has its own private
365          * connection pool.
366          */
367         Builder maxConnections(Integer maxConnections);
368 
369         /**
370          * Configuration that defines how to communicate via an HTTP proxy.
371          */
372         Builder proxyConfiguration(ProxyConfiguration proxyConfiguration);
373 
374         /**
375          * Configure the local address that the HTTP client should use for communication.
376          */
377         Builder localAddress(InetAddress localAddress);
378 
379         /**
380          * Configure whether the client should send an HTTP expect-continue handshake before each request.
381          */
382         Builder expectContinueEnabled(Boolean expectContinueEnabled);
383 
384         /**
385          * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency.
386          */
387         Builder connectionTimeToLive(Duration connectionTimeToLive);
388 
389         /**
390          * Configure the maximum amount of time that a connection should be allowed to remain open while idle.
391          */
392         Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout);
393 
394         /**
395          * Configure whether the idle connections in the connection pool should be closed asynchronously.
396          * <p>
397          * When enabled, connections left idling for longer than {@link #connectionMaxIdleTime(Duration)} will be
398          * closed. This will not close connections currently in use. By default, this is enabled.
399          */
400         Builder useIdleConnectionReaper(Boolean useConnectionReaper);
401 
402         /**
403          * Configuration that defines a DNS resolver. If no matches are found, the default resolver is used.
404          */
405         Builder dnsResolver(DnsResolver dnsResolver);
406 
407         /**
408          * Configuration that defines a custom Socket factory. If set to a null value, a default factory is used.
409          * <p>
410          * When set to a non-null value, the use of a custom factory implies the configuration options TRUST_ALL_CERTIFICATES,
411          * TLS_TRUST_MANAGERS_PROVIDER, and TLS_KEY_MANAGERS_PROVIDER are ignored.
412          */
413         Builder socketFactory(ConnectionSocketFactory socketFactory);
414 
415         /**
416          * Configuration that defines an HTTP route planner that computes the route an HTTP request should take.
417          * May not be used in conjunction with {@link #proxyConfiguration(ProxyConfiguration)}.
418          */
419         Builder httpRoutePlanner(HttpRoutePlanner proxyConfiguration);
420 
421         /**
422          * Configuration that defines a custom credential provider for HTTP requests.
423          * May not be used in conjunction with {@link ProxyConfiguration#username()} and {@link ProxyConfiguration#password()}.
424          */
425         Builder credentialsProvider(CredentialsProvider credentialsProvider);
426 
427         /**
428          * Configure whether to enable or disable TCP KeepAlive.
429          * The configuration will be passed to the socket option {@link java.net.SocketOptions#SO_KEEPALIVE}.
430          * <p>
431          * By default, this is disabled.
432          * <p>
433          * When enabled, the actual KeepAlive mechanism is dependent on the Operating System and therefore additional TCP
434          * KeepAlive values (like timeout, number of packets, etc) must be configured via the Operating System (sysctl on
435          * Linux/Mac, and Registry values on Windows).
436          */
437         Builder tcpKeepAlive(Boolean keepConnectionAlive);
438 
439         /**
440          * Configure the {@link TlsKeyManagersProvider} that will provide the {@link javax.net.ssl.KeyManager}s to use
441          * when constructing the SSL context.
442          * <p>
443          * The default used by the client will be {@link SystemPropertyTlsKeyManagersProvider}. Configure an instance of
444          * {@link software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider} or another implementation of
445          * {@link TlsKeyManagersProvider} to override it.
446          */
447         Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider);
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         Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider);
454     }
455 
456     private static final class DefaultBuilder implements Builder {
457         private final AttributeMap.Builder standardOptions = AttributeMap.builder();
458         private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build();
459         private InetAddress localAddress;
460         private Boolean expectContinueEnabled;
461         private HttpRoutePlanner httpRoutePlanner;
462         private CredentialsProvider credentialsProvider;
463         private DnsResolver dnsResolver;
464         private ConnectionSocketFactory socketFactory;
465 
466         private DefaultBuilder() {
467         }
468 
469         @Override
470         public Builder socketTimeout(Duration socketTimeout) {
471             standardOptions.put(SdkHttpConfigurationOption.READ_TIMEOUT, socketTimeout);
472             return this;
473         }
474 
475         public void setSocketTimeout(Duration socketTimeout) {
476             socketTimeout(socketTimeout);
477         }
478 
479         @Override
480         public Builder connectionTimeout(Duration connectionTimeout) {
481             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout);
482             return this;
483         }
484 
485         public void setConnectionTimeout(Duration connectionTimeout) {
486             connectionTimeout(connectionTimeout);
487         }
488 
489         /**
490          * The amount of time to wait when acquiring a connection from the pool before giving up and timing out.
491          * @param connectionAcquisitionTimeout the timeout duration
492          * @return this builder for method chaining.
493          */
494         @Override
495         public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
496             Validate.isPositive(connectionAcquisitionTimeout, "connectionAcquisitionTimeout");
497             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT, connectionAcquisitionTimeout);
498             return this;
499         }
500 
501         public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
502             connectionAcquisitionTimeout(connectionAcquisitionTimeout);
503         }
504 
505         @Override
506         public Builder maxConnections(Integer maxConnections) {
507             standardOptions.put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections);
508             return this;
509         }
510 
511         public void setMaxConnections(Integer maxConnections) {
512             maxConnections(maxConnections);
513         }
514 
515         @Override
516         public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
517             this.proxyConfiguration = proxyConfiguration;
518             return this;
519         }
520 
521         public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
522             proxyConfiguration(proxyConfiguration);
523         }
524 
525         @Override
526         public Builder localAddress(InetAddress localAddress) {
527             this.localAddress = localAddress;
528             return this;
529         }
530 
531         public void setLocalAddress(InetAddress localAddress) {
532             localAddress(localAddress);
533         }
534 
535         @Override
536         public Builder expectContinueEnabled(Boolean expectContinueEnabled) {
537             this.expectContinueEnabled = expectContinueEnabled;
538             return this;
539         }
540 
541         public void setExpectContinueEnabled(Boolean useExpectContinue) {
542             this.expectContinueEnabled = useExpectContinue;
543         }
544 
545         @Override
546         public Builder connectionTimeToLive(Duration connectionTimeToLive) {
547             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, connectionTimeToLive);
548             return this;
549         }
550 
551         public void setConnectionTimeToLive(Duration connectionTimeToLive) {
552             connectionTimeToLive(connectionTimeToLive);
553         }
554 
555         @Override
556         public Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout) {
557             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, maxIdleConnectionTimeout);
558             return this;
559         }
560 
561         public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) {
562             connectionMaxIdleTime(connectionMaxIdleTime);
563         }
564 
565         @Override
566         public Builder useIdleConnectionReaper(Boolean useIdleConnectionReaper) {
567             standardOptions.put(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS, useIdleConnectionReaper);
568             return this;
569         }
570 
571         public void setUseIdleConnectionReaper(Boolean useIdleConnectionReaper) {
572             useIdleConnectionReaper(useIdleConnectionReaper);
573         }
574 
575         @Override
576         public Builder dnsResolver(DnsResolver dnsResolver) {
577             this.dnsResolver = dnsResolver;
578             return this;
579         }
580 
581         public void setDnsResolver(DnsResolver dnsResolver) {
582             dnsResolver(dnsResolver);
583         }
584 
585         @Override
586         public Builder socketFactory(ConnectionSocketFactory socketFactory) {
587             this.socketFactory = socketFactory;
588             return this;
589         }
590 
591         public void setSocketFactory(ConnectionSocketFactory socketFactory) {
592             socketFactory(socketFactory);
593         }
594 
595         @Override
596         public Builder httpRoutePlanner(HttpRoutePlanner httpRoutePlanner) {
597             this.httpRoutePlanner = httpRoutePlanner;
598             return this;
599         }
600 
601         public void setHttpRoutePlanner(HttpRoutePlanner httpRoutePlanner) {
602             httpRoutePlanner(httpRoutePlanner);
603         }
604 
605         @Override
606         public Builder credentialsProvider(CredentialsProvider credentialsProvider) {
607             this.credentialsProvider = credentialsProvider;
608             return this;
609         }
610 
611         public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
612             credentialsProvider(credentialsProvider);
613         }
614 
615         @Override
616         public Builder tcpKeepAlive(Boolean keepConnectionAlive) {
617             standardOptions.put(SdkHttpConfigurationOption.TCP_KEEPALIVE, keepConnectionAlive);
618             return this;
619         }
620 
621         public void setTcpKeepAlive(Boolean keepConnectionAlive) {
622             tcpKeepAlive(keepConnectionAlive);
623         }
624 
625         @Override
626         public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
627             standardOptions.put(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
628             return this;
629         }
630 
631         public void setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
632             tlsKeyManagersProvider(tlsKeyManagersProvider);
633         }
634 
635         @Override
636         public Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
637             standardOptions.put(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER, tlsTrustManagersProvider);
638             return this;
639         }
640 
641         public void setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
642             tlsTrustManagersProvider(tlsTrustManagersProvider);
643         }
644 
645         @Override
646         public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
647             AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge(
648                 SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS);
649             return new ApacheHttpClient(this, resolvedOptions);
650         }
651     }
652 
653     private static class ApacheConnectionManagerFactory {
654 
655         public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder configuration,
656                                                   AttributeMap standardOptions) {
657             ConnectionSocketFactory sslsf = getPreferredSocketFactory(configuration, standardOptions);
658 
659             PoolingHttpClientConnectionManager cm = new
660                     PoolingHttpClientConnectionManager(
661                     createSocketFactoryRegistry(sslsf),
662                     null,
663                     DefaultSchemePortResolver.INSTANCE,
664                     configuration.dnsResolver,
665                     standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE).toMillis(),
666                     TimeUnit.MILLISECONDS);
667 
668             cm.setDefaultMaxPerRoute(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS));
669             cm.setMaxTotal(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS));
670             cm.setDefaultSocketConfig(buildSocketConfig(standardOptions));
671 
672             return cm;
673         }
674 
675         private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration,
676                                                                   AttributeMap standardOptions) {
677             return Optional.ofNullable(configuration.socketFactory)
678                            .orElseGet(() -> new SdkTlsSocketFactory(getSslContext(standardOptions),
679                                                                     getHostNameVerifier(standardOptions)));
680         }
681 
682         private HostnameVerifier getHostNameVerifier(AttributeMap standardOptions) {
683             return standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)
684                    ? NoopHostnameVerifier.INSTANCE
685                    : SSLConnectionSocketFactory.getDefaultHostnameVerifier();
686         }
687 
688         private SSLContext getSslContext(AttributeMap standardOptions) {
689             Validate.isTrue(standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) == null ||
690                             !standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES),
691                             "A TlsTrustManagerProvider can't be provided if TrustAllCertificates is also set");
692 
693             TrustManager[] trustManagers = null;
694             if (standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) != null) {
695                 trustManagers = standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER).trustManagers();
696             }
697 
698             if (standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) {
699                 log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be "
700                                + "used for testing.");
701                 trustManagers = trustAllTrustManager();
702             }
703 
704             TlsKeyManagersProvider provider = standardOptions.get(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER);
705             KeyManager[] keyManagers = provider.keyManagers();
706 
707             try {
708                 SSLContext sslcontext = SSLContext.getInstance("TLS");
709                 // http://download.java.net/jdk9/docs/technotes/guides/security/jsse/JSSERefGuide.html
710                 sslcontext.init(keyManagers, trustManagers, null);
711                 return sslcontext;
712             } catch (final NoSuchAlgorithmException | KeyManagementException ex) {
713                 throw new SSLInitializationException(ex.getMessage(), ex);
714             }
715         }
716 
717         /**
718          * Insecure trust manager to trust all certs. Should only be used for testing.
719          */
720         private static TrustManager[] trustAllTrustManager() {
721             return new TrustManager[] {
722                 new X509TrustManager() {
723                     @Override
724                     public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
725                         log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN());
726                     }
727 
728                     @Override
729                     public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
730                         log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN());
731                     }
732 
733                     @Override
734                     public X509Certificate[] getAcceptedIssuers() {
735                         return new X509Certificate[0];
736                     }
737                 }
738             };
739         }
740 
741         private SocketConfig buildSocketConfig(AttributeMap standardOptions) {
742             return SocketConfig.custom()
743                                .setSoKeepAlive(standardOptions.get(SdkHttpConfigurationOption.TCP_KEEPALIVE))
744                                .setSoTimeout(
745                                        saturatedCast(standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT)
746                                                                     .toMillis()))
747                                .setTcpNoDelay(true)
748                                .build();
749         }
750 
751         private Registry<ConnectionSocketFactory> createSocketFactoryRegistry(ConnectionSocketFactory sslSocketFactory) {
752             return RegistryBuilder.<ConnectionSocketFactory>create()
753                     .register("http", PlainConnectionSocketFactory.getSocketFactory())
754                     .register("https", sslSocketFactory)
755                     .build();
756         }
757     }
758 }
759