/* * 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
* 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