diff --git a/.changes/next-release/feature-ApacheHTTPClient5-bd35717.json b/.changes/next-release/feature-ApacheHTTPClient5-bd35717.json new file mode 100644 index 000000000000..6902de05110a --- /dev/null +++ b/.changes/next-release/feature-ApacheHTTPClient5-bd35717.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Apache HTTP Client 5", + "contributor": "", + "description": "Replace deprecated APIs from Apache HttpClient 5.5.x with corresponding recommended APIs" +} diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java index d39522574561..d5e1a25ad846 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java @@ -39,11 +39,13 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.auth.AuthSchemeFactory; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -53,9 +55,11 @@ import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.routing.HttpRoutePlanner; +import org.apache.hc.client5.http.routing.RoutingSupport; import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; +import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; @@ -70,6 +74,7 @@ import org.apache.hc.core5.pool.PoolStats; import org.apache.hc.core5.ssl.SSLInitializationException; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.SdkTestInternalApi; @@ -285,14 +290,25 @@ private HttpExecuteResponse execute(HttpUriRequestBase apacheRequest, MetricColl HttpClientContext localRequestContext = Apache5Utils.newClientContext(requestConfig.proxyConfiguration()); THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.set(metricCollector); try { - HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext); - // Create a connection-aware input stream that closes the response when closed + HttpHost target = determineTarget(apacheRequest); + ClassicHttpResponse httpResponse = httpClient.executeOpen(target, apacheRequest, localRequestContext); return createResponse(httpResponse, apacheRequest); } finally { THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.remove(); } } + /** + * Determines the target host from the request using Apache HttpClient's official routing support utility. + */ + private static HttpHost determineTarget(ClassicHttpRequest request) throws IOException { + try { + return RoutingSupport.determineHost(request); + } catch (HttpException ex) { + throw new ClientProtocolException(ex); + } + } + private HttpUriRequestBase toApacheRequest(HttpExecuteRequest request) { return apacheHttpRequestFactory.create(request, requestConfig); } @@ -355,7 +371,6 @@ private Apache5HttpRequestConfig createRequestConfig(DefaultBuilder builder, AttributeMap resolvedOptions) { return Apache5HttpRequestConfig.builder() .socketTimeout(resolvedOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT)) - .connectionTimeout(resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT)) .connectionAcquireTimeout( resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT)) .proxyConfiguration(builder.proxyConfiguration) @@ -464,12 +479,15 @@ public interface Builder extends SdkHttpClient.Builder - * 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. + * Configure a custom TLS strategy for SSL/TLS connections. + * This is the preferred method over the ConnectionSocketFactory. + * + * @param tlsSocketStrategy The TLS strategy to use for upgrading connections to TLS. + * If null, default TLS configuration will be used. + * @return This builder for method chaining + */ - Builder socketFactory(SSLConnectionSocketFactory socketFactory); + Builder tlsSocketStrategy(TlsSocketStrategy tlsSocketStrategy); /** * Configuration that defines an HTTP route planner that computes the route an HTTP request should take. @@ -527,7 +545,7 @@ private static final class DefaultBuilder implements Builder { private HttpRoutePlanner httpRoutePlanner; private CredentialsProvider credentialsProvider; private DnsResolver dnsResolver; - private SSLConnectionSocketFactory socketFactory; + private TlsSocketStrategy tlsStrategy; private DefaultBuilder() { } @@ -649,15 +667,11 @@ public void setDnsResolver(DnsResolver dnsResolver) { } @Override - public Builder socketFactory(SSLConnectionSocketFactory socketFactory) { - this.socketFactory = socketFactory; + public Builder tlsSocketStrategy(TlsSocketStrategy tlsSocketStrategy) { + this.tlsStrategy = tlsSocketStrategy; return this; } - public void setSocketFactory(SSLConnectionSocketFactory socketFactory) { - socketFactory(socketFactory); - } - @Override public Builder httpRoutePlanner(HttpRoutePlanner httpRoutePlanner) { this.httpRoutePlanner = httpRoutePlanner; @@ -731,31 +745,44 @@ public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { private static class ApacheConnectionManagerFactory { public PoolingHttpClientConnectionManager create(Apache5HttpClient.DefaultBuilder configuration, - AttributeMap standardOptions) { - // TODO : Deprecated method needs to be removed with new replacements - SSLConnectionSocketFactory sslsf = getPreferredSocketFactory(configuration, standardOptions); + AttributeMap standardOptions) { + + TlsSocketStrategy tlsStrategy = getPreferredTlsStrategy(configuration, standardOptions); PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) + .setTlsSocketStrategy(tlsStrategy) .setSchemePortResolver(DefaultSchemePortResolver.INSTANCE) .setDnsResolver(configuration.dnsResolver); - Duration connectionTtl = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE); - if (!connectionTtl.isZero()) { - // Skip TTL=0 to maintain backward compatibility (infinite in 4.x vs immediate expiration in 5.x) - builder.setConnectionTimeToLive(TimeValue.of(connectionTtl.toMillis(), TimeUnit.MILLISECONDS)); - } builder.setMaxConnPerRoute(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); builder.setMaxConnTotal(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); builder.setDefaultSocketConfig(buildSocketConfig(standardOptions)); + builder.setDefaultConnectionConfig(getConnectionConfig(standardOptions)); return builder.build(); } - private SSLConnectionSocketFactory getPreferredSocketFactory(Apache5HttpClient.DefaultBuilder configuration, - AttributeMap standardOptions) { - return Optional.ofNullable(configuration.socketFactory) - .orElseGet(() -> new SdkTlsSocketFactory(getSslContext(standardOptions), - getHostNameVerifier(standardOptions))); + private static ConnectionConfig getConnectionConfig(AttributeMap standardOptions) { + ConnectionConfig.Builder connectionConfigBuilder = + ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds( + standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT).toMillis())) + .setSocketTimeout(Timeout.ofMilliseconds( + standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT).toMillis())); + Duration connectionTtl = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE); + if (!connectionTtl.isZero()) { + // Skip TTL=0 to maintain backward compatibility (infinite in 4.x vs immediate expiration in 5.x) + connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); + } + return connectionConfigBuilder.build(); + } + + private TlsSocketStrategy getPreferredTlsStrategy(Apache5HttpClient.DefaultBuilder configuration, + AttributeMap standardOptions) { + if (configuration.tlsStrategy != null) { + return configuration.tlsStrategy; + } + return new SdkTlsSocketFactory(getSslContext(standardOptions), + getHostNameVerifier(standardOptions)); } diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/Apache5HttpRequestConfig.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/Apache5HttpRequestConfig.java index 92bf31c33bfa..159ae9aceca9 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/Apache5HttpRequestConfig.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/Apache5HttpRequestConfig.java @@ -27,14 +27,12 @@ public final class Apache5HttpRequestConfig { private final Duration socketTimeout; - private final Duration connectionTimeout; private final Duration connectionAcquireTimeout; private final boolean expectContinueEnabled; private final ProxyConfiguration proxyConfiguration; private Apache5HttpRequestConfig(Builder builder) { this.socketTimeout = builder.socketTimeout; - this.connectionTimeout = builder.connectionTimeout; this.connectionAcquireTimeout = builder.connectionAcquireTimeout; this.expectContinueEnabled = builder.expectContinueEnabled; this.proxyConfiguration = builder.proxyConfiguration; @@ -44,10 +42,6 @@ public Duration socketTimeout() { return socketTimeout; } - public Duration connectionTimeout() { - return connectionTimeout; - } - public Duration connectionAcquireTimeout() { return connectionAcquireTimeout; } @@ -73,7 +67,6 @@ public static Builder builder() { public static final class Builder { private Duration socketTimeout; - private Duration connectionTimeout; private Duration connectionAcquireTimeout; private boolean expectContinueEnabled; private ProxyConfiguration proxyConfiguration; @@ -86,11 +79,6 @@ public Builder socketTimeout(Duration socketTimeout) { return this; } - public Builder connectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - return this; - } - public Builder connectionAcquireTimeout(Duration connectionAcquireTimeout) { this.connectionAcquireTimeout = connectionAcquireTimeout; return this; diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionManagerFactory.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionManagerFactory.java index d3e30aec3532..208c47712643 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionManagerFactory.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionManagerFactory.java @@ -49,7 +49,7 @@ public static HttpClientConnectionManager wrap(HttpClientConnectionManager orig) /** * Further wraps {@link LeaseRequest} to capture performance metrics. */ - private static class InstrumentedHttpClientConnectionManager extends DelegatingHttpClientConnectionManager { + private static final class InstrumentedHttpClientConnectionManager extends DelegatingHttpClientConnectionManager { private InstrumentedHttpClientConnectionManager(HttpClientConnectionManager delegate) { super(delegate); diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionRequestFactory.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionRequestFactory.java index b107b7c59df7..513a8da42d0b 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionRequestFactory.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/ClientConnectionRequestFactory.java @@ -55,7 +55,7 @@ static LeaseRequest wrap(LeaseRequest orig) { /** * Measures the latency of {@link LeaseRequest#get(Timeout)}. */ - private static class InstrumentedConnectionRequest extends DelegatingConnectionRequest { + private static final class InstrumentedConnectionRequest extends DelegatingConnectionRequest { private InstrumentedConnectionRequest(LeaseRequest delegate) { super(delegate); diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactory.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactory.java index 8f2a0ef44406..62359fc9b851 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactory.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactory.java @@ -16,22 +16,19 @@ package software.amazon.awssdk.http.apache5.internal.conn; import java.io.IOException; -import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.util.TimeValue; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.http.apache5.internal.net.SdkSocket; +import software.amazon.awssdk.http.apache5.internal.net.SdkSslSocket; import software.amazon.awssdk.utils.Logger; @SdkInternalApi -public class SdkTlsSocketFactory extends SSLConnectionSocketFactory { +public class SdkTlsSocketFactory extends DefaultClientTlsStrategy { private static final Logger log = Logger.loggerFor(SdkTlsSocketFactory.class); @@ -39,27 +36,30 @@ public SdkTlsSocketFactory(SSLContext sslContext, HostnameVerifier hostnameVerif super(sslContext, hostnameVerifier); if (sslContext == null) { throw new IllegalArgumentException( - "sslContext must not be null. " + "Use SSLContext.getDefault() if you are unsure."); + "sslContext must not be null. Use SSLContext.getDefault() if you are unsure."); } } @Override - protected final void prepareSocket(SSLSocket socket) { + protected void initializeSocket(SSLSocket socket) { + super.initializeSocket(socket); log.debug(() -> String.format("socket.getSupportedProtocols(): %s, socket.getEnabledProtocols(): %s", Arrays.toString(socket.getSupportedProtocols()), Arrays.toString(socket.getEnabledProtocols()))); } @Override - public Socket connectSocket(TimeValue connectTimeout, - Socket socket, - HttpHost host, - InetSocketAddress remoteAddress, - InetSocketAddress localAddress, - HttpContext context) throws IOException { - log.trace(() -> String.format("Connecting to %s:%s", remoteAddress.getAddress(), remoteAddress.getPort())); + public SSLSocket upgrade(Socket socket, + String target, + int port, + Object attachment, + HttpContext context) throws IOException { + log.trace(() -> String.format("Upgrading socket to TLS for %s:%s", target, port)); - Socket connectSocket = super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context); - return new SdkSocket(connectSocket); + SSLSocket upgradedSocket = super.upgrade(socket, target, port, attachment, context); + + // Wrap the upgraded SSLSocket in SdkSSLSocket for logging + return new SdkSslSocket(upgradedSocket); } + } diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5HttpRequestFactory.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5HttpRequestFactory.java index fc3ecf5c1cd6..e9eaad4cc968 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5HttpRequestFactory.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5HttpRequestFactory.java @@ -52,7 +52,7 @@ public class Apache5HttpRequestFactory { private static final List IGNORE_HEADERS = Arrays.asList(HttpHeaders.CONTENT_LENGTH, HttpHeaders.HOST, HttpHeaders.TRANSFER_ENCODING); - public HttpUriRequestBase create(final HttpExecuteRequest request, final Apache5HttpRequestConfig requestConfig) { + public HttpUriRequestBase create(HttpExecuteRequest request, Apache5HttpRequestConfig requestConfig) { HttpUriRequestBase base = createApacheRequest(request, sanitizeUri(request.httpRequest())); addHeadersToRequest(base, request.httpRequest()); addRequestConfig(base, request.httpRequest(), requestConfig); @@ -90,12 +90,10 @@ private URI sanitizeUri(SdkHttpRequest request) { private void addRequestConfig(HttpUriRequestBase base, SdkHttpRequest request, Apache5HttpRequestConfig requestConfig) { - int connectTimeout = saturatedCast(requestConfig.connectionTimeout().toMillis()); int connectAcquireTimeout = saturatedCast(requestConfig.connectionAcquireTimeout().toMillis()); RequestConfig.Builder requestConfigBuilder = RequestConfig .custom() .setConnectionRequestTimeout(connectAcquireTimeout, TimeUnit.MILLISECONDS) - .setConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .setResponseTimeout(saturatedCast(requestConfig.socketTimeout().toMillis()), TimeUnit.MILLISECONDS); /* diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5SdkHttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5SdkHttpClient.java index 06ed5efad6a6..a9585325f792 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5SdkHttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/impl/Apache5SdkHttpClient.java @@ -65,6 +65,11 @@ public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request) return delegate.execute(target, request); } + @Override + public ClassicHttpResponse executeOpen(HttpHost target, ClassicHttpRequest request, HttpContext context) throws IOException { + return delegate.executeOpen(target, request, context); + } + @Override public HttpResponse execute(HttpHost target, ClassicHttpRequest request, HttpContext context) throws IOException { return delegate.execute(target, request, context); diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/DelegateSocket.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/DelegateSocket.java deleted file mode 100644 index cc203f059630..000000000000 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/DelegateSocket.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.net; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.channels.SocketChannel; -import software.amazon.awssdk.annotations.SdkInternalApi; - -/** - * Socket delegate class. Subclasses could extend this class, so that - * they only need to override methods they are interested in enhancing. - */ -@SdkInternalApi -public class DelegateSocket extends Socket { - - protected final Socket sock; - - public DelegateSocket(Socket sock) { - this.sock = sock; - } - - @Override - public void connect(SocketAddress endpoint) throws IOException { - sock.connect(endpoint); - } - - @Override - public void connect(SocketAddress endpoint, int timeout) throws IOException { - sock.connect(endpoint, timeout); - } - - @Override - public void bind(SocketAddress bindpoint) throws IOException { - sock.bind(bindpoint); - } - - @Override - public InetAddress getInetAddress() { - return sock.getInetAddress(); - } - - @Override - public InetAddress getLocalAddress() { - return sock.getLocalAddress(); - } - - @Override - public int getPort() { - return sock.getPort(); - } - - @Override - public int getLocalPort() { - return sock.getLocalPort(); - } - - @Override - public SocketAddress getRemoteSocketAddress() { - return sock.getRemoteSocketAddress(); - } - - @Override - public SocketAddress getLocalSocketAddress() { - return sock.getLocalSocketAddress(); - } - - @Override - public SocketChannel getChannel() { - return sock.getChannel(); - } - - @Override - public InputStream getInputStream() throws IOException { - return sock.getInputStream(); - } - - @Override - public OutputStream getOutputStream() throws IOException { - return sock.getOutputStream(); - } - - @Override - public void setTcpNoDelay(boolean on) throws SocketException { - sock.setTcpNoDelay(on); - } - - @Override - public boolean getTcpNoDelay() throws SocketException { - return sock.getTcpNoDelay(); - } - - @Override - public void setSoLinger(boolean on, int linger) throws SocketException { - sock.setSoLinger(on, linger); - } - - @Override - public int getSoLinger() throws SocketException { - return sock.getSoLinger(); - } - - @Override - public void sendUrgentData(int data) throws IOException { - sock.sendUrgentData(data); - } - - @Override - public void setOOBInline(boolean on) throws SocketException { - sock.setOOBInline(on); - } - - @Override - public boolean getOOBInline() throws SocketException { - return sock.getOOBInline(); - } - - @Override - public void setSoTimeout(int timeout) throws SocketException { - sock.setSoTimeout(timeout); - } - - @Override - public int getSoTimeout() throws SocketException { - return sock.getSoTimeout(); - } - - @Override - public void setSendBufferSize(int size) throws SocketException { - sock.setSendBufferSize(size); - } - - @Override - public int getSendBufferSize() throws SocketException { - return sock.getSendBufferSize(); - } - - @Override - public void setReceiveBufferSize(int size) throws SocketException { - sock.setReceiveBufferSize(size); - } - - @Override - public int getReceiveBufferSize() throws SocketException { - return sock.getReceiveBufferSize(); - } - - @Override - public void setKeepAlive(boolean on) throws SocketException { - sock.setKeepAlive(on); - } - - @Override - public boolean getKeepAlive() throws SocketException { - return sock.getKeepAlive(); - } - - @Override - public void setTrafficClass(int tc) throws SocketException { - sock.setTrafficClass(tc); - } - - @Override - public int getTrafficClass() throws SocketException { - return sock.getTrafficClass(); - } - - @Override - public void setReuseAddress(boolean on) throws SocketException { - sock.setReuseAddress(on); - } - - @Override - public boolean getReuseAddress() throws SocketException { - return sock.getReuseAddress(); - } - - @Override - public void close() throws IOException { - sock.close(); - } - - @Override - public void shutdownInput() throws IOException { - sock.shutdownInput(); - } - - @Override - public void shutdownOutput() throws IOException { - sock.shutdownOutput(); - } - - @Override - public String toString() { - return sock.toString(); - } - - @Override - public boolean isConnected() { - return sock.isConnected(); - } - - @Override - public boolean isBound() { - return sock.isBound(); - } - - @Override - public boolean isClosed() { - return sock.isClosed(); - } - - @Override - public boolean isInputShutdown() { - return sock.isInputShutdown(); - } - - @Override - public boolean isOutputShutdown() { - return sock.isOutputShutdown(); - } - - @Override - public void setPerformancePreferences(int connectionTime, int latency, - int bandwidth) { - sock.setPerformancePreferences(connectionTime, latency, bandwidth); - } -} diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/SdkSocket.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/SdkSocket.java deleted file mode 100644 index 286ce1b87d65..000000000000 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/net/SdkSocket.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.net; - -import java.io.IOException; -import java.net.Socket; -import java.net.SocketAddress; -import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.utils.Logger; - -@SdkInternalApi -public class SdkSocket extends DelegateSocket { - private static final Logger log = Logger.loggerFor(SdkSocket.class); - - public SdkSocket(Socket sock) { - super(sock); - log.debug(() -> "created: " + endpoint()); - } - - /** - * Returns the endpoint in the format of "address:port" - */ - private String endpoint() { - return sock.getInetAddress() + ":" + sock.getPort(); - } - - @Override - public void connect(SocketAddress endpoint) throws IOException { - log.trace(() -> "connecting to: " + endpoint); - sock.connect(endpoint); - log.debug(() -> "connected to: " + endpoint); - } - - @Override - public void connect(SocketAddress endpoint, int timeout) throws IOException { - log.trace(() -> "connecting to: " + endpoint); - sock.connect(endpoint, timeout); - log.debug(() -> "connected to: " + endpoint); - } - - @Override - public void close() throws IOException { - log.debug(() -> "closing " + endpoint()); - sock.close(); - } - - @Override - public void shutdownInput() throws IOException { - log.debug(() -> "shutting down input of " + endpoint()); - sock.shutdownInput(); - } - - @Override - public void shutdownOutput() throws IOException { - log.debug(() -> "shutting down output of " + endpoint()); - sock.shutdownOutput(); - } -} diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5Utils.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5Utils.java index 5bd726463206..2b6afe771775 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5Utils.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/internal/utils/Apache5Utils.java @@ -79,6 +79,7 @@ public static CredentialsProvider newProxyCredentialsProvider(ProxyConfiguration * Returns a new instance of NTCredentials used for proxy authentication. */ private static Credentials newNtCredentials(ProxyConfiguration proxyConfiguration) { + // Deprecated NTCredentials is used to maintain backward compatibility with Apache4. return new NTCredentials( proxyConfiguration.username(), proxyConfiguration.password().toCharArray(), diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsAuthTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsAuthTest.java index 45e4b44a6d59..87771b1ab2a8 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsAuthTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsAuthTest.java @@ -27,15 +27,16 @@ import com.github.tomakehurst.wiremock.WireMockServer; import java.io.IOException; +import java.net.Socket; import java.net.SocketException; import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; +import org.apache.hc.core5.http.protocol.HttpContext; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -113,7 +114,7 @@ public void methodTeardown() { } @Test - public void canMakeHttpsRequestWhenKeyProviderConfigured() throws IOException { + public void prepareRequest_whenKeyProviderConfigured_successfullyMakesHttpsRequest() throws IOException { client = Apache5HttpClient.builder() .tlsKeyManagersProvider(keyManagersProvider) .build(); @@ -122,13 +123,13 @@ public void canMakeHttpsRequestWhenKeyProviderConfigured() throws IOException { } @Test - public void requestFailsWhenKeyProviderNotConfigured() throws IOException { + public void prepareRequest_whenKeyProviderNotConfigured_throwsSslException() throws IOException { client = Apache5HttpClient.builder().tlsKeyManagersProvider(NoneTlsKeyManagersProvider.getInstance()).build(); assertThatThrownBy(() -> makeRequestWithHttpClient(client)).isInstanceOfAny(SSLException.class, SocketException.class); } @Test - public void authenticatesWithTlsProxy() throws IOException { + public void prepareRequest_whenTlsProxyConfigured_authenticatesSuccessfully() throws IOException { ProxyConfiguration proxyConfig = ProxyConfiguration.builder() .endpoint(URI.create("https://localhost:" + wireMockServer.httpsPort())) .build(); @@ -145,7 +146,7 @@ public void authenticatesWithTlsProxy() throws IOException { } @Test - public void defaultTlsKeyManagersProviderIsSystemPropertyProvider() throws IOException { + public void build_whenNoTlsKeyManagersProviderSet_usesSystemPropertyProvider() throws IOException { System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString()); System.setProperty(SSL_KEY_STORE_TYPE.property(), CLIENT_STORE_TYPE); System.setProperty(SSL_KEY_STORE_PASSWORD.property(), STORE_PASSWORD); @@ -161,7 +162,7 @@ public void defaultTlsKeyManagersProviderIsSystemPropertyProvider() throws IOExc } @Test - public void defaultTlsKeyManagersProviderIsSystemPropertyProvider_explicitlySetToNull() throws IOException { + public void build_whenTlsKeyManagersProviderExplicitlySetToNull_usesSystemPropertyProvider() throws IOException { System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString()); System.setProperty(SSL_KEY_STORE_TYPE.property(), CLIENT_STORE_TYPE); System.setProperty(SSL_KEY_STORE_PASSWORD.property(), STORE_PASSWORD); @@ -177,9 +178,7 @@ public void defaultTlsKeyManagersProviderIsSystemPropertyProvider_explicitlySetT } @Test - public void build_notSettingSocketFactory_configuresClientWithDefaultSocketFactory() throws IOException, - NoSuchAlgorithmException, - KeyManagementException { + public void build_whenSocketFactoryNotSet_configuresDefaultSocketFactory() throws Exception { System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString()); System.setProperty(SSL_KEY_STORE_TYPE.property(), CLIENT_STORE_TYPE); System.setProperty(SSL_KEY_STORE_PASSWORD.property(), STORE_PASSWORD); @@ -192,8 +191,9 @@ public void build_notSettingSocketFactory_configuresClientWithDefaultSocketFacto SSLContext sslcontext = SSLContext.getInstance("TLS"); sslcontext.init(keyManagers, null, null); - ConnectionSocketFactory socketFactory = new SdkTlsSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); - ConnectionSocketFactory socketFactoryMock = Mockito.spy(socketFactory); + // Use TlsSocketStrategy instead of ConnectionSocketFactory + TlsSocketStrategy socketFactory = new SdkTlsSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); + TlsSocketStrategy socketFactoryMock = Mockito.spy(socketFactory); client = Apache5HttpClient.builder().build(); @@ -209,35 +209,6 @@ public void build_notSettingSocketFactory_configuresClientWithDefaultSocketFacto Mockito.verifyNoInteractions(socketFactoryMock); } - @Test - public void build_settingCustomSocketFactory_configuresClientWithGivenSocketFactory() throws IOException, - NoSuchAlgorithmException, - KeyManagementException { - TlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, - CLIENT_STORE_TYPE, - STORE_PASSWORD); - KeyManager[] keyManagers = provider.keyManagers(); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, null, null); - - SdkTlsSocketFactory socketFactory = new SdkTlsSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); - SdkTlsSocketFactory socketFactorySpy = Mockito.spy(socketFactory); - - - client = Apache5HttpClient.builder() - .socketFactory(socketFactorySpy) - .build(); - makeRequestWithHttpClient(client); - - Mockito.verify(socketFactorySpy).createLayeredSocket( - Mockito.any(), // Socket - Mockito.anyString(), // Target host - Mockito.anyInt(), // Port - Mockito.any() // HttpContext - ); - } - private HttpExecuteResponse makeRequestWithHttpClient(SdkHttpClient httpClient) throws IOException { SdkHttpRequest httpRequest = SdkHttpFullRequest.builder() .method(SdkHttpMethod.GET) @@ -252,4 +223,34 @@ private HttpExecuteResponse makeRequestWithHttpClient(SdkHttpClient httpClient) return httpClient.prepareRequest(request).call(); } + @Test + public void build_whenTlsSocketStrategyConfigured_usesProvidedStrategy() throws Exception { + // Setup TLS context + KeyManager[] keyManagers = keyManagersProvider.keyManagers(); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, null, null); + + // Create and spy on TlsSocketStrategy + TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE); + TlsSocketStrategy tlsStrategySpy = Mockito.spy(tlsStrategy); + + // Build client with TLS strategy + client = Apache5HttpClient.builder() + .tlsSocketStrategy(tlsStrategySpy) + .build(); + + // Make request and verify + HttpExecuteResponse response = makeRequestWithHttpClient(client); + assertThat(response.httpResponse().isSuccessful()).isTrue(); + + // Verify upgrade method was called + Mockito.verify(tlsStrategySpy).upgrade( + Mockito.any(Socket.class), + Mockito.anyString(), + Mockito.anyInt(), + Mockito.any(), + Mockito.any(HttpContext.class) + ); + } + } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsHalfCloseTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsHalfCloseTest.java index 334eb2ac6b5b..d6a15b337ad1 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsHalfCloseTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5ClientTlsHalfCloseTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -59,8 +60,11 @@ public void errorWhenServerHalfClosesSocketWhileStreamIsOpened() { IOException exception = assertThrows(IOException.class, () -> { executeHttpRequest(httpClient); }); - assertThat(exception.getMessage()) - .containsIgnoringCase("broken pipe"); + assertTrue( + exception.getMessage().equals("Connection or outbound has closed") || + exception.getMessage().equals("Connection is closed"), + "Expected connection closed message, but got: " + exception.getMessage()); + } @@ -78,7 +82,11 @@ public void errorWhenServerFullClosesSocketWhileStreamIsOpened() throws IOExcept }); if(halfCloseSupported()){ - assertEquals("Connection or outbound has closed", exception.getMessage()); + assertTrue( + exception.getMessage().equals("Connection or outbound has closed") || + exception.getMessage().equals("Connection is closed"), + "Expected connection closed message, but got: " + exception.getMessage()); + }else { assertEquals("Socket is closed", exception.getMessage()); diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientUriSanitizationTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientUriSanitizationTest.java index 60b101d07763..8170c89c7406 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientUriSanitizationTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientUriSanitizationTest.java @@ -15,7 +15,6 @@ package software.amazon.awssdk.http.apache5; - import org.junit.jupiter.api.DisplayName; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpClientUriSanitizationTestSuite; diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWireMockTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWireMockTest.java index 613280251d49..be44888c7e4b 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWireMockTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWireMockTest.java @@ -17,6 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -28,10 +29,15 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; import java.io.IOException; +import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.SocketTimeoutException; import java.net.URI; import java.net.UnknownHostException; +import java.time.Duration; +import java.util.stream.Stream; +import org.apache.hc.client5.http.ConnectTimeoutException; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SystemDefaultDnsResolver; @@ -279,4 +285,71 @@ public void closeReleasesResources() throws Exception { IllegalStateException.class ).hasMessageContaining("Connection pool shut down"); } + + @Test + public void connectionTimeout_exceedsLimit_throwsException() { + // Test connection timeout with a very short timeout and non-responsive address + try (SdkHttpClient client = Apache5HttpClient.builder() + .connectionTimeout(Duration.ofMillis(100)) + .build()) { + + // Use a non-routable address to simulate connection timeout + // 192.0.2.1 is a reserved test address + SdkHttpFullRequest request = SdkHttpFullRequest.builder() + .uri(URI.create("http://192.0.2.1:8080/test")) + .method(SdkHttpMethod.GET) + .putHeader("Host", "192.0.2.1:8080") + .build(); + + assertThatThrownBy(() -> + client.prepareRequest(HttpExecuteRequest.builder().request(request).build()).call()) + .isInstanceOfAny( + ConnectTimeoutException.class, + ConnectException.class, + IOException.class) + .satisfies(exception -> { + // message vary based on JVM + String message = exception.getMessage().toLowerCase(); + boolean hasTimeoutMessage = Stream.of("timeout", "timed out", "read timeout") + .anyMatch(message::contains); + assertThat(hasTimeoutMessage).isTrue(); + }); + } + } + + @Test + public void socketTimeout_exceedsLimit_throwsException() { + // Configure WireMock to delay response longer than socket timeout + mockServer.stubFor(any(urlPathEqualTo("/delayed")) + .willReturn(aResponse() + .withStatus(200) + .withBody("delayed response") + .withFixedDelay(2000))); + + try (SdkHttpClient client = Apache5HttpClient.builder() + .socketTimeout(Duration.ofMillis(500)) + .build()) { + + SdkHttpFullRequest request = SdkHttpFullRequest.builder() + .uri(URI.create("http://localhost:" + mockServer.port() + "/delayed")) + .method(SdkHttpMethod.GET) + .putHeader("Host", "localhost:" + mockServer.port()) + .putHeader("User-Agent", "test-client") + .build(); + + assertThatThrownBy(() -> + client.prepareRequest(HttpExecuteRequest.builder().request(request).build()).call()) + .isInstanceOfAny( + SocketTimeoutException.class, + IOException.class) + .satisfies(exception -> { + String message = exception.getMessage().toLowerCase(); + boolean hasTimeoutMessage = Stream.of("timeout", "timed out", "read timeout") + .anyMatch(message::contains); + assertThat(hasTimeoutMessage).isTrue(); + + }); + mockServer.verify(1, getRequestedFor(urlPathEqualTo("/delayed"))); + } + } } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWithSocketFactoryWireMockTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWithSocketFactoryWireMockTest.java new file mode 100644 index 000000000000..fe9630773ec1 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientWithSocketFactoryWireMockTest.java @@ -0,0 +1,148 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.ssl.TrustStrategy; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.apache5.internal.conn.SdkTlsSocketFactory; +import software.amazon.awssdk.utils.IoUtils; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import java.io.InputStream; +import java.net.Socket; +import java.net.URI; +import java.security.cert.X509Certificate; + +class Apache5HttpClientWithSocketFactoryWireMockTest { + + private WireMockServer httpMockServer; + private WireMockServer httpsMockServer; + + @BeforeEach + void setUp() { + httpMockServer = new WireMockServer(WireMockConfiguration.options().port(0).dynamicPort()); + httpMockServer.start(); + + httpsMockServer = new WireMockServer(WireMockConfiguration.options().port(0).dynamicHttpsPort()); + httpsMockServer.start(); + } + + @AfterEach + void tearDown() { + httpMockServer.stop(); + httpsMockServer.stop(); + } + + @Test + void prepareRequest_withTlsStrategyOverHttp_doesNotCallUpgrade() throws Exception { + TlsSocketStrategy tlsStrategySpy = spy(new TlsSocketStrategy() { + @Override + public SSLSocket upgrade(Socket socket, String target, int port, + Object attachment, HttpContext context) { + fail("TLS upgrade should not be called for HTTP"); + return null; + } + }); + SdkHttpClient client = Apache5HttpClient.builder() + .tlsSocketStrategy(tlsStrategySpy) + .build(); + + stubForMockRequest(httpMockServer, 200); + SdkHttpFullRequest request = mockSdkRequest("http://localhost:" + httpMockServer.port(), SdkHttpMethod.GET); + HttpExecuteResponse response = client.prepareRequest(HttpExecuteRequest.builder() + .request(request) + .contentStreamProvider(request.contentStreamProvider().orElse(null)) + .build()) + .call(); + validateResponse(response, 200); + verify(tlsStrategySpy, never()).upgrade(any(), any(), anyInt(), any(), any()); + } + + @Test + void prepareRequest_withTlsStrategyOverHttps_callsUpgrade() throws Exception { + SSLContext trustAllContext = createTrustAllSslContext(); + TlsSocketStrategy tlsStrategySpy = spy(new SdkTlsSocketFactory( + trustAllContext, + NoopHostnameVerifier.INSTANCE + )); + SdkHttpClient client = Apache5HttpClient.builder() + .tlsSocketStrategy(tlsStrategySpy) + .build(); + + stubForMockRequest(httpsMockServer, 200); + SdkHttpFullRequest request = mockSdkRequest("https://localhost:" + httpsMockServer.httpsPort(), SdkHttpMethod.GET); + HttpExecuteResponse response = client.prepareRequest(HttpExecuteRequest.builder() + .request(request) + .contentStreamProvider(request.contentStreamProvider().orElse(null)) + .build()) + .call(); + + validateResponse(response, 200); + verify(tlsStrategySpy, atLeastOnce()).upgrade(any(), eq("localhost"), eq(httpsMockServer.httpsPort()), any(), any()); + } + + private void stubForMockRequest(WireMockServer server, int returnCode) { + server.stubFor(WireMock.get(WireMock.urlPathEqualTo("/test")) + .willReturn(WireMock.aResponse() + .withStatus(returnCode) + .withBody("test response body"))); + } + + private SdkHttpFullRequest mockSdkRequest(String url, SdkHttpMethod method) { + return SdkHttpFullRequest.builder() + .uri(URI.create(url + "/test")) + .method(method) + .build(); + } + + private void validateResponse(HttpExecuteResponse response, int expectedStatusCode) throws Exception { + assertThat(response).isNotNull(); + assertThat(response.httpResponse()).isNotNull(); + assertThat(response.httpResponse().statusCode()).isEqualTo(expectedStatusCode); + + if (response.responseBody().isPresent()) { + try (InputStream is = response.responseBody().get()) { + String body = IoUtils.toUtf8String(is); + assertThat(body).isEqualTo("test response body"); + } + } + } + + private SSLContext createTrustAllSslContext() throws Exception { + TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true; + return SSLContexts.custom() + .loadTrustMaterial(null, acceptingTrustStrategy) + .build(); + } +} diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/MetricReportingTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/MetricReportingTest.java index 3c2529d697f7..a598f8b65dc7 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/MetricReportingTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/MetricReportingTest.java @@ -30,11 +30,7 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.HttpVersion; -import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; -import org.apache.hc.core5.http.message.BasicHttpResponse; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.pool.PoolStats; import org.junit.Before; @@ -43,7 +39,6 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.http.HttpExecuteRequest; -import software.amazon.awssdk.http.HttpExecuteResponse; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.apache5.internal.Apache5HttpRequestConfig; @@ -63,12 +58,12 @@ public class MetricReportingTest { @Before public void methodSetup() throws IOException { - - when(mockHttpClient.execute(any(HttpUriRequest.class), any(HttpContext.class))) - .thenReturn(new BasicClassicHttpResponse(200, "OK")); - + // Create a response that can be reused + BasicClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK"); + // Mock executeOpen which is now being used + when(mockHttpClient.executeOpen(any(), any(HttpUriRequest.class), any(HttpContext.class))) + .thenReturn(response); when(mockHttpClient.getHttpClientConnectionManager()).thenReturn(cm); - PoolStats stats = new PoolStats(1, 2, 3, 4); when(cm.getTotalStats()).thenReturn(stats); } @@ -109,7 +104,6 @@ public void prepareRequest_connectionManagerNotPooling_callableCalled_metricsRep private Apache5HttpClient newClient() { Apache5HttpRequestConfig config = Apache5HttpRequestConfig.builder() .connectionAcquireTimeout(Duration.ofDays(1)) - .connectionTimeout(Duration.ofDays(1)) .socketTimeout(Duration.ofDays(1)) .proxyConfiguration(ProxyConfiguration.builder().build()) .build(); @@ -118,11 +112,11 @@ private Apache5HttpClient newClient() { } private HttpExecuteRequest newRequest(MetricCollector collector) { - final SdkHttpFullRequest sdkRequest = SdkHttpFullRequest.builder() - .method(SdkHttpMethod.HEAD) - .host("amazonaws.com") - .protocol("https") - .build(); + SdkHttpFullRequest sdkRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.HEAD) + .host("amazonaws.com") + .protocol("https") + .build(); return HttpExecuteRequest.builder() .request(sdkRequest) .metricCollector(collector) diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/IdleConnectionReaperTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/IdleConnectionReaperTest.java index 9f0ef8d8667a..25b2a446abbc 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/IdleConnectionReaperTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/IdleConnectionReaperTest.java @@ -16,7 +16,6 @@ package software.amazon.awssdk.http.apache5.internal.conn; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.core5.io.CloseMode; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactoryTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactoryTest.java index ed8e89686eb9..1ed5b7ea0d27 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactoryTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/conn/SdkTlsSocketFactoryTest.java @@ -42,7 +42,7 @@ void nullProtocols() { when(socket.getSupportedProtocols()).thenReturn(null); when(socket.getEnabledProtocols()).thenReturn(null); - factory.prepareSocket(socket); + factory.initializeSocket(socket); verify(socket, never()).setEnabledProtocols(any()); } @@ -56,7 +56,7 @@ void amazonCorretto_8_0_292_defaultEnabledProtocols() { "TLSv1.2", "TLSv1.1", "TLSv1" }); - factory.prepareSocket(socket); + factory.initializeSocket(socket); verify(socket, never()).setEnabledProtocols(any()); } @@ -70,7 +70,7 @@ void amazonCorretto_11_0_08_defaultEnabledProtocols() { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" }); - factory.prepareSocket(socket); + factory.initializeSocket(socket); verify(socket, never()).setEnabledProtocols(any()); } @@ -84,7 +84,7 @@ void amazonCorretto_17_0_1_defaultEnabledProtocols() { "TLSv1.3", "TLSv1.2" }); - factory.prepareSocket(socket); + factory.initializeSocket(socket); verify(socket, never()).setEnabledProtocols(any()); } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/impl/ApacheHttpRequestFactoryTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/impl/ApacheHttpRequestFactoryTest.java index dbc2cc79db7f..5dcc215db480 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/impl/ApacheHttpRequestFactoryTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/internal/impl/ApacheHttpRequestFactoryTest.java @@ -50,7 +50,6 @@ public void setup() { instance = new Apache5HttpRequestFactory(); requestConfig = Apache5HttpRequestConfig.builder() .connectionAcquireTimeout(Duration.ZERO) - .connectionTimeout(Duration.ZERO) .socketTimeout(Duration.ZERO) .build(); }