All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.http.apache.ApacheHttpClient Maven / Gradle / Ivy

Go to download

A single bundled dependency that includes all service and dependent JARs with third-party libraries relocated to different namespaces.

There is a newer version: 2.5.20
Show newest version
/*
 * Copyright 2010-2018 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 java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.CONNECTION_TIMEOUT;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_CONNECTIONS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.READ_TIMEOUT;
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.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
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.HttpClientConnectionManager;
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.protocol.HttpRequestExecutor;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.http.AbortableCallable;
import software.amazon.awssdk.http.AbortableInputStream;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpFullResponse;
import software.amazon.awssdk.http.SdkRequestContext;
import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
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.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.ConnectionManagerAwareHttpClient;
import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.Logger;

/**
 * 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 { 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; 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(); } private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions) { ApacheConnectionManagerFactory cmFactory = new ApacheConnectionManagerFactory(); final 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. final HttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions); builder.setRequestExecutor(new HttpRequestExecutor()) // SDK handles decompression .disableContentCompression() .setKeepAliveStrategy(buildKeepAliveStrategy(configuration)) .disableRedirectHandling() .disableAutomaticRetries() .setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time .setConnectionManager(ClientConnectionManagerFactory.wrap(cm)); addProxyConfig(builder, configuration.proxyConfiguration); // TODO idle connection reaper // if (.useReaper()) { // IdleConnectionReaper.registerConnectionManager(cm, settings.getMaxIdleConnectionTime()); // } return new software.amazon.awssdk.http.apache.internal.impl.ApacheSdkHttpClient(builder.build(), cm); } private void addProxyConfig(HttpClientBuilder builder, ProxyConfiguration proxyConfiguration) { if (isProxyEnabled(proxyConfiguration)) { log.debug(() -> "Configuring Proxy. Proxy Host: " + proxyConfiguration.endpoint()); builder.setRoutePlanner(new SdkProxyRoutePlanner(proxyConfiguration.endpoint().getHost(), proxyConfiguration.endpoint().getPort(), proxyConfiguration.nonProxyHosts())); if (isAuthenticatedProxy(proxyConfiguration)) { builder.setDefaultCredentialsProvider(ApacheUtils.newProxyCredentialsProvider(proxyConfiguration)); } } } private ConnectionKeepAliveStrategy buildKeepAliveStrategy(ApacheHttpClient.DefaultBuilder configuration) { final long maxIdle = Optional.ofNullable(configuration.connectionMaxIdleTime) .orElse(DefaultConfiguration.MAX_IDLE_CONNECTION_TIME) .toMillis(); return maxIdle > 0 ? new SdkConnectionKeepAliveStrategy(maxIdle) : null; } private boolean isAuthenticatedProxy(ProxyConfiguration proxyConfiguration) { return proxyConfiguration.username() != null && proxyConfiguration.password() != null; } private boolean isProxyEnabled(ProxyConfiguration proxyConfiguration) { return proxyConfiguration.endpoint() != null && proxyConfiguration.endpoint().getHost() != null && proxyConfiguration.endpoint().getPort() > 0; } @Override public AbortableCallable prepareRequest(SdkHttpFullRequest request, SdkRequestContext context) { final HttpRequestBase apacheRequest = toApacheRequest(request); return new AbortableCallable() { @Override public SdkHttpFullResponse call() throws Exception { return execute(apacheRequest); } @Override public void abort() { apacheRequest.abort(); } }; } @Override public Optional getConfigurationValue(SdkHttpConfigurationOption key) { return Optional.ofNullable(resolvedOptions.get(key)); } @Override public void close() { httpClient.getHttpClientConnectionManager().shutdown(); } private SdkHttpFullResponse execute(HttpRequestBase apacheRequest) throws IOException { HttpClientContext localRequestContext = ApacheUtils.newClientContext(requestConfig.proxyConfiguration()); HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext); return createResponse(httpResponse, apacheRequest); } private HttpRequestBase toApacheRequest(SdkHttpFullRequest 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 SdkHttpFullResponse createResponse(org.apache.http.HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest) throws IOException { return SdkHttpFullResponse.builder() .statusCode(apacheHttpResponse.getStatusLine().getStatusCode()) .statusText(apacheHttpResponse.getStatusLine().getReasonPhrase()) .content(apacheHttpResponse.getEntity() != null ? toAbortableInputStream(apacheHttpResponse, apacheRequest) : null) .headers(transformHeaders(apacheHttpResponse)) .build(); } private AbortableInputStream toAbortableInputStream(HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest) throws IOException { return new AbortableInputStream(apacheHttpResponse.getEntity().getContent(), apacheRequest::abort); } private Map> transformHeaders(HttpResponse apacheHttpResponse) { return Stream.of(apacheHttpResponse.getAllHeaders()) .collect(groupingBy(Header::getName, mapping(Header::getValue, toList()))); } private ApacheHttpRequestConfig createRequestConfig(DefaultBuilder builder, AttributeMap resolvedOptions) { return ApacheHttpRequestConfig.builder() .socketTimeout(resolvedOptions.get(READ_TIMEOUT)) .connectionTimeout(resolvedOptions.get(CONNECTION_TIMEOUT)) .proxyConfiguration(builder.proxyConfiguration) .localAddress(Optional.ofNullable(builder.localAddress).orElse(null)) .expectContinueEnabled(Optional.ofNullable(builder.expectContinueEnabled) .orElse(DefaultConfiguration.EXPECT_CONTINUE_ENABLED)) .build(); } /** * 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 = SdkHttpClient.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 maximum number of connections allowed in the connection pool. Each built HTTP client has it's 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); } 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 Duration connectionTimeToLive; private Duration connectionMaxIdleTime; private DefaultBuilder() { } @Override public Builder socketTimeout(Duration socketTimeout) { standardOptions.put(READ_TIMEOUT, socketTimeout); return this; } public void setSocketTimeout(Duration socketTimeout) { socketTimeout(socketTimeout); } @Override public Builder connectionTimeout(Duration connectionTimeout) { standardOptions.put(CONNECTION_TIMEOUT, connectionTimeout); return this; } public void setConnectionTimeout(Duration connectionTimeout) { connectionTimeout(connectionTimeout); } @Override public Builder maxConnections(Integer maxConnections) { standardOptions.put(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) { this.connectionTimeToLive = connectionTimeToLive; return this; } public void setConnectionTimeToLive(Duration connectionTimeToLive) { connectionTimeToLive(connectionTimeToLive); } @Override public Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout) { this.connectionMaxIdleTime = maxIdleConnectionTimeout; return this; } public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) { connectionMaxIdleTime(connectionMaxIdleTime); } @Override public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge(GLOBAL_HTTP_DEFAULTS); return new ApacheHttpClient(this, resolvedOptions); } } private static class ApacheConnectionManagerFactory { public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions) { ConnectionSocketFactory sslsf = getPreferredSocketFactory(standardOptions); final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager( createSocketFactoryRegistry(sslsf), null, DefaultSchemePortResolver.INSTANCE, null, Optional.ofNullable(configuration.connectionTimeToLive) .orElse(DefaultConfiguration.CONNECTION_POOL_TTL) .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(AttributeMap standardOptions) { // TODO v2 custom socket factory return 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) { TrustManager[] trustManagers = null; 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(); } try { final SSLContext sslcontext = SSLContext.getInstance("TLS"); // http://download.java.net/jdk9/docs/technotes/guides/security/jsse/JSSERefGuide.html sslcontext.init(null, 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() // TODO do we want to keep SO keep alive .setSoKeepAlive(false) .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(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy