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 java.util.stream.Collectors.groupingBy;
19 import static java.util.stream.Collectors.mapping;
20 import static java.util.stream.Collectors.toList;
21 import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;
22
23 import java.io.IOException;
24 import java.net.InetAddress;
25 import java.security.KeyManagementException;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.cert.CertificateException;
28 import java.security.cert.X509Certificate;
29 import java.time.Duration;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.concurrent.TimeUnit;
34 import java.util.stream.Stream;
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.HttpResponse;
42 import org.apache.http.client.CredentialsProvider;
43 import org.apache.http.client.methods.HttpRequestBase;
44 import org.apache.http.client.protocol.HttpClientContext;
45 import org.apache.http.config.Registry;
46 import org.apache.http.config.RegistryBuilder;
47 import org.apache.http.config.SocketConfig;
48 import org.apache.http.conn.ConnectionKeepAliveStrategy;
49 import org.apache.http.conn.HttpClientConnectionManager;
50 import org.apache.http.conn.routing.HttpRoutePlanner;
51 import org.apache.http.conn.socket.ConnectionSocketFactory;
52 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
53 import org.apache.http.conn.ssl.NoopHostnameVerifier;
54 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
55 import org.apache.http.conn.ssl.SSLInitializationException;
56 import org.apache.http.impl.client.HttpClientBuilder;
57 import org.apache.http.impl.client.HttpClients;
58 import org.apache.http.impl.conn.DefaultSchemePortResolver;
59 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
60 import org.apache.http.protocol.HttpRequestExecutor;
61 import software.amazon.awssdk.annotations.SdkPublicApi;
62 import software.amazon.awssdk.annotations.SdkTestInternalApi;
63 import software.amazon.awssdk.http.AbortableInputStream;
64 import software.amazon.awssdk.http.ExecutableHttpRequest;
65 import software.amazon.awssdk.http.HttpExecuteRequest;
66 import software.amazon.awssdk.http.HttpExecuteResponse;
67 import software.amazon.awssdk.http.SdkHttpClient;
68 import software.amazon.awssdk.http.SdkHttpConfigurationOption;
69 import software.amazon.awssdk.http.SdkHttpResponse;
70 import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider;
71 import software.amazon.awssdk.http.TlsKeyManagersProvider;
72 import software.amazon.awssdk.http.TlsTrustManagersProvider;
73 import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
74 import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
75 import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner;
76 import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory;
77 import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper;
78 import software.amazon.awssdk.http.apache.internal.conn.SdkConnectionKeepAliveStrategy;
79 import software.amazon.awssdk.http.apache.internal.conn.SdkTlsSocketFactory;
80 import software.amazon.awssdk.http.apache.internal.impl.ApacheHttpRequestFactory;
81 import software.amazon.awssdk.http.apache.internal.impl.ApacheSdkHttpClient;
82 import software.amazon.awssdk.http.apache.internal.impl.ConnectionManagerAwareHttpClient;
83 import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils;
84 import software.amazon.awssdk.utils.AttributeMap;
85 import software.amazon.awssdk.utils.Logger;
86 import software.amazon.awssdk.utils.Validate;
87
88 /**
89  * An implementation of {@link SdkHttpClient} that uses Apache HTTP client to communicate with the service. This is the most
90  * powerful synchronous client that adds an extra dependency and additional startup latency in exchange for more functionality,
91  * like support for HTTP proxies.
92  *
93  * <p>See software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient for an alternative implementation.</p>
94  *
95  * <p>This can be created via {@link #builder()}</p>
96  */

97 @SdkPublicApi
98 public final class ApacheHttpClient implements SdkHttpClient {
99
100     public static final String CLIENT_NAME = "Apache";
101
102     private static final Logger log = Logger.loggerFor(ApacheHttpClient.class);
103
104     private final ApacheHttpRequestFactory apacheHttpRequestFactory = new ApacheHttpRequestFactory();
105     private final ConnectionManagerAwareHttpClient httpClient;
106     private final ApacheHttpRequestConfig requestConfig;
107     private final AttributeMap resolvedOptions;
108
109     @SdkTestInternalApi
110     ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient,
111                      ApacheHttpRequestConfig requestConfig,
112                      AttributeMap resolvedOptions) {
113         this.httpClient = httpClient;
114         this.requestConfig = requestConfig;
115         this.resolvedOptions = resolvedOptions;
116     }
117
118     private ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions) {
119         this.httpClient = createClient(builder, resolvedOptions);
120         this.requestConfig = createRequestConfig(builder, resolvedOptions);
121         this.resolvedOptions = resolvedOptions;
122     }
123
124     public static Builder builder() {
125         return new DefaultBuilder();
126     }
127
128     private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBuilder configuration,
129                                                           AttributeMap standardOptions) {
130         ApacheConnectionManagerFactory cmFactory = new ApacheConnectionManagerFactory();
131
132         HttpClientBuilder builder = HttpClients.custom();
133         // Note that it is important we register the original connection manager with the
134         // IdleConnectionReaper as it's required for the successful deregistration of managers
135         // from the reaper. See https://github.com/aws/aws-sdk-java/issues/722.
136         HttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions);
137
138         builder.setRequestExecutor(new HttpRequestExecutor())
139                // SDK handles decompression
140                .disableContentCompression()
141                .setKeepAliveStrategy(buildKeepAliveStrategy(standardOptions))
142                .disableRedirectHandling()
143                .disableAutomaticRetries()
144                .setUserAgent(""// SDK will set the user agent header in the pipeline. Don't let Apache waste time
145                .setConnectionManager(ClientConnectionManagerFactory.wrap(cm));
146
147         addProxyConfig(builder, configuration);
148
149         if (useIdleConnectionReaper(standardOptions)) {
150             IdleConnectionReaper.getInstance().registerConnectionManager(
151                     cm, standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis());
152         }
153
154         return new ApacheSdkHttpClient(builder.build(), cm);
155     }
156
157     private void addProxyConfig(HttpClientBuilder builder,
158                                 DefaultBuilder configuration) {
159         ProxyConfiguration proxyConfiguration = configuration.proxyConfiguration;
160
161         Validate.isTrue(configuration.httpRoutePlanner == null || !isProxyEnabled(proxyConfiguration),
162                         "The httpRoutePlanner and proxyConfiguration can't both be configured.");
163         Validate.isTrue(configuration.credentialsProvider == null || !isAuthenticatedProxy(proxyConfiguration),
164                         "The credentialsProvider and proxyConfiguration username/password can't both be configured.");
165
166         HttpRoutePlanner routePlanner = configuration.httpRoutePlanner;
167         if (isProxyEnabled(proxyConfiguration)) {
168             log.debug(() -> "Configuring Proxy. Proxy Host: " + proxyConfiguration.host());
169             routePlanner = new SdkProxyRoutePlanner(proxyConfiguration.host(),
170                                                     proxyConfiguration.port(),
171                                                     proxyConfiguration.scheme(),
172                                                     proxyConfiguration.nonProxyHosts());
173         }
174
175         CredentialsProvider credentialsProvider = configuration.credentialsProvider;
176         if (isAuthenticatedProxy(proxyConfiguration)) {
177             credentialsProvider = ApacheUtils.newProxyCredentialsProvider(proxyConfiguration);
178         }
179
180         if (routePlanner != null) {
181             builder.setRoutePlanner(routePlanner);
182         }
183
184         if (credentialsProvider != null) {
185             builder.setDefaultCredentialsProvider(credentialsProvider);
186         }
187     }
188
189     private ConnectionKeepAliveStrategy buildKeepAliveStrategy(AttributeMap standardOptions) {
190         long maxIdle = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis();
191         return maxIdle > 0 ? new SdkConnectionKeepAliveStrategy(maxIdle) : null;
192     }
193
194     private boolean useIdleConnectionReaper(AttributeMap standardOptions) {
195         return Boolean.TRUE.equals(standardOptions.get(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS));
196     }
197
198     private boolean isAuthenticatedProxy(ProxyConfiguration proxyConfiguration) {
199         return proxyConfiguration.username() != null && proxyConfiguration.password() != null;
200     }
201
202     private boolean isProxyEnabled(ProxyConfiguration proxyConfiguration) {
203         return proxyConfiguration.host() != null
204                && proxyConfiguration.port() > 0;
205     }
206
207     @Override
208     public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
209         HttpRequestBase apacheRequest = toApacheRequest(request);
210         return new ExecutableHttpRequest() {
211             @Override
212             public HttpExecuteResponse call() throws IOException {
213                 return execute(apacheRequest);
214             }
215
216             @Override
217             public void abort() {
218                 apacheRequest.abort();
219             }
220         };
221     }
222
223     @Override
224     public void close() {
225         HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager();
226         IdleConnectionReaper.getInstance().deregisterConnectionManager(cm);
227         cm.shutdown();
228     }
229
230     private HttpExecuteResponse execute(HttpRequestBase apacheRequest) throws IOException {
231         HttpClientContext localRequestContext = ApacheUtils.newClientContext(requestConfig.proxyConfiguration());
232         HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext);
233         return createResponse(httpResponse, apacheRequest);
234     }
235
236     private HttpRequestBase toApacheRequest(HttpExecuteRequest request) {
237         return apacheHttpRequestFactory.create(request, requestConfig);
238     }
239
240     /**
241      * Creates and initializes an HttpResponse object suitable to be passed to an HTTP response
242      * handler object.
243      *
244      * @return The new, initialized HttpResponse object ready to be passed to an HTTP response handler object.
245      * @throws IOException If there were any problems getting any response information from the
246      *                     HttpClient method object.
247      */

248     private HttpExecuteResponse createResponse(org.apache.http.HttpResponse apacheHttpResponse,
249                                                HttpRequestBase apacheRequest) throws IOException {
250         SdkHttpResponse response = SdkHttpResponse.builder()
251                                                   .statusCode(apacheHttpResponse.getStatusLine().getStatusCode())
252                                                   .statusText(apacheHttpResponse.getStatusLine().getReasonPhrase())
253                                                   .headers(transformHeaders(apacheHttpResponse))
254                                                   .build();
255         AbortableInputStream responseBody = apacheHttpResponse.getEntity() != null ?
256                                    toAbortableInputStream(apacheHttpResponse, apacheRequest) : null;
257
258         return HttpExecuteResponse.builder().response(response).responseBody(responseBody).build();
259
260     }
261
262     private AbortableInputStream toAbortableInputStream(HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest)
263             throws IOException {
264         return AbortableInputStream.create(apacheHttpResponse.getEntity().getContent(), apacheRequest::abort);
265     }
266
267     private Map<String, List<String>> transformHeaders(HttpResponse apacheHttpResponse) {
268         return Stream.of(apacheHttpResponse.getAllHeaders())
269                      .collect(groupingBy(Header::getName, mapping(Header::getValue, toList())));
270     }
271
272     private ApacheHttpRequestConfig createRequestConfig(DefaultBuilder builder,
273                                                         AttributeMap resolvedOptions) {
274         return ApacheHttpRequestConfig.builder()
275                                       .socketTimeout(resolvedOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT))
276                                       .connectionTimeout(resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT))
277                                       .connectionAcquireTimeout(
278                                           resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT))
279                                       .proxyConfiguration(builder.proxyConfiguration)
280                                       .localAddress(Optional.ofNullable(builder.localAddress).orElse(null))
281                                       .expectContinueEnabled(Optional.ofNullable(builder.expectContinueEnabled)
282                                                                      .orElse(DefaultConfiguration.EXPECT_CONTINUE_ENABLED))
283                                       .build();
284     }
285
286     @Override
287     public String clientName() {
288         return CLIENT_NAME;
289     }
290
291     /**
292      * Builder for creating an instance of {@link SdkHttpClient}. The factory can be configured through the builder {@link
293      * #builder()}, once built it can create a {@link SdkHttpClient} via {@link #build()} or can be passed to the SDK
294      * client builders directly to have the SDK create and manage the HTTP client. See documentation on the service's respective
295      * client builder for more information on configuring the HTTP layer.
296      *
297      * <pre class="brush: java">
298      * SdkHttpClient httpClient = SdkHttpClient.builder()
299      * .socketTimeout(Duration.ofSeconds(10))
300      * .build();
301      * </pre>
302      */

303     public interface Builder extends SdkHttpClient.Builder<ApacheHttpClient.Builder> {
304
305         /**
306          * The amount of time to wait for data to be transferred over an established, open connection before the connection is
307          * timed out. A duration of 0 means infinity, and is not recommended.
308          */

309         Builder socketTimeout(Duration socketTimeout);
310
311         /**
312          * The amount of time to wait when initially establishing a connection before giving up and timing out. A duration of 0
313          * means infinity, and is not recommended.
314          */

315         Builder connectionTimeout(Duration connectionTimeout);
316
317         /**
318          * The amount of time to wait when acquiring a connection from the pool before giving up and timing out.
319          * @param connectionAcquisitionTimeout the timeout duration
320          * @return this builder for method chaining.
321          */

322         Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout);
323
324         /**
325          * The maximum number of connections allowed in the connection pool. Each built HTTP client has it's own private
326          * connection pool.
327          */

328         Builder maxConnections(Integer maxConnections);
329
330         /**
331          * Configuration that defines how to communicate via an HTTP proxy.
332          */

333         Builder proxyConfiguration(ProxyConfiguration proxyConfiguration);
334
335         /**
336          * Configure the local address that the HTTP client should use for communication.
337          */

338         Builder localAddress(InetAddress localAddress);
339
340         /**
341          * Configure whether the client should send an HTTP expect-continue handshake before each request.
342          */

343         Builder expectContinueEnabled(Boolean expectContinueEnabled);
344
345         /**
346          * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency.
347          */

348         Builder connectionTimeToLive(Duration connectionTimeToLive);
349
350         /**
351          * Configure the maximum amount of time that a connection should be allowed to remain open while idle.
352          */

353         Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout);
354
355         /**
356          * Configure whether the idle connections in the connection pool should be closed asynchronously.
357          * <p>
358          * When enabled, connections left idling for longer than {@link #connectionMaxIdleTime(Duration)} will be
359          * closed. This will not close connections currently in use. By defaultthis is enabled.
360          */

361         Builder useIdleConnectionReaper(Boolean useConnectionReaper);
362
363         /**
364          * Configuration that defines an HTTP route planner that computes the route an HTTP request should take.
365          * May not be used in conjunction with {@link #proxyConfiguration(ProxyConfiguration)}.
366          */

367         Builder httpRoutePlanner(HttpRoutePlanner proxyConfiguration);
368
369         /**
370          * Configuration that defines a custom credential provider for HTTP requests.
371          * May not be used in conjunction with {@link ProxyConfiguration#username()} and {@link ProxyConfiguration#password()}.
372          */

373         Builder credentialsProvider(CredentialsProvider credentialsProvider);
374
375         /**
376          * Configure the {@link TlsKeyManagersProvider} that will provide the {@link javax.net.ssl.KeyManager}s to use
377          * when constructing the SSL context.
378          * <p>
379          * The default used by the client will be {@link SystemPropertyTlsKeyManagersProvider}. Configure an instance of
380          * {@link software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider} or another implementation of
381          * {@link TlsKeyManagersProvider} to override it.
382          */

383         Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider);
384
385         /**
386          * Configure the {@link TlsTrustManagersProvider} that will provide the {@link javax.net.ssl.TrustManager}s to use
387          * when constructing the SSL context.
388          */

