From 86a120a818ebe40426412c58160587aaf9f27126 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 14:32:10 -0800 Subject: [PATCH 1/8] Define infinite TTL as <= 0 This commit updates the definition of the CONNECTION_TIME_TO_LIVE (TTL) so that non-positive values indicate an infinite TTL. The reason for this is that in contrast to Apache 4.x, Apache 5.x uses 0 to indicate immediate expiry. This updated definition allows us to use a new default value of -1 across all supported HTTP clients to indicate an infinite TTL. Note that in practice, this is already the case, as all clients other than the Apache 5.x client treat non-positive values as infinite: - For Apache 4.x, value of <= 0 is treated as infinite: https://github.com/apache/httpcomponents-core/blob/a5c117028b7c620974304636d52f06f172f1d08b/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java#L87-L93 - For Netty, `OldConnectionReaperHandler` is only used when the TTL value > 0, otherwise the handler is not added to the channel's pipeline: https://github.com/aws/aws-sdk-java-v2/blob/90f17ab300e27bee5367e4eff30b49bfa1f06af0/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java#L213-L216 - CRT and URLConnection client does not support this configuration. --- .../amazon/awssdk/http/SdkHttpConfigurationOption.java | 6 +++--- .../amazon/awssdk/http/apache5/Apache5HttpClient.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java index e9efa8b1814b..c915b83115a4 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java @@ -60,8 +60,8 @@ public final class SdkHttpConfigurationOption extends AttributeMap.Key { new SdkHttpConfigurationOption<>("ConnectionMaxIdleTimeout", Duration.class); /** - * Timeout after which a connection should be closed, regardless of whether it is idle. Zero indicates an infinite amount - * of time. + * Timeout after which a connection should be closed, regardless of whether it is idle. A value less than or equal to 0 + * indicates an infinite amount of time. */ public static final SdkHttpConfigurationOption CONNECTION_TIME_TO_LIVE = new SdkHttpConfigurationOption<>("ConnectionTimeToLive", Duration.class); @@ -142,7 +142,7 @@ public final class SdkHttpConfigurationOption extends AttributeMap.Key { private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(2); private static final Duration DEFAULT_CONNECTION_ACQUIRE_TIMEOUT = Duration.ofSeconds(10); private static final Duration DEFAULT_CONNECTION_MAX_IDLE_TIMEOUT = Duration.ofSeconds(60); - private static final Duration DEFAULT_CONNECTION_TIME_TO_LIVE = Duration.ZERO; + private static final Duration DEFAULT_CONNECTION_TIME_TO_LIVE = Duration.ofMillis(-1); /** * 5 seconds = 3 seconds (RTO for 2 packets loss) + 2 seconds (startup latency and RTT buffer) */ 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 d5e1a25ad846..885328c78b65 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 @@ -769,8 +769,8 @@ private static ConnectionConfig getConnectionConfig(AttributeMap standardOptions .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) + if (!connectionTtl.isNegative()) { + // Note: TTL=0 is infinite in 4.x vs immediate expiration in 5.x connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); } return connectionConfigBuilder.build(); From c07bac7c1cc107b7f8a31a1f05e571ba1ce8dd1d Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 15:19:48 -0800 Subject: [PATCH 2/8] Don't special case 0 TTL in Apache 5 Shadow the global default CONNECTION_TIME_TO_LIVE value of 0 to -1 within the Apache 5 client instead of special casing the 0 value when building the connection config. --- .../awssdk/http/apache5/Apache5HttpClient.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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 885328c78b65..8f53f6336cd3 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 @@ -132,6 +132,13 @@ public final class Apache5HttpClient implements SdkHttpClient { private static final String CLIENT_NAME = "Apache5Preview"; + // The global SdkHttpConfigurationOption.DEFAULT_CONNECTION_TIME_TO_LIVE is defined as Duration.ZERO to indicate infinite + // TTL. However, Apache 5.x defines 0 as immediate expiry. Shadow this default to -1 instead so that we still default to + // infinite TTL when not explicitly configured. + private static final AttributeMap APACHE5_HTTP_DEFAULTS = AttributeMap.builder() + .put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, Duration.ofMillis(-1)) + .build(); + private static final Logger log = Logger.loggerFor(Apache5HttpClient.class); private static final HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = new DefaultHostnameVerifier(); private final Apache5HttpRequestFactory apacheHttpRequestFactory = new Apache5HttpRequestFactory(); @@ -736,8 +743,10 @@ public void setAuthSchemeProviderRegistry(Registry authScheme @Override public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { - AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( - SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); + AttributeMap resolvedOptions = standardOptions.build() + .merge(serviceDefaults) + .merge(APACHE5_HTTP_DEFAULTS) + .merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); return new Apache5HttpClient(this, resolvedOptions); } } @@ -769,7 +778,7 @@ private static ConnectionConfig getConnectionConfig(AttributeMap standardOptions .setSocketTimeout(Timeout.ofMilliseconds( standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT).toMillis())); Duration connectionTtl = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE); - if (!connectionTtl.isNegative()) { + if (!connectionTtl.isNegative() && !connectionTtl.isZero()) { // Note: TTL=0 is infinite in 4.x vs immediate expiration in 5.x connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); } From df5b52b5e6ffb697a79e2972e0c4920ebb01a9d7 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 15:21:30 -0800 Subject: [PATCH 3/8] Revert "Define infinite TTL as <= 0" This reverts commit 86a120a818ebe40426412c58160587aaf9f27126. --- .../amazon/awssdk/http/SdkHttpConfigurationOption.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java index c915b83115a4..e9efa8b1814b 100644 --- a/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java +++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/SdkHttpConfigurationOption.java @@ -60,8 +60,8 @@ public final class SdkHttpConfigurationOption extends AttributeMap.Key { new SdkHttpConfigurationOption<>("ConnectionMaxIdleTimeout", Duration.class); /** - * Timeout after which a connection should be closed, regardless of whether it is idle. A value less than or equal to 0 - * indicates an infinite amount of time. + * Timeout after which a connection should be closed, regardless of whether it is idle. Zero indicates an infinite amount + * of time. */ public static final SdkHttpConfigurationOption CONNECTION_TIME_TO_LIVE = new SdkHttpConfigurationOption<>("ConnectionTimeToLive", Duration.class); @@ -142,7 +142,7 @@ public final class SdkHttpConfigurationOption extends AttributeMap.Key { private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(2); private static final Duration DEFAULT_CONNECTION_ACQUIRE_TIMEOUT = Duration.ofSeconds(10); private static final Duration DEFAULT_CONNECTION_MAX_IDLE_TIMEOUT = Duration.ofSeconds(60); - private static final Duration DEFAULT_CONNECTION_TIME_TO_LIVE = Duration.ofMillis(-1); + private static final Duration DEFAULT_CONNECTION_TIME_TO_LIVE = Duration.ZERO; /** * 5 seconds = 3 seconds (RTO for 2 packets loss) + 2 seconds (startup latency and RTT buffer) */ From 6aec4454383866caf94c38238640989ba80061c4 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 16:14:55 -0800 Subject: [PATCH 4/8] Add tests --- .../http/apache5/ConnectionTtlTest.java | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java new file mode 100644 index 000000000000..c46741e5bbe7 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java @@ -0,0 +1,204 @@ +/* + * 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.assertThat; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import java.io.IOException; +import java.net.Socket; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +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.SdkHttpRequest; +import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider; +import software.amazon.awssdk.http.apache5.internal.conn.SdkTlsSocketFactory; +import software.amazon.awssdk.utils.IoUtils; + +public class ConnectionTtlTest { + private static WireMockServer wireMockServer; + private SdkHttpClient apache5; + + @BeforeAll + public static void setup() { + wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort().dynamicHttpsPort()); + wireMockServer.start(); + + wireMockServer.stubFor(WireMock.get(WireMock.anyUrl()) + .willReturn(WireMock.aResponse() + .withStatus(200) + .withBody("Hello there!"))); + } + + @AfterEach + public void methodTeardown() { + if (apache5 != null) { + apache5.close(); + apache5 = null; + } + } + + @AfterAll + public static void teardown() { + wireMockServer.stop(); + } + + @Test + public void execute_ttlDefault_connectionNotClosed() throws Exception { + TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); + + apache5 = Apache5HttpClient.builder() + .connectionMaxIdleTime(Duration.ofDays(1)) + .tlsSocketStrategy(socketStrategy) + .build(); + + doGetCall(apache5); + doGetCall(apache5); + + List sockets = socketStrategy.getCreatedSockets(); + assertThat(sockets).hasSize(1); + } + + @Test + public void execute_ttlNegative_connectionNotClosed() throws Exception { + TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); + + apache5 = Apache5HttpClient.builder() + .connectionTimeToLive(Duration.ofMillis(-1)) + .connectionMaxIdleTime(Duration.ofDays(1)) + .tlsSocketStrategy(socketStrategy) + .build(); + + doGetCall(apache5); + doGetCall(apache5); + + List sockets = socketStrategy.getCreatedSockets(); + assertThat(sockets).hasSize(1); + } + + @Test + public void execute_ttlIsZero_connectionNotClosed() throws Exception { + TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); + + apache5 = Apache5HttpClient.builder() + .connectionTimeToLive(Duration.ZERO) + .connectionMaxIdleTime(Duration.ofDays(1)) + .tlsSocketStrategy(socketStrategy) + .build(); + + doGetCall(apache5); + doGetCall(apache5); + + List sockets = socketStrategy.getCreatedSockets(); + assertThat(sockets).hasSize(1); + } + + @Test + public void execute_ttlIsShort_idleExceedsTtl_connectionClosed() throws Exception { + TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); + + long ttlMs = 5; + + apache5 = Apache5HttpClient.builder() + .connectionTimeToLive(Duration.ofMillis(ttlMs)) + .connectionMaxIdleTime(Duration.ofDays(1)) + .tlsSocketStrategy(socketStrategy) + .build(); + + doGetCall(apache5); + Thread.sleep(ttlMs * 10); + doGetCall(apache5); + + List sockets = socketStrategy.getCreatedSockets(); + // second request should have created a second socket as the first goes over TTL + assertThat(sockets).hasSize(2); + } + + private void doGetCall(SdkHttpClient apache) throws IOException { + SdkHttpRequest sdkRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .uri(URI.create("https://localhost:" + wireMockServer.httpsPort())) + .build(); + + HttpExecuteRequest executeRequest = HttpExecuteRequest.builder().request(sdkRequest).build(); + + HttpExecuteResponse response = apache.prepareRequest(executeRequest).call(); + IoUtils.drainInputStream(response.responseBody().get()); + } + + private static class TestTlsSocketStrategy extends SdkTlsSocketFactory { + private List sslSockets = new ArrayList<>(); + + TestTlsSocketStrategy(SSLContext ctx) { + super(ctx, NoopHostnameVerifier.INSTANCE); + } + + @Override + public SSLSocket upgrade(Socket socket, String target, int port, Object attachment, HttpContext context) throws IOException { + SSLSocket upgradedSocket = super.upgrade(socket, target, port, attachment, context); + sslSockets.add(upgradedSocket); + return upgradedSocket; + } + + List getCreatedSockets() { + return sslSockets; + } + + static TestTlsSocketStrategy create() throws Exception { + KeyManager[] keyManagers = SystemPropertyTlsKeyManagersProvider.create().keyManagers(); + + TrustManager[] trustManagers = { + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + SSLContext ssl = SSLContext.getInstance("SSL"); + ssl.init(keyManagers, trustManagers, null); + return new TestTlsSocketStrategy(ssl); + } + } +} From 0f5a6100d814b2fec06d0415fe8d16e270cd969f Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 16:21:33 -0800 Subject: [PATCH 5/8] Add changelog --- .../bugfix-Apache5HTTPClientPreview-ee81371.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json diff --git a/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json b/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json new file mode 100644 index 000000000000..76a170ecf3fa --- /dev/null +++ b/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Apache5 HTTP Client (Preview)", + "contributor": "", + "description": "Update `Apache5HttpClient` so that it uses connection TTL as 'infinite' if `CONNECTION_TIME_TO_LIVE` is not explicitly configured by the user." +} From bdeaea36f245f22eb60a59ebfc90bc8b78d2a1f4 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 2 Dec 2025 16:27:52 -0800 Subject: [PATCH 6/8] Remove check --- .../amazon/awssdk/http/apache5/Apache5HttpClient.java | 5 +---- .../amazon/awssdk/http/apache5/ConnectionTtlTest.java | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) 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 8f53f6336cd3..6849c9922e9f 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 @@ -778,10 +778,7 @@ private static ConnectionConfig getConnectionConfig(AttributeMap standardOptions .setSocketTimeout(Timeout.ofMilliseconds( standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT).toMillis())); Duration connectionTtl = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE); - if (!connectionTtl.isNegative() && !connectionTtl.isZero()) { - // Note: TTL=0 is infinite in 4.x vs immediate expiration in 5.x - connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); - } + connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); return connectionConfigBuilder.build(); } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java index c46741e5bbe7..2c0b49b78225 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java @@ -110,7 +110,7 @@ public void execute_ttlNegative_connectionNotClosed() throws Exception { } @Test - public void execute_ttlIsZero_connectionNotClosed() throws Exception { + public void execute_ttlIsZero_connectionClosed() throws Exception { TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); apache5 = Apache5HttpClient.builder() @@ -123,7 +123,7 @@ public void execute_ttlIsZero_connectionNotClosed() throws Exception { doGetCall(apache5); List sockets = socketStrategy.getCreatedSockets(); - assertThat(sockets).hasSize(1); + assertThat(sockets).hasSize(2); } @Test From efa4396b797d8009a9a4d7d3f10366fb66ad0ccc Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Wed, 3 Dec 2025 11:36:08 -0800 Subject: [PATCH 7/8] Review comments --- .../http/apache5/Apache5HttpClient.java | 19 +++++++------------ .../http/apache5/ConnectionTtlTest.java | 4 ++-- 2 files changed, 9 insertions(+), 14 deletions(-) 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 6849c9922e9f..1b5d510a4a8b 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 @@ -132,13 +132,6 @@ public final class Apache5HttpClient implements SdkHttpClient { private static final String CLIENT_NAME = "Apache5Preview"; - // The global SdkHttpConfigurationOption.DEFAULT_CONNECTION_TIME_TO_LIVE is defined as Duration.ZERO to indicate infinite - // TTL. However, Apache 5.x defines 0 as immediate expiry. Shadow this default to -1 instead so that we still default to - // infinite TTL when not explicitly configured. - private static final AttributeMap APACHE5_HTTP_DEFAULTS = AttributeMap.builder() - .put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, Duration.ofMillis(-1)) - .build(); - private static final Logger log = Logger.loggerFor(Apache5HttpClient.class); private static final HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = new DefaultHostnameVerifier(); private final Apache5HttpRequestFactory apacheHttpRequestFactory = new Apache5HttpRequestFactory(); @@ -743,10 +736,8 @@ public void setAuthSchemeProviderRegistry(Registry authScheme @Override public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { - AttributeMap resolvedOptions = standardOptions.build() - .merge(serviceDefaults) - .merge(APACHE5_HTTP_DEFAULTS) - .merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); + AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( + SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); return new Apache5HttpClient(this, resolvedOptions); } } @@ -778,7 +769,11 @@ private static ConnectionConfig getConnectionConfig(AttributeMap standardOptions .setSocketTimeout(Timeout.ofMilliseconds( standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT).toMillis())); Duration connectionTtl = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE); - connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); + // Only accept positive values. + // Note: TTL=0 is infinite in 4.x vs immediate expiration in 5.x + if (!connectionTtl.isNegative() && !connectionTtl.isZero()) { + connectionConfigBuilder.setTimeToLive(TimeValue.ofMilliseconds(connectionTtl.toMillis())); + } return connectionConfigBuilder.build(); } diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java index 2c0b49b78225..c46741e5bbe7 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/ConnectionTtlTest.java @@ -110,7 +110,7 @@ public void execute_ttlNegative_connectionNotClosed() throws Exception { } @Test - public void execute_ttlIsZero_connectionClosed() throws Exception { + public void execute_ttlIsZero_connectionNotClosed() throws Exception { TestTlsSocketStrategy socketStrategy = TestTlsSocketStrategy.create(); apache5 = Apache5HttpClient.builder() @@ -123,7 +123,7 @@ public void execute_ttlIsZero_connectionClosed() throws Exception { doGetCall(apache5); List sockets = socketStrategy.getCreatedSockets(); - assertThat(sockets).hasSize(2); + assertThat(sockets).hasSize(1); } @Test From 45d782da91c153c4ef25192c9a2dd7a87799a939 Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Wed, 3 Dec 2025 11:42:29 -0800 Subject: [PATCH 8/8] Update javadoc,changelog --- .../bugfix-Apache5HTTPClientPreview-7f5d968.json | 6 ++++++ .../bugfix-Apache5HTTPClientPreview-ee81371.json | 6 ------ .../amazon/awssdk/http/apache5/Apache5HttpClient.java | 6 ++---- 3 files changed, 8 insertions(+), 10 deletions(-) create mode 100644 .changes/next-release/bugfix-Apache5HTTPClientPreview-7f5d968.json delete mode 100644 .changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json diff --git a/.changes/next-release/bugfix-Apache5HTTPClientPreview-7f5d968.json b/.changes/next-release/bugfix-Apache5HTTPClientPreview-7f5d968.json new file mode 100644 index 000000000000..f20151e99b2d --- /dev/null +++ b/.changes/next-release/bugfix-Apache5HTTPClientPreview-7f5d968.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Apache 5 HTTP Client (Preview)", + "contributor": "", + "description": "Ignore negative values set `connectionTimeToLive`. There is no behavior change on the client as negative values have no meaning for Apache 5." +} diff --git a/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json b/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json deleted file mode 100644 index 76a170ecf3fa..000000000000 --- a/.changes/next-release/bugfix-Apache5HTTPClientPreview-ee81371.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "bugfix", - "category": "Apache5 HTTP Client (Preview)", - "contributor": "", - "description": "Update `Apache5HttpClient` so that it uses connection TTL as 'infinite' if `CONNECTION_TIME_TO_LIVE` is not explicitly configured by the user." -} 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 1b5d510a4a8b..acfe372502ca 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 @@ -453,10 +453,8 @@ public interface Builder extends SdkHttpClient.BuilderNote: 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.

+ * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency. Only + * positive values have an effect. */ Builder connectionTimeToLive(Duration connectionTimeToLive);