See software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient for an alternative implementation.
+ * + *This can be created via {@link #builder()}
+ */ +@SdkPreviewApi +@SdkPublicApi +public final class Apache5HttpClient implements SdkHttpClient { + + private static final String CLIENT_NAME = "Apache5Preview"; + + private static final Logger log = Logger.loggerFor(Apache5HttpClient.class); + private static final HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = new DefaultHostnameVerifier(); + private final Apache5HttpRequestFactory apacheHttpRequestFactory = new Apache5HttpRequestFactory(); + private final ConnectionManagerAwareHttpClient httpClient; + private final Apache5HttpRequestConfig requestConfig; + private final AttributeMap resolvedOptions; + + @SdkTestInternalApi + Apache5HttpClient(ConnectionManagerAwareHttpClient httpClient, + Apache5HttpRequestConfig requestConfig, + AttributeMap resolvedOptions) { + this.httpClient = httpClient; + this.requestConfig = requestConfig; + this.resolvedOptions = resolvedOptions; + } + + private Apache5HttpClient(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 Apache5HttpClient} with the default properties + * + * @return an {@link Apache5HttpClient} + */ + public static SdkHttpClient create() { + return new DefaultBuilder().build(); + } + + private ConnectionManagerAwareHttpClient createClient(Apache5HttpClient.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. + PoolingHttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions); + + Registry+ * SdkHttpClient httpClient = + * Apache5HttpClient.builder() + * .socketTimeout(Duration.ofSeconds(10)) + * .build(); + *+ */ + public interface Builder extends SdkHttpClient.Builder
Note: A duration of 0 is treated as infinite to maintain backward compatibility with Apache 4.x behavior. + * The SDK handles this internally by not setting the TTL when the value is 0.
+ */ + 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(SSLConnectionSocketFactory 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);
+
+ /**
+ * Configure the authentication scheme registry that can be used to obtain the corresponding authentication scheme
+ * implementation for a given type of authorization challenge.
+ */
+ Builder authSchemeRegistry(Registry All implementations of this interface are mutable and not thread safe.
+ * This value is set to "true" by default which means SDK will automatically use system property values for options that
+ * are not provided during building the {@link ProxyConfiguration} object. To disable this behavior, set this value to
+ * "false".It is important to note that when this property is set to "true," all proxy settings will exclusively originate
+ * from system properties, and no partial settings will be obtained from EnvironmentVariableValues.
+ */
+ Builder useSystemPropertyValues(Boolean useSystemPropertyValues);
+
+ /**
+ * Option whether to use environment variable values for proxy configuration if any of the config options are missing.
+ *
+ * This value is set to "true" by default, which means the SDK will automatically use environment variable values for
+ * proxy configuration options that are not provided during the building of the {@link ProxyConfiguration} object. To
+ * disable this behavior, set this value to "false". It is important to note that when this property is set to "true," all
+ * proxy settings will exclusively originate from environment variableValues, and no partial settings will be obtained
+ * from SystemPropertyValues.
+ * Comma-separated host names in the NO_PROXY environment variable indicate multiple hosts to exclude from
+ * proxy settings.
+ *
+ * @param useEnvironmentVariableValues The option whether to use environment variable values.
+ * @return This object for method chaining.
+ */
+ Builder useEnvironmentVariableValues(Boolean useEnvironmentVariableValues);
+
+ /**
+ * The HTTP scheme to use for connecting to the proxy. Valid values are {@code http} and {@code https}.
+ *
+ * The client defaults to {@code http} if none is given.
+ *
+ * @param scheme The proxy scheme.
+ * @return This object for method chaining.
+ */
+ Builder scheme(String scheme);
+
+ }
+
+ /**
+ * An SDK-internal implementation of {@link Builder}.
+ */
+ private static final class DefaultClientProxyConfigurationBuilder implements Builder {
+
+ private URI endpoint;
+ private String username;
+ private String password;
+ private String ntlmDomain;
+ private String ntlmWorkstation;
+ private Set
+ * We store the content stream reference to avoid calling {@code getContent()} on the wrapped
+ * entity multiple times, which could potentially create new stream instances or perform
+ * unnecessary operations. This ensures we consistently use the same stream instance for
+ * {@code markSupported()} checks and {@code reset()} operations throughout the entity's lifecycle.
+ */
+
+ private static class EntityCreationResult {
+ final InputStreamEntity entity;
+ final InputStream content;
+
+ EntityCreationResult(InputStreamEntity entity, InputStream content) {
+ this.entity = entity;
+ this.content = content;
+ }
+ }
+
+ public RepeatableInputStreamRequestEntity(HttpExecuteRequest request) {
+ this(createInputStreamEntityWithMetadata(request), request);
+ }
+
+ private RepeatableInputStreamRequestEntity(EntityCreationResult result, HttpExecuteRequest request) {
+ super(result.entity);
+ this.content = result.content;
+ this.isChunked = request.httpRequest().matchingHeaders(TRANSFER_ENCODING).contains(CHUNKED);
+ }
+
+ private static EntityCreationResult createInputStreamEntityWithMetadata(HttpExecuteRequest request) {
+ InputStream content = getContent(request.contentStreamProvider());
+
+ /*
+ * If we don't specify a content length when we instantiate our
+ * InputStreamRequestEntity, then HttpClient will attempt to
+ * buffer the entire stream contents into memory to determine
+ * the content length.
+ */
+ long contentLength = request.httpRequest().firstMatchingHeader("Content-Length")
+ .map(RepeatableInputStreamRequestEntity::parseContentLength)
+ .orElse(-1L);
+
+ ContentType contentType = request.httpRequest().firstMatchingHeader("Content-Type")
+ .map(RepeatableInputStreamRequestEntity::parseContentType)
+ .orElse(null);
+
+ InputStreamEntity entity = contentLength >= 0
+ ? new InputStreamEntity(content, contentLength, contentType)
+ : new InputStreamEntity(content, contentType);
+ return new EntityCreationResult(entity, content);
+ }
+
+ private static long parseContentLength(String contentLength) {
+ try {
+ return Long.parseLong(contentLength);
+ } catch (NumberFormatException nfe) {
+ log.warn(() -> "Unable to parse content length from request. Buffering contents in memory.");
+ return -1;
+ }
+ }
+
+ private static ContentType parseContentType(String contentTypeValue) {
+ if (contentTypeValue == null) {
+ return null;
+ }
+ try {
+ return ContentType.parse(contentTypeValue);
+ } catch (Exception e) {
+ log.warn(() -> "Unable to parse content type: " + contentTypeValue);
+ return null;
+ }
+ }
+
+ /**
+ * @return The request content input stream or an empty input stream if there is no content.
+ */
+ private static InputStream getContent(Optional
+ * If an error is encountered the first time we try to write the request
+ * entity, we remember the original exception, and report that as the root
+ * cause if we continue to encounter errors, rather than masking the
+ * original error.
+ */
+ @Override
+ public void writeTo(OutputStream output) throws IOException {
+ try {
+ if (!firstAttempt && isRepeatable()) {
+ content.reset();
+ }
+
+ firstAttempt = false;
+ super.writeTo(output);
+ } catch (IOException ioe) {
+ if (originalException == null) {
+ originalException = ioe;
+ }
+ throw originalException;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ // The InputStreamEntity handles closing the stream when it's closed
+ // We don't need to close our reference separately to avoid double-closing
+ super.close();
+ }
+}
diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/SdkProxyRoutePlanner.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/SdkProxyRoutePlanner.java
new file mode 100644
index 000000000000..b05c1d7e7900
--- /dev/null
+++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/SdkProxyRoutePlanner.java
@@ -0,0 +1,63 @@
+/*
+ * 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.apache5.internal;
+
+import static software.amazon.awssdk.utils.StringUtils.lowerCase;
+
+import java.util.Set;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+
+/**
+ * SdkProxyRoutePlanner delegates a Proxy Route Planner from the settings instead of the
+ * system properties. It will use the proxy created from proxyHost and proxyPort and
+ * filter the hosts who matches nonProxyHosts pattern.
+ */
+@SdkInternalApi
+public class SdkProxyRoutePlanner extends DefaultRoutePlanner {
+
+ private HttpHost proxy;
+ private Set