389         Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider);
390     }
391
392     private static final class DefaultBuilder implements Builder {
393         private final AttributeMap.Builder standardOptions = AttributeMap.builder();
394         private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build();
395         private InetAddress localAddress;
396         private Boolean expectContinueEnabled;
397         private HttpRoutePlanner httpRoutePlanner;
398         private CredentialsProvider credentialsProvider;
399
400         private DefaultBuilder() {
401         }
402
403         @Override
404         public Builder socketTimeout(Duration socketTimeout) {
405             standardOptions.put(SdkHttpConfigurationOption.READ_TIMEOUT, socketTimeout);
406             return this;
407         }
408
409         public void setSocketTimeout(Duration socketTimeout) {
410             socketTimeout(socketTimeout);
411         }
412
413         @Override
414         public Builder connectionTimeout(Duration connectionTimeout) {
415             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout);
416             return this;
417         }
418
419         public void setConnectionTimeout(Duration connectionTimeout) {
420             connectionTimeout(connectionTimeout);
421         }
422
423         /**
424          * The amount of time to wait when acquiring a connection from the pool before giving up and timing out.
425          * @param connectionAcquisitionTimeout the timeout duration
426          * @return this builder for method chaining.
427          */

428         @Override
429         public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
430             Validate.isPositive(connectionAcquisitionTimeout, "connectionAcquisitionTimeout");
431             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT, connectionAcquisitionTimeout);
432             return this;
433         }
434
435         public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) {
436             connectionAcquisitionTimeout(connectionAcquisitionTimeout);
437         }
438
439         @Override
440         public Builder maxConnections(Integer maxConnections) {
441             standardOptions.put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections);
442             return this;
443         }
444
445         public void setMaxConnections(Integer maxConnections) {
446             maxConnections(maxConnections);
447         }
448
449         @Override
450         public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
451             this.proxyConfiguration = proxyConfiguration;
452             return this;
453         }
454
455         public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) {
456             proxyConfiguration(proxyConfiguration);
457         }
458
459         @Override
460         public Builder localAddress(InetAddress localAddress) {
461             this.localAddress = localAddress;
462             return this;
463         }
464
465         public void setLocalAddress(InetAddress localAddress) {
466             localAddress(localAddress);
467         }
468
469         @Override
470         public Builder expectContinueEnabled(Boolean expectContinueEnabled) {
471             this.expectContinueEnabled = expectContinueEnabled;
472             return this;
473         }
474
475         public void setExpectContinueEnabled(Boolean useExpectContinue) {
476             this.expectContinueEnabled = useExpectContinue;
477         }
478
479         @Override
480         public Builder connectionTimeToLive(Duration connectionTimeToLive) {
481             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, connectionTimeToLive);
482             return this;
483         }
484
485         public void setConnectionTimeToLive(Duration connectionTimeToLive) {
486             connectionTimeToLive(connectionTimeToLive);
487         }
488
489         @Override
490         public Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout) {
491             standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, maxIdleConnectionTimeout);
492             return this;
493         }
494
495         public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) {
496             connectionMaxIdleTime(connectionMaxIdleTime);
497         }
498
499         @Override
500         public Builder useIdleConnectionReaper(Boolean useIdleConnectionReaper) {
501             standardOptions.put(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS, useIdleConnectionReaper);
502             return this;
503         }
504
505         public void setUseIdleConnectionReaper(Boolean useIdleConnectionReaper) {
506             useIdleConnectionReaper(useIdleConnectionReaper);
507         }
508
509         @Override
510         public Builder httpRoutePlanner(HttpRoutePlanner httpRoutePlanner) {
511             this.httpRoutePlanner = httpRoutePlanner;
512             return this;
513         }
514
515         public void setHttpRoutePlanner(HttpRoutePlanner httpRoutePlanner) {
516             httpRoutePlanner(httpRoutePlanner);
517         }
518
519         @Override
520         public Builder credentialsProvider(CredentialsProvider credentialsProvider) {
521             this.credentialsProvider = credentialsProvider;
522             return this;
523         }
524
525         public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
526             credentialsProvider(credentialsProvider);
527         }
528
529         @Override
530         public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
531             standardOptions.put(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider);
532             return this;
533         }
534
535         public void setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) {
536             tlsKeyManagersProvider(tlsKeyManagersProvider);
537         }
538
539         @Override
540         public Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
541             standardOptions.put(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER, tlsTrustManagersProvider);
542             return this;
543         }
544
545         public void setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) {
546             tlsTrustManagersProvider(tlsTrustManagersProvider);
547         }
548
549         @Override
550         public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
551             AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge(
552                 SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS);
553             return new ApacheHttpClient(this, resolvedOptions);
554         }
555     }
556
557     private static class ApacheConnectionManagerFactory {
558
559         public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder configuration,
560                                                   AttributeMap standardOptions) {
561             ConnectionSocketFactory sslsf = getPreferredSocketFactory(configuration, standardOptions);
562
563             PoolingHttpClientConnectionManager cm = new
564                     PoolingHttpClientConnectionManager(
565                     createSocketFactoryRegistry(sslsf),
566                     null,
567                     DefaultSchemePortResolver.INSTANCE,
568                     null,
569                     standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE).toMillis(),
570                     TimeUnit.MILLISECONDS);
571
572             cm.setDefaultMaxPerRoute(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS));
573             cm.setMaxTotal(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS));
574             cm.setDefaultSocketConfig(buildSocketConfig(standardOptions));
575
576             return cm;
577         }
578
579         private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration,
580                                                                   AttributeMap standardOptions) {
581             // TODO v2 custom socket factory
582             return new SdkTlsSocketFactory(getSslContext(standardOptions),
583                                            getHostNameVerifier(standardOptions));
584         }
585
586         private HostnameVerifier getHostNameVerifier(AttributeMap standardOptions) {
587             return standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)
588                    ? NoopHostnameVerifier.INSTANCE
589                    : SSLConnectionSocketFactory.getDefaultHostnameVerifier();
590         }
591
592         private SSLContext getSslContext(AttributeMap standardOptions) {
593             Validate.isTrue(standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) == null ||
594                             !standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES),
595                             "A TlsTrustManagerProvider can't be provided if TrustAllCertificates is also set");
596
597             TrustManager[] trustManagers = null;
598             if (standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) != null) {
599                 trustManagers = standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER).trustManagers();
600             }
601
602             if (standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) {
603                 log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be "
604                                + "used for testing.");
605                 trustManagers = trustAllTrustManager();
606             }
607
608             TlsKeyManagersProvider provider = standardOptions.get(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER);
609             KeyManager[] keyManagers = provider.keyManagers();
610
611             try {
612                 SSLContext sslcontext = SSLContext.getInstance("TLS");
613                 // http://download.java.net/jdk9/docs/technotes/guides/security/jsse/JSSERefGuide.html
614                 sslcontext.init(keyManagers, trustManagers, null);
615                 return sslcontext;
616             } catch (final NoSuchAlgorithmException | KeyManagementException ex) {
617                 throw new SSLInitializationException(ex.getMessage(), ex);
618             }
619         }
620
621         /**
622          * Insecure trust manager to trust all certs. Should only be used for testing.
623          */

624         private static TrustManager[] trustAllTrustManager() {
625             return new TrustManager[] {
626                 new X509TrustManager() {
627                     @Override
628                     public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
629                         log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN());
630                     }
631
632                     @Override
633                     public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
634                         log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN());
635                     }
636
637                     @Override
638                     public X509Certificate[] getAcceptedIssuers() {
639                         return new X509Certificate[0];
640                     }
641                 }
642             };
643         }
644
645         private SocketConfig buildSocketConfig(AttributeMap standardOptions) {
646             return SocketConfig.custom()
647                                // TODO do we want to keep SO keep alive
648                                .setSoKeepAlive(false)
649                                .setSoTimeout(
650                                        saturatedCast(standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT)
651                                                                     .toMillis()))
652                                .setTcpNoDelay(true)
653                                .build();
654         }
655
656         private Registry<ConnectionSocketFactory> createSocketFactoryRegistry(ConnectionSocketFactory sslSocketFactory) {
657             return RegistryBuilder.<ConnectionSocketFactory>create()
658                     .register("http", PlainConnectionSocketFactory.getSocketFactory())
659                     .register("https", sslSocketFactory)
660                     .build();
661         }
662     }
663 }
664