/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package software.amazon.awssdk.http.apache; import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY; import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME; import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY; import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY; import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES; import static software.amazon.awssdk.http.apache.internal.conn.ClientConnectionRequestFactory.THREAD_LOCAL_REQUEST_METRIC_COLLECTOR; import static software.amazon.awssdk.utils.NumericUtils.saturatedCast; import java.io.IOException; import java.net.InetAddress; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; import java.util.Optional; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.Header; import org.apache.http.HeaderIterator; import org.apache.http.HttpResponse; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.DnsResolver; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLInitializationException; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.DefaultSchemePortResolver; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.pool.PoolStats; import org.apache.http.protocol.HttpRequestExecutor; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; import software.amazon.awssdk.http.AbortableInputStream; import software.amazon.awssdk.http.ExecutableHttpRequest; import software.amazon.awssdk.http.HttpExecuteRequest; import software.amazon.awssdk.http.HttpExecuteResponse; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpConfigurationOption; import software.amazon.awssdk.http.SdkHttpResponse; import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider; import software.amazon.awssdk.http.TlsKeyManagersProvider; import software.amazon.awssdk.http.TlsTrustManagersProvider; import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig; import software.amazon.awssdk.http.apache.internal.DefaultConfiguration; import software.amazon.awssdk.http.apache.internal.SdkConnectionReuseStrategy; import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner; import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory; import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper; import software.amazon.awssdk.http.apache.internal.conn.SdkConnectionKeepAliveStrategy; import software.amazon.awssdk.http.apache.internal.conn.SdkTlsSocketFactory; import software.amazon.awssdk.http.apache.internal.impl.ApacheHttpRequestFactory; import software.amazon.awssdk.http.apache.internal.impl.ApacheSdkHttpClient; import software.amazon.awssdk.http.apache.internal.impl.ConnectionManagerAwareHttpClient; import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.NoOpMetricCollector; import software.amazon.awssdk.utils.AttributeMap; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; /** * An implementation of {@link SdkHttpClient} that uses Apache HTTP client to communicate with the service. This is the most * powerful synchronous client that adds an extra dependency and additional startup latency in exchange for more functionality, * like support for HTTP proxies. * *

See software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient for an alternative implementation.

* *

This can be created via {@link #builder()}

*/ @SdkPublicApi public final class ApacheHttpClient implements SdkHttpClient { public static final String CLIENT_NAME = "Apache"; private static final Logger log = Logger.loggerFor(ApacheHttpClient.class); private final ApacheHttpRequestFactory apacheHttpRequestFactory = new ApacheHttpRequestFactory(); private final ConnectionManagerAwareHttpClient httpClient; private final ApacheHttpRequestConfig requestConfig; private final AttributeMap resolvedOptions; @SdkTestInternalApi ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient, ApacheHttpRequestConfig requestConfig, AttributeMap resolvedOptions) { this.httpClient = httpClient; this.requestConfig = requestConfig; this.resolvedOptions = resolvedOptions; } private ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions) { this.httpClient = createClient(builder, resolvedOptions); this.requestConfig = createRequestConfig(builder, resolvedOptions); this.resolvedOptions = resolvedOptions; } public static Builder builder() { return new DefaultBuilder(); } /** * Create a {@link ApacheHttpClient} with the default properties * * @return an {@link ApacheHttpClient} */ public static SdkHttpClient create() { return new DefaultBuilder().build(); } private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions) { ApacheConnectionManagerFactory cmFactory = new ApacheConnectionManagerFactory(); HttpClientBuilder builder = HttpClients.custom(); // Note that it is important we register the original connection manager with the // IdleConnectionReaper as it's required for the successful deregistration of managers // from the reaper. See https://github.com/aws/aws-sdk-java/issues/722. HttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions); builder.setRequestExecutor(new HttpRequestExecutor()) // SDK handles decompression .disableContentCompression() .setKeepAliveStrategy(buildKeepAliveStrategy(standardOptions)) .disableRedirectHandling() .disableAutomaticRetries() .setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time .setConnectionReuseStrategy(new SdkConnectionReuseStrategy()) .setConnectionManager(ClientConnectionManagerFactory.wrap(cm)); addProxyConfig(builder, configuration); if (useIdleConnectionReaper(standardOptions)) { IdleConnectionReaper.getInstance().registerConnectionManager( cm, standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis()); } return new ApacheSdkHttpClient(builder.build(), cm); } private void addProxyConfig(HttpClientBuilder builder, DefaultBuilder configuration) { ProxyConfiguration proxyConfiguration = configuration.proxyConfiguration; Validate.isTrue(configuration.httpRoutePlanner == null || !isProxyEnabled(proxyConfiguration), "The httpRoutePlanner and proxyConfiguration can't both be configured."); Validate.isTrue(configuration.credentialsProvider == null || !isAuthenticatedProxy(proxyConfiguration), "The credentialsProvider and proxyConfiguration username/password can't both be configured."); HttpRoutePlanner routePlanner = configuration.httpRoutePlanner; if (isProxyEnabled(proxyConfiguration)) { log.debug(() -> "Configuring Proxy. Proxy Host: " + proxyConfiguration.host()); routePlanner = new SdkProxyRoutePlanner(proxyConfiguration.host(), proxyConfiguration.port(), proxyConfiguration.scheme(), proxyConfiguration.nonProxyHosts()); } CredentialsProvider credentialsProvider = configuration.credentialsProvider; if (isAuthenticatedProxy(proxyConfiguration)) { credentialsProvider = ApacheUtils.newProxyCredentialsProvider(proxyConfiguration); } if (routePlanner != null) { builder.setRoutePlanner(routePlanner); } if (credentialsProvider != null) { builder.setDefaultCredentialsProvider(credentialsProvider); } } private ConnectionKeepAliveStrategy buildKeepAliveStrategy(AttributeMap standardOptions) { long maxIdle = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis(); return maxIdle > 0 ? new SdkConnectionKeepAliveStrategy(maxIdle) : null; } private boolean useIdleConnectionReaper(AttributeMap standardOptions) { return Boolean.TRUE.equals(standardOptions.get(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS)); } private boolean isAuthenticatedProxy(ProxyConfiguration proxyConfiguration) { return proxyConfiguration.username() != null && proxyConfiguration.password() != null; } private boolean isProxyEnabled(ProxyConfiguration proxyConfiguration) { return proxyConfiguration.host() != null && proxyConfiguration.port() > 0; } @Override public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) { MetricCollector metricCollector = request.metricCollector().orElseGet(NoOpMetricCollector::create); metricCollector.reportMetric(HTTP_CLIENT_NAME, clientName()); HttpRequestBase apacheRequest = toApacheRequest(request); return new ExecutableHttpRequest() { @Override public HttpExecuteResponse call() throws IOException { HttpExecuteResponse executeResponse = execute(apacheRequest, metricCollector); collectPoolMetric(metricCollector); return executeResponse; } @Override public void abort() { apacheRequest.abort(); } }; } @Override public void close() { HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager(); IdleConnectionReaper.getInstance().deregisterConnectionManager(cm); cm.shutdown(); } private HttpExecuteResponse execute(HttpRequestBase apacheRequest, MetricCollector metricCollector) throws IOException { HttpClientContext localRequestContext = ApacheUtils.newClientContext(requestConfig.proxyConfiguration()); THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.set(metricCollector); try { HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext); return createResponse(httpResponse, apacheRequest); } finally { THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.remove(); } } private HttpRequestBase toApacheRequest(HttpExecuteRequest request) { return apacheHttpRequestFactory.create(request, requestConfig); } /** * Creates and initializes an HttpResponse object suitable to be passed to an HTTP response * handler object. * * @return The new, initialized HttpResponse object ready to be passed to an HTTP response handler object. * @throws IOException If there were any problems getting any response information from the * HttpClient method object. */ private HttpExecuteResponse createResponse(org.apache.http.HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest) throws IOException { SdkHttpResponse.Builder responseBuilder = SdkHttpResponse.builder() .statusCode(apacheHttpResponse.getStatusLine().getStatusCode()) .statusText(apacheHttpResponse.getStatusLine().getReasonPhrase()); HeaderIterator headerIterator = apacheHttpResponse.headerIterator(); while (headerIterator.hasNext()) { Header header = headerIterator.nextHeader(); responseBuilder.appendHeader(header.getName(), header.getValue()); } AbortableInputStream responseBody = apacheHttpResponse.getEntity() != null ? toAbortableInputStream(apacheHttpResponse, apacheRequest) : null; return HttpExecuteResponse.builder().response(responseBuilder.build()).responseBody(responseBody).build(); } private AbortableInputStream toAbortableInputStream(HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest) throws IOException { return AbortableInputStream.create(apacheHttpResponse.getEntity().getContent(), apacheRequest::abort); } private ApacheHttpRequestConfig createRequestConfig(DefaultBuilder builder, AttributeMap resolvedOptions) { return ApacheHttpRequestConfig.builder() .socketTimeout(resolvedOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT)) .connectionTimeout(resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT)) .connectionAcquireTimeout( resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT)) .proxyConfiguration(builder.proxyConfiguration) .localAddress(Optional.ofNullable(builder.localAddress).orElse(null)) .expectContinueEnabled(Optional.ofNullable(builder.expectContinueEnabled) .orElse(DefaultConfiguration.EXPECT_CONTINUE_ENABLED)) .build(); } private void collectPoolMetric(MetricCollector metricCollector) { HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager(); if (cm instanceof PoolingHttpClientConnectionManager && !(metricCollector instanceof NoOpMetricCollector)) { PoolingHttpClientConnectionManager poolingCm = (PoolingHttpClientConnectionManager) cm; PoolStats totalStats = poolingCm.getTotalStats(); metricCollector.reportMetric(MAX_CONCURRENCY, totalStats.getMax()); metricCollector.reportMetric(AVAILABLE_CONCURRENCY, totalStats.getAvailable()); metricCollector.reportMetric(LEASED_CONCURRENCY, totalStats.getLeased()); metricCollector.reportMetric(PENDING_CONCURRENCY_ACQUIRES, totalStats.getPending()); } } @Override public String clientName() { return CLIENT_NAME; } /** * Builder for creating an instance of {@link SdkHttpClient}. The factory can be configured through the builder {@link * #builder()}, once built it can create a {@link SdkHttpClient} via {@link #build()} or can be passed to the SDK * client builders directly to have the SDK create and manage the HTTP client. See documentation on the service's respective * client builder for more information on configuring the HTTP layer. * *
     * SdkHttpClient httpClient =
     *     ApacheHttpClient.builder()
     *                     .socketTimeout(Duration.ofSeconds(10))
     *                     .build();
     * 
*/ public interface Builder extends SdkHttpClient.Builder { /** * The amount of time to wait for data to be transferred over an established, open connection before the connection is * timed out. A duration of 0 means infinity, and is not recommended. */ Builder socketTimeout(Duration socketTimeout); /** * The amount of time to wait when initially establishing a connection before giving up and timing out. A duration of 0 * means infinity, and is not recommended. */ Builder connectionTimeout(Duration connectionTimeout); /** * The amount of time to wait when acquiring a connection from the pool before giving up and timing out. * @param connectionAcquisitionTimeout the timeout duration * @return this builder for method chaining. */ Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout); /** * The maximum number of connections allowed in the connection pool. Each built HTTP client has its own private * connection pool. */ Builder maxConnections(Integer maxConnections); /** * Configuration that defines how to communicate via an HTTP proxy. */ Builder proxyConfiguration(ProxyConfiguration proxyConfiguration); /** * Configure the local address that the HTTP client should use for communication. */ Builder localAddress(InetAddress localAddress); /** * Configure whether the client should send an HTTP expect-continue handshake before each request. */ Builder expectContinueEnabled(Boolean expectContinueEnabled); /** * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency. */ Builder connectionTimeToLive(Duration connectionTimeToLive); /** * Configure the maximum amount of time that a connection should be allowed to remain open while idle. */ Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout); /** * Configure whether the idle connections in the connection pool should be closed asynchronously. *

* When enabled, connections left idling for longer than {@link #connectionMaxIdleTime(Duration)} will be * closed. This will not close connections currently in use. By default, this is enabled. */ Builder useIdleConnectionReaper(Boolean useConnectionReaper); /** * Configuration that defines a DNS resolver. If no matches are found, the default resolver is used. */ Builder dnsResolver(DnsResolver dnsResolver); /** * Configuration that defines a custom Socket factory. If set to a null value, a default factory is used. *

* When set to a non-null value, the use of a custom factory implies the configuration options TRUST_ALL_CERTIFICATES, * TLS_TRUST_MANAGERS_PROVIDER, and TLS_KEY_MANAGERS_PROVIDER are ignored. */ Builder socketFactory(ConnectionSocketFactory socketFactory); /** * Configuration that defines an HTTP route planner that computes the route an HTTP request should take. * May not be used in conjunction with {@link #proxyConfiguration(ProxyConfiguration)}. */ Builder httpRoutePlanner(HttpRoutePlanner proxyConfiguration); /** * Configuration that defines a custom credential provider for HTTP requests. * May not be used in conjunction with {@link ProxyConfiguration#username()} and {@link ProxyConfiguration#password()}. */ Builder credentialsProvider(CredentialsProvider credentialsProvider); /** * Configure whether to enable or disable TCP KeepAlive. * The configuration will be passed to the socket option {@link java.net.SocketOptions#SO_KEEPALIVE}. *

* By default, this is disabled. *

* When enabled, the actual KeepAlive mechanism is dependent on the Operating System and therefore additional TCP * KeepAlive values (like timeout, number of packets, etc) must be configured via the Operating System (sysctl on * Linux/Mac, and Registry values on Windows). */ Builder tcpKeepAlive(Boolean keepConnectionAlive); /** * Configure the {@link TlsKeyManagersProvider} that will provide the {@link javax.net.ssl.KeyManager}s to use * when constructing the SSL context. *

* The default used by the client will be {@link SystemPropertyTlsKeyManagersProvider}. Configure an instance of * {@link software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider} or another implementation of * {@link TlsKeyManagersProvider} to override it. */ Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider); /** * Configure the {@link TlsTrustManagersProvider} that will provide the {@link javax.net.ssl.TrustManager}s to use * when constructing the SSL context. */ Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider); } private static final class DefaultBuilder implements Builder { private final AttributeMap.Builder standardOptions = AttributeMap.builder(); private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build(); private InetAddress localAddress; private Boolean expectContinueEnabled; private HttpRoutePlanner httpRoutePlanner; private CredentialsProvider credentialsProvider; private DnsResolver dnsResolver; private ConnectionSocketFactory socketFactory; private DefaultBuilder() { } @Override public Builder socketTimeout(Duration socketTimeout) { standardOptions.put(SdkHttpConfigurationOption.READ_TIMEOUT, socketTimeout); return this; } public void setSocketTimeout(Duration socketTimeout) { socketTimeout(socketTimeout); } @Override public Builder connectionTimeout(Duration connectionTimeout) { standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout); return this; } public void setConnectionTimeout(Duration connectionTimeout) { connectionTimeout(connectionTimeout); } /** * The amount of time to wait when acquiring a connection from the pool before giving up and timing out. * @param connectionAcquisitionTimeout the timeout duration * @return this builder for method chaining. */ @Override public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { Validate.isPositive(connectionAcquisitionTimeout, "connectionAcquisitionTimeout"); standardOptions.put(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT, connectionAcquisitionTimeout); return this; } public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { connectionAcquisitionTimeout(connectionAcquisitionTimeout); } @Override public Builder maxConnections(Integer maxConnections) { standardOptions.put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections); return this; } public void setMaxConnections(Integer maxConnections) { maxConnections(maxConnections); } @Override public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) { this.proxyConfiguration = proxyConfiguration; return this; } public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) { proxyConfiguration(proxyConfiguration); } @Override public Builder localAddress(InetAddress localAddress) { this.localAddress = localAddress; return this; } public void setLocalAddress(InetAddress localAddress) { localAddress(localAddress); } @Override public Builder expectContinueEnabled(Boolean expectContinueEnabled) { this.expectContinueEnabled = expectContinueEnabled; return this; } public void setExpectContinueEnabled(Boolean useExpectContinue) { this.expectContinueEnabled = useExpectContinue; } @Override public Builder connectionTimeToLive(Duration connectionTimeToLive) { standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, connectionTimeToLive); return this; } public void setConnectionTimeToLive(Duration connectionTimeToLive) { connectionTimeToLive(connectionTimeToLive); } @Override public Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout) { standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, maxIdleConnectionTimeout); return this; } public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) { connectionMaxIdleTime(connectionMaxIdleTime); } @Override public Builder useIdleConnectionReaper(Boolean useIdleConnectionReaper) { standardOptions.put(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS, useIdleConnectionReaper); return this; } public void setUseIdleConnectionReaper(Boolean useIdleConnectionReaper) { useIdleConnectionReaper(useIdleConnectionReaper); } @Override public Builder dnsResolver(DnsResolver dnsResolver) { this.dnsResolver = dnsResolver; return this; } public void setDnsResolver(DnsResolver dnsResolver) { dnsResolver(dnsResolver); } @Override public Builder socketFactory(ConnectionSocketFactory socketFactory) { this.socketFactory = socketFactory; return this; } public void setSocketFactory(ConnectionSocketFactory socketFactory) { socketFactory(socketFactory); } @Override public Builder httpRoutePlanner(HttpRoutePlanner httpRoutePlanner) { this.httpRoutePlanner = httpRoutePlanner; return this; } public void setHttpRoutePlanner(HttpRoutePlanner httpRoutePlanner) { httpRoutePlanner(httpRoutePlanner); } @Override public Builder credentialsProvider(CredentialsProvider credentialsProvider) { this.credentialsProvider = credentialsProvider; return this; } public void setCredentialsProvider(CredentialsProvider credentialsProvider) { credentialsProvider(credentialsProvider); } @Override public Builder tcpKeepAlive(Boolean keepConnectionAlive) { standardOptions.put(SdkHttpConfigurationOption.TCP_KEEPALIVE, keepConnectionAlive); return this; } public void setTcpKeepAlive(Boolean keepConnectionAlive) { tcpKeepAlive(keepConnectionAlive); } @Override public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) { standardOptions.put(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider); return this; } public void setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) { tlsKeyManagersProvider(tlsKeyManagersProvider); } @Override public Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) { standardOptions.put(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER, tlsTrustManagersProvider); return this; } public void setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) { tlsTrustManagersProvider(tlsTrustManagersProvider); } @Override public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); return new ApacheHttpClient(this, resolvedOptions); } } private static class ApacheConnectionManagerFactory { public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions) { ConnectionSocketFactory sslsf = getPreferredSocketFactory(configuration, standardOptions); PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager( createSocketFactoryRegistry(sslsf), null, DefaultSchemePortResolver.INSTANCE, configuration.dnsResolver, standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE).toMillis(), TimeUnit.MILLISECONDS); cm.setDefaultMaxPerRoute(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); cm.setMaxTotal(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); cm.setDefaultSocketConfig(buildSocketConfig(standardOptions)); return cm; } private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions) { return Optional.ofNullable(configuration.socketFactory) .orElseGet(() -> new SdkTlsSocketFactory(getSslContext(standardOptions), getHostNameVerifier(standardOptions))); } private HostnameVerifier getHostNameVerifier(AttributeMap standardOptions) { return standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES) ? NoopHostnameVerifier.INSTANCE : SSLConnectionSocketFactory.getDefaultHostnameVerifier(); } private SSLContext getSslContext(AttributeMap standardOptions) { Validate.isTrue(standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) == null || !standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES), "A TlsTrustManagerProvider can't be provided if TrustAllCertificates is also set"); TrustManager[] trustManagers = null; if (standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) != null) { trustManagers = standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER).trustManagers(); } if (standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) { log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be " + "used for testing."); trustManagers = trustAllTrustManager(); } TlsKeyManagersProvider provider = standardOptions.get(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER); KeyManager[] keyManagers = provider.keyManagers(); try { SSLContext sslcontext = SSLContext.getInstance("TLS"); // http://download.java.net/jdk9/docs/technotes/guides/security/jsse/JSSERefGuide.html sslcontext.init(keyManagers, trustManagers, null); return sslcontext; } catch (final NoSuchAlgorithmException | KeyManagementException ex) { throw new SSLInitializationException(ex.getMessage(), ex); } } /** * Insecure trust manager to trust all certs. Should only be used for testing. */ private static TrustManager[] trustAllTrustManager() { return new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN()); } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN()); } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; } private SocketConfig buildSocketConfig(AttributeMap standardOptions) { return SocketConfig.custom() .setSoKeepAlive(standardOptions.get(SdkHttpConfigurationOption.TCP_KEEPALIVE)) .setSoTimeout( saturatedCast(standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT) .toMillis())) .setTcpNoDelay(true) .build(); } private Registry createSocketFactoryRegistry(ConnectionSocketFactory sslSocketFactory) { return RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build(); } } }