Date: Mon, 28 Jul 2025 18:42:13 +0200
Subject: [PATCH 11/29] fix JavaDoc for PoolingHttpClientConnectionManager
---
.../http/impl/io/PoolingHttpClientConnectionManager.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
index 8d509209a5..bf674f96c0 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
@@ -84,15 +84,15 @@
import org.slf4j.LoggerFactory;
/**
- * {@code ClientConnectionPoolManager} maintains a pool of
+ * {@code PoolingHttpClientConnectionManager} maintains a pool of
* {@link ManagedHttpClientConnection}s and is able to service connection requests
* from multiple execution threads. Connections are pooled on a per route
* basis. A request for a route which already the manager has persistent
* connections for available in the pool will be serviced by leasing
* a connection from the pool rather than creating a new connection.
*
- * {@code ClientConnectionPoolManager} maintains a maximum limit of connection
- * on a per route basis and in total. Connection limits, however, can be adjusted
+ * {@code PoolingHttpClientConnectionManager} maintains a maximum limit of connections
+ * per route and in total. Connection limits, however, can be adjusted
* using {@link ConnPoolControl} methods.
*
* Total time to live (TTL) set at construction time defines maximum life span
From da4d9a43212f6ff273e0e05c102d7da8b366becc Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sun, 3 Aug 2025 13:49:17 +0200
Subject: [PATCH 12/29] HTTPCLIENT-2384: Socket options related to TcpKeepAlive
are ignored
---
.../impl/io/DefaultHttpClientConnectionOperator.java | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
index f81bd797db..134d97b4cd 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
@@ -57,6 +57,7 @@
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.io.Closer;
+import org.apache.hc.core5.io.SocketSupport;
import org.apache.hc.core5.net.NamedEndpoint;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
@@ -197,7 +198,15 @@ public void connect(
if (socketConfig.getSndBufSize() > 0) {
socket.setSendBufferSize(socketConfig.getSndBufSize());
}
-
+ if (socketConfig.getTcpKeepIdle() > 0) {
+ SocketSupport.setOption(socket, SocketSupport.TCP_KEEPIDLE, socketConfig.getTcpKeepIdle());
+ }
+ if (socketConfig.getTcpKeepInterval() > 0) {
+ SocketSupport.setOption(socket, SocketSupport.TCP_KEEPINTERVAL, socketConfig.getTcpKeepInterval());
+ }
+ if (socketConfig.getTcpKeepCount() > 0) {
+ SocketSupport.setOption(socket, SocketSupport.TCP_KEEPCOUNT, socketConfig.getTcpKeepCount());
+ }
final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
if (linger >= 0) {
socket.setSoLinger(true, linger);
From 8094bac428ad61622530d216ee763724985f612f Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Tue, 5 Aug 2025 11:37:41 +0200
Subject: [PATCH 13/29] HTTPCLIENT-2386: Classic transport to use connect
timeout as a default if TLS timeout has not been explicitly set
---
.../io/DefaultHttpClientConnectionOperator.java | 15 +++++++++++----
.../nio/DefaultAsyncClientConnectionOperator.java | 4 ++--
.../http/ssl/AbstractClientTlsStrategy.java | 8 --------
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
index 134d97b4cd..cebfcfd4d6 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
@@ -41,6 +41,7 @@
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.UnsupportedSchemeException;
+import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.ConnPoolSupport;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.io.DetachedSocketFactory;
@@ -160,7 +161,6 @@ public void connect(
Args.notNull(socketConfig, "Socket config");
Args.notNull(context, "Context");
- final Timeout soTimeout = socketConfig.getSoTimeout();
final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress();
final Proxy socksProxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null;
@@ -186,8 +186,9 @@ public void connect(
socket.bind(localAddress);
}
conn.bind(socket);
- if (soTimeout != null) {
- socket.setSoTimeout(soTimeout.toMillisecondsIntBound());
+ final Timeout socketTimeout = socketConfig.getSoTimeout();
+ if (socketTimeout != null) {
+ socket.setSoTimeout(socketTimeout.toMillisecondsIntBound());
}
socket.setReuseAddress(socketConfig.isSoReuseAddress());
socket.setTcpNoDelay(socketConfig.isTcpNoDelay());
@@ -217,7 +218,6 @@ public void connect(
if (LOG.isDebugEnabled()) {
LOG.debug("{} {} connected {}->{}", ConnPoolSupport.getId(conn), endpointHost, conn.getLocalAddress(), conn.getRemoteAddress());
}
- conn.setSocketTimeout(soTimeout);
final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null;
if (tlsSocketStrategy != null) {
final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
@@ -225,8 +225,15 @@ public void connect(
if (LOG.isDebugEnabled()) {
LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName);
}
+ final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
+ final int soTimeout = socket.getSoTimeout();
+ final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout() != null ? tlsConfig.getHandshakeTimeout() : connectTimeout;
+ if (handshakeTimeout != null) {
+ socket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
+ }
final SSLSocket sslSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
conn.bind(sslSocket, socket);
+ socket.setSoTimeout(soTimeout);
onAfterTlsHandshake(context, endpointHost);
if (LOG.isDebugEnabled()) {
LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java
index 4f04025236..4a90e45a40 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java
@@ -141,7 +141,7 @@ public void completed(final IOSession session) {
if (tlsStrategy != null) {
try {
final Timeout socketTimeout = connection.getSocketTimeout();
- final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
+ final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout() != null ? tlsConfig.getHandshakeTimeout() : connectTimeout;
final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
onBeforeTlsHandshake(context, endpointHost);
if (LOG.isDebugEnabled()) {
@@ -151,7 +151,7 @@ public void completed(final IOSession session) {
connection,
tlsName,
attachment,
- handshakeTimeout != null ? handshakeTimeout : connectTimeout,
+ handshakeTimeout,
new FutureContribution(future) {
@Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java
index e4f6481f6e..35373c8650 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java
@@ -220,8 +220,6 @@ private void executeHandshake(
final SSLSocket upgradedSocket,
final String target,
final Object attachment) throws IOException {
- final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
-
final SSLParameters sslParameters = upgradedSocket.getSSLParameters();
if (supportedProtocols != null) {
sslParameters.setProtocols(supportedProtocols);
@@ -238,17 +236,11 @@ private void executeHandshake(
}
upgradedSocket.setSSLParameters(sslParameters);
- final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
- if (handshakeTimeout != null) {
- upgradedSocket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
- }
-
initializeSocket(upgradedSocket);
if (LOG.isDebugEnabled()) {
LOG.debug("Enabled protocols: {}", (Object) upgradedSocket.getEnabledProtocols());
LOG.debug("Enabled cipher suites: {}", (Object) upgradedSocket.getEnabledCipherSuites());
- LOG.debug("Starting handshake ({})", handshakeTimeout);
}
upgradedSocket.startHandshake();
verifySession(target, upgradedSocket.getSession());
From ab40ec96cc755bbe6e189213521e56f9cbfc744d Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Fri, 29 Aug 2025 19:37:28 +0200
Subject: [PATCH 14/29] Upgraded HttpCore to version 5.3.5
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 03abba507e..844fe25dbc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
1.8
1.8
- 5.3.4
+ 5.3.5
2.24.3
0.1.2
2.5.2
From da1a8e0fed0d83a14c4e39895ac190fdcfe18c4c Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sun, 14 Sep 2025 14:29:48 +0200
Subject: [PATCH 15/29] Imporved TestAsyncClient wiring; added internal methods
to get the underlying connection from the connection endpoint; coverage for
async connection management operations
---
.../async/TestAsyncConnectionManagement.java | 315 ++++++++++++++++++
.../async/MinimalTestClientBuilder.java | 27 +-
.../async/StandardTestClientBuilder.java | 17 +-
.../extension/async/TestAsyncClient.java | 13 +-
.../async/TestAsyncClientBuilder.java | 9 +-
.../extension/sync/TestClientBuilder.java | 4 +-
.../sync/TestConnectionManagement.java | 4 +-
.../client5/http/impl/ConnectionHolder.java | 41 +++
.../async/AbstractHttpAsyncClientBase.java | 19 +-
.../io/BasicHttpClientConnectionManager.java | 9 +-
.../PoolingHttpClientConnectionManager.java | 10 +-
.../PoolingAsyncClientConnectionManager.java | 10 +-
12 files changed, 447 insertions(+), 31 deletions(-)
create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/ConnectionHolder.java
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
new file mode 100644
index 0000000000..fa6f35ab55
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
@@ -0,0 +1,315 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.testing.async;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
+import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.nio.AsyncConnectionEndpoint;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.testing.extension.async.ClientProtocolLevel;
+import org.apache.hc.client5.testing.extension.async.ServerProtocolLevel;
+import org.apache.hc.client5.testing.extension.async.TestAsyncClient;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
+import org.apache.hc.core5.pool.PoolReusePolicy;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestAsyncConnectionManagement extends AbstractIntegrationTestBase {
+
+ static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+ static final Timeout LEASE_TIMEOUT = Timeout.ofSeconds(5);
+
+ public TestAsyncConnectionManagement() {
+ super(URIScheme.HTTP, ClientProtocolLevel.STANDARD, ServerProtocolLevel.STANDARD);
+ }
+
+ @BeforeEach
+ void setup() {
+ configureServer(bootstrap -> bootstrap.register("*", () -> new AbstractSimpleServerExchangeHandler() {
+
+ @Override
+ protected SimpleHttpResponse handle(
+ final SimpleHttpRequest request,
+ final HttpCoreContext context) {
+ final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+ response.setBody("Whatever", ContentType.TEXT_PLAIN);
+ return response;
+ }
+ }));
+ }
+
+ /**
+ * Tests releasing and re-using a connection after a response is read.
+ */
+ @Test
+ void testReleaseConnection() throws Exception {
+ final HttpHost target = startServer();
+
+ final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+ .build();
+ configureClient(builder -> builder.setConnectionManager(connManager));
+ final TestAsyncClient client = startClient();
+
+ connManager.setMaxTotal(1);
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final HttpClientContext context = HttpClientContext.create();
+
+ final Future endpointFuture1 = connManager.lease("id1", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint1 = endpointFuture1.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+
+ final Future connectFuture1 = connManager.connect(endpoint1, client.getImplementation(), TIMEOUT, null, context, null);
+ final AsyncConnectionEndpoint openEndpoint1 = connectFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ final SimpleHttpRequest request = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .addHeader(HttpHeaders.HOST, target.toHostString())
+ .build();
+
+ final Future responseFuture1 = openEndpoint1.execute("ex-1", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response1 = responseFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+
+ // this should fail quickly, connection has not been released
+ final Future endpointFuture2 = connManager.lease("id2", route, null, TIMEOUT, null);
+ Assertions.assertThrows(TimeoutException.class, () -> endpointFuture2.get(10, TimeUnit.MILLISECONDS));
+ endpointFuture2.cancel(true);
+
+ // close and release the connection
+ // expect the next connection obtained to be closed
+ openEndpoint1.close();
+ connManager.release(openEndpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ final Future endpointFuture3 = connManager.lease("id3", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint2 = endpointFuture3.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+ Assertions.assertFalse(endpoint2.isConnected());
+
+ final Future connectFuture2 = connManager.connect(endpoint2, client.getImplementation(), TIMEOUT, null, context, null);
+ final AsyncConnectionEndpoint openEndpoint2 = connectFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ final Future responseFuture2 = openEndpoint2.execute("ex-2", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response2 = responseFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
+
+ // release connection keeping it open
+ // expect the next connection obtained to be open
+ connManager.release(openEndpoint2, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ final Future endpointFuture4 = connManager.lease("id4", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint openEndpoint3 = endpointFuture4.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+ Assertions.assertTrue(openEndpoint3.isConnected());
+
+ final Future responseFuture3 = openEndpoint3.execute("ex-3", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response3 = responseFuture3.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response3.getCode());
+
+ connManager.release(openEndpoint3, null, TimeValue.NEG_ONE_MILLISECOND);
+ connManager.close();
+ }
+
+ /**
+ * Tests releasing with time limits.
+ */
+ @Test
+ void testReleaseConnectionWithTimeLimits() throws Exception {
+ final HttpHost target = startServer();
+
+ final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+ .build();
+ configureClient(builder -> builder.setConnectionManager(connManager));
+ final TestAsyncClient client = startClient();
+
+ connManager.setMaxTotal(1);
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final HttpClientContext context = HttpClientContext.create();
+
+ final Future endpointFuture1 = connManager.lease("id1", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint1 = endpointFuture1.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+
+ final Future connectFuture1 = connManager.connect(endpoint1, client.getImplementation(), TIMEOUT, null, context, null);
+ final AsyncConnectionEndpoint openEndpoint1 = connectFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ final SimpleHttpRequest request = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .addHeader(HttpHeaders.HOST, target.toHostString())
+ .build();
+
+ final Future responseFuture1 = openEndpoint1.execute("ex-1", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response1 = responseFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+
+ final Future endpointFuture2 = connManager.lease("id2", route, null, TIMEOUT, null);
+ Assertions.assertThrows(TimeoutException.class, () -> endpointFuture2.get(10, TimeUnit.MILLISECONDS));
+ endpointFuture2.cancel(true);
+
+ openEndpoint1.close();
+ connManager.release(openEndpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ final Future endpointFuture3 = connManager.lease("id3", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint2 = endpointFuture3.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+ Assertions.assertFalse(endpoint2.isConnected());
+
+ final Future connectFuture2 = connManager.connect(endpoint2, client.getImplementation(), TIMEOUT, null, context, null);
+ final AsyncConnectionEndpoint openEndpoint2 = connectFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ final Future responseFuture2 = openEndpoint2.execute("ex-2", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response2 = responseFuture2.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
+
+ connManager.release(openEndpoint2, null, TimeValue.ofMilliseconds(100));
+ Thread.sleep(150);
+
+ final Future endpointFuture4 = connManager.lease("id4", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint openEndpoint3 = endpointFuture4.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+ Assertions.assertFalse(openEndpoint3.isConnected());
+ connManager.release(openEndpoint3, null, TimeValue.ofMilliseconds(100));
+
+ connManager.close();
+ }
+
+ @Test
+ void testCloseExpiredIdleConnections() throws Exception {
+ final HttpHost target = startServer();
+
+ final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+ .build();
+ configureClient(builder -> builder.setConnectionManager(connManager));
+ final TestAsyncClient client = startClient();
+
+ connManager.setMaxTotal(1);
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final HttpClientContext context = HttpClientContext.create();
+
+ final Future endpointFuture1 = connManager.lease("id1", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint1 = endpointFuture1.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+
+ connManager.connect(endpoint1, client.getImplementation(), TIMEOUT, null, context, null)
+ .get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ Assertions.assertEquals(1, connManager.getTotalStats().getLeased());
+ Assertions.assertEquals(1, connManager.getStats(route).getLeased());
+
+ connManager.release(endpoint1, null, TimeValue.ofMilliseconds(100));
+
+ // Released, still active.
+ Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
+
+ connManager.closeExpired();
+
+ // Time has not expired yet.
+ Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
+
+ Thread.sleep(150);
+
+ connManager.closeExpired();
+
+ // Time expired now, connections are destroyed.
+ Assertions.assertEquals(0, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(0, connManager.getStats(route).getAvailable());
+
+ connManager.close();
+ }
+
+ @Test
+ void testCloseExpiredTTLConnections() throws Exception {
+ final HttpHost target = startServer();
+
+ final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+ .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.STRICT)
+ .setConnPoolPolicy(PoolReusePolicy.LIFO)
+ .setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setTimeToLive(TimeValue.ofMilliseconds(100))
+ .build())
+ .build();
+ configureClient(builder -> builder.setConnectionManager(connManager));
+
+ final TestAsyncClient client = startClient();
+
+ connManager.setMaxTotal(1);
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final HttpClientContext context = HttpClientContext.create();
+
+ final Future endpointFuture1 = connManager.lease("id1", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint1 = endpointFuture1.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+
+ connManager.connect(endpoint1, client.getImplementation(), TIMEOUT, null, context, null)
+ .get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ Assertions.assertEquals(1, connManager.getTotalStats().getLeased());
+ Assertions.assertEquals(1, connManager.getStats(route).getLeased());
+ // Release, let remain idle for forever
+ connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ // Released, still active.
+ Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
+
+ connManager.closeExpired();
+
+ // Time has not expired yet.
+ Assertions.assertEquals(1, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(1, connManager.getStats(route).getAvailable());
+
+ Thread.sleep(150);
+
+ connManager.closeExpired();
+
+ // TTL expired now, connections are destroyed.
+ Assertions.assertEquals(0, connManager.getTotalStats().getAvailable());
+ Assertions.assertEquals(0, connManager.getStats(route).getAvailable());
+
+ connManager.close();
+ }
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/MinimalTestClientBuilder.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/MinimalTestClientBuilder.java
index 5c8b1c0a9f..e99a78c679 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/MinimalTestClientBuilder.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/MinimalTestClientBuilder.java
@@ -31,8 +31,8 @@
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.testing.SSLTestContexts;
import org.apache.hc.core5.http.config.Http1Config;
@@ -45,6 +45,7 @@ final class MinimalTestClientBuilder implements TestAsyncClientBuilder {
private final PoolingAsyncClientConnectionManagerBuilder connectionManagerBuilder;
+ private AsyncClientConnectionManager connectionManager;
private Timeout timeout;
private TlsStrategy tlsStrategy;
private Http1Config http1Config;
@@ -65,6 +66,12 @@ public TestAsyncClientBuilder setTimeout(final Timeout timeout) {
return this;
}
+ @Override
+ public TestAsyncClientBuilder setConnectionManager(final AsyncClientConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ return this;
+ }
+
@Override
public TestAsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) {
this.tlsStrategy = tlsStrategy;
@@ -97,21 +104,21 @@ public TestAsyncClientBuilder setH2Config(final H2Config h2Config) {
@Override
public TestAsyncClient build() throws Exception {
- final PoolingAsyncClientConnectionManager connectionManager = connectionManagerBuilder
+ final AsyncClientConnectionManager connectionManagerCopy = connectionManager == null ? connectionManagerBuilder
.setTlsStrategy(tlsStrategy != null ? tlsStrategy : new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.build())
- .build();
+ .build() : connectionManager;
final CloseableHttpAsyncClient client = HttpAsyncClients.createMinimal(
- h2Config,
- http1Config,
- IOReactorConfig.custom()
- .setSoTimeout(timeout)
- .build(),
- connectionManager);
- return new TestAsyncClient(client, connectionManager);
+ h2Config,
+ http1Config,
+ IOReactorConfig.custom()
+ .setSoTimeout(timeout)
+ .build(),
+ connectionManagerCopy);
+ return new TestAsyncClient(client, connectionManagerCopy);
}
}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/StandardTestClientBuilder.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/StandardTestClientBuilder.java
index 1df164a8c6..11c291686a 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/StandardTestClientBuilder.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/StandardTestClientBuilder.java
@@ -38,8 +38,8 @@
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.testing.SSLTestContexts;
import org.apache.hc.core5.http.Header;
@@ -57,6 +57,7 @@ final class StandardTestClientBuilder implements TestAsyncClientBuilder {
private final PoolingAsyncClientConnectionManagerBuilder connectionManagerBuilder;
private final HttpAsyncClientBuilder clientBuilder;
+ private AsyncClientConnectionManager connectionManager;
private Timeout timeout;
private TlsStrategy tlsStrategy;
@@ -76,6 +77,12 @@ public TestAsyncClientBuilder setTimeout(final Timeout timeout) {
return this;
}
+ @Override
+ public TestAsyncClientBuilder setConnectionManager(final AsyncClientConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ return this;
+ }
+
@Override
public TestAsyncClientBuilder addResponseInterceptorFirst(final HttpResponseInterceptor interceptor) {
this.clientBuilder.addResponseInterceptorFirst(interceptor);
@@ -168,20 +175,20 @@ public TestAsyncClientBuilder setDefaultCredentialsProvider(final CredentialsPro
@Override
public TestAsyncClient build() throws Exception {
- final PoolingAsyncClientConnectionManager connectionManager = connectionManagerBuilder
+ final AsyncClientConnectionManager connectionManagerCopy = connectionManager == null ? connectionManagerBuilder
.setTlsStrategy(tlsStrategy != null ? tlsStrategy : new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setSocketTimeout(timeout)
.setConnectTimeout(timeout)
.build())
- .build();
+ .build() : connectionManager;
final CloseableHttpAsyncClient client = clientBuilder
.setIOReactorConfig(IOReactorConfig.custom()
.setSoTimeout(timeout)
.build())
- .setConnectionManager(connectionManager)
+ .setConnectionManager(connectionManagerCopy)
.build();
- return new TestAsyncClient(client, connectionManager);
+ return new TestAsyncClient(client, connectionManagerCopy);
}
}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClient.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClient.java
index adefc45602..30563ebf2d 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClient.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClient.java
@@ -31,7 +31,7 @@
import java.util.concurrent.Future;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.function.Supplier;
import org.apache.hc.core5.http.HttpHost;
@@ -43,16 +43,15 @@
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.reactor.IOReactorStatus;
import org.apache.hc.core5.util.Args;
-import org.apache.hc.core5.util.Asserts;
import org.apache.hc.core5.util.TimeValue;
public class TestAsyncClient extends CloseableHttpAsyncClient {
private final CloseableHttpAsyncClient client;
- private final PoolingAsyncClientConnectionManager connectionManager;
+ private final AsyncClientConnectionManager connectionManager;
public TestAsyncClient(final CloseableHttpAsyncClient client,
- final PoolingAsyncClientConnectionManager connectionManager) {
+ final AsyncClientConnectionManager connectionManager) {
this.client = Args.notNull(client, "Client");
this.connectionManager = connectionManager;
}
@@ -113,9 +112,9 @@ public T getImplementation() {
return (T) client;
}
- public PoolingAsyncClientConnectionManager getConnectionManager() {
- Asserts.check(connectionManager != null, "Connection manager is not available");
- return connectionManager;
+ @SuppressWarnings("unchecked")
+ public T getConnectionManager() {
+ return (T) connectionManager;
}
}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClientBuilder.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClientBuilder.java
index 3f12b77cf0..cbb2878eff 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClientBuilder.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/async/TestAsyncClientBuilder.java
@@ -35,6 +35,7 @@
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.config.TlsConfig;
+import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpResponseInterceptor;
@@ -50,8 +51,12 @@ public interface TestAsyncClientBuilder {
TestAsyncClientBuilder setTimeout(Timeout soTimeout);
- default TestAsyncClientBuilder addResponseInterceptorFirst(final HttpResponseInterceptor interceptor) {
- return this;
+ default TestAsyncClientBuilder setConnectionManager(AsyncClientConnectionManager connManager) {
+ throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
+ }
+
+ default TestAsyncClientBuilder addResponseInterceptorFirst(HttpResponseInterceptor interceptor) {
+ throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
}
default TestAsyncClientBuilder addResponseInterceptorLast(HttpResponseInterceptor interceptor) {
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientBuilder.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientBuilder.java
index 459124693f..57526ef2c1 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientBuilder.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/extension/sync/TestClientBuilder.java
@@ -51,8 +51,8 @@ public interface TestClientBuilder {
TestClientBuilder setConnectionManager(HttpClientConnectionManager connManager);
- default TestClientBuilder addResponseInterceptorFirst(final HttpResponseInterceptor interceptor) {
- return this;
+ default TestClientBuilder addResponseInterceptorFirst(HttpResponseInterceptor interceptor) {
+ throw new UnsupportedOperationException("Operation not supported by " + getProtocolLevel());
}
default TestClientBuilder addResponseInterceptorLast(HttpResponseInterceptor interceptor) {
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
index 8c215b2a88..c46f98e87c 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
@@ -129,6 +129,8 @@ void testReleaseConnection() throws Exception {
final LeaseRequest leaseRequest2 = connManager.lease("id2", route, null);
Assertions.assertThrows(TimeoutException.class, () -> leaseRequest2.get(Timeout.ofMilliseconds(10)));
+ // close and release the connection
+ // expect the next connection obtained to be closed
endpoint1.close();
connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
final LeaseRequest leaseRequest3 = connManager.lease("id2", route, null);
@@ -141,7 +143,7 @@ void testReleaseConnection() throws Exception {
Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
}
- // release connection after marking it for re-use
+ // release connection keeping it open
// expect the next connection obtained to be open
connManager.release(endpoint2, null, TimeValue.NEG_ONE_MILLISECOND);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ConnectionHolder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ConnectionHolder.java
new file mode 100644
index 0000000000..33a1d3d04f
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ConnectionHolder.java
@@ -0,0 +1,41 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl;
+
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.http.HttpConnection;
+
+/**
+ * Internal interface to expose the underlying connection
+ * @since 5.5
+ */
+@Internal
+public interface ConnectionHolder {
+
+ HttpConnection get();
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
index 264faa2c70..e6b859c33d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
@@ -26,22 +26,28 @@
*/
package org.apache.hc.client5.http.impl.async;
+import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.function.Supplier;
import org.apache.hc.core5.http.nio.AsyncPushConsumer;
import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.net.NamedEndpoint;
import org.apache.hc.core5.reactor.ConnectionInitiator;
import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
import org.apache.hc.core5.reactor.IOReactorStatus;
+import org.apache.hc.core5.reactor.IOSession;
import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-abstract class AbstractHttpAsyncClientBase extends CloseableHttpAsyncClient {
+abstract class AbstractHttpAsyncClientBase extends CloseableHttpAsyncClient implements ConnectionInitiator {
enum Status { READY, RUNNING, TERMINATED }
@@ -84,6 +90,17 @@ boolean isRunning() {
return status.get() == Status.RUNNING;
}
+ @Override
+ public Future connect(
+ final NamedEndpoint remoteEndpoint,
+ final SocketAddress remoteAddress,
+ final SocketAddress localAddress,
+ final Timeout timeout,
+ final Object attachment,
+ final FutureCallback callback) {
+ return ioReactor.connect(remoteEndpoint, remoteAddress, localAddress, timeout, attachment, callback);
+ }
+
ConnectionInitiator getConnectionInitiator() {
return ioReactor;
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
index ef99e78725..9ca40f9986 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
@@ -44,6 +44,7 @@
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.ConnPoolSupport;
+import org.apache.hc.client5.http.impl.ConnectionHolder;
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
@@ -56,6 +57,7 @@
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.URIScheme;
@@ -590,7 +592,7 @@ public void setValidateAfterInactivity(final TimeValue validateAfterInactivity)
.build();
}
- class InternalConnectionEndpoint extends ConnectionEndpoint implements Identifiable {
+ class InternalConnectionEndpoint extends ConnectionEndpoint implements ConnectionHolder, Identifiable {
private final HttpRoute route;
private final AtomicReference connRef;
@@ -703,6 +705,11 @@ public EndpointInfo getInfo() {
return null;
}
+ @Override
+ public HttpConnection get() {
+ return this.connRef.get();
+ }
+
}
/**
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
index bf674f96c0..e027f56a81 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
@@ -42,6 +42,7 @@
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.ConnPoolSupport;
+import org.apache.hc.client5.http.impl.ConnectionHolder;
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.impl.PrefixedIncrementingId;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
@@ -57,6 +58,7 @@
import org.apache.hc.core5.function.Resolver;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.URIScheme;
@@ -689,7 +691,7 @@ public void setValidateAfterInactivity(final TimeValue validateAfterInactivity)
private static final PrefixedIncrementingId INCREMENTING_ID = new PrefixedIncrementingId("ep-");
- static class InternalConnectionEndpoint extends ConnectionEndpoint implements Identifiable {
+ static class InternalConnectionEndpoint extends ConnectionEndpoint implements ConnectionHolder, Identifiable {
private final AtomicReference> poolEntryRef;
private final String id;
@@ -806,6 +808,12 @@ public EndpointInfo getInfo() {
return null;
}
+ @Override
+ public HttpConnection get() {
+ final PoolEntry poolEntry = poolEntryRef.get();
+ return poolEntry != null ? poolEntry.getConnection() : null;
+ }
+
}
/**
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
index 3fbf34d197..be7b5348e4 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
@@ -42,6 +42,7 @@
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.impl.ConnPoolSupport;
+import org.apache.hc.client5.http.impl.ConnectionHolder;
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
import org.apache.hc.client5.http.impl.PrefixedIncrementingId;
import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
@@ -57,6 +58,7 @@
import org.apache.hc.core5.concurrent.ComplexFuture;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.function.Resolver;
+import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolVersion;
@@ -676,7 +678,7 @@ public void setValidateAfterInactivity(final TimeValue validateAfterInactivity)
private static final PrefixedIncrementingId INCREMENTING_ID = new PrefixedIncrementingId("ep-");
- static class InternalConnectionEndpoint extends AsyncConnectionEndpoint implements Identifiable {
+ static class InternalConnectionEndpoint extends AsyncConnectionEndpoint implements ConnectionHolder, Identifiable {
private final AtomicReference> poolEntryRef;
private final String id;
@@ -773,6 +775,12 @@ public EndpointInfo getInfo() {
return null;
}
+ @Override
+ public HttpConnection get() {
+ final PoolEntry poolEntry = poolEntryRef.get();
+ return poolEntry != null ? poolEntry.getConnection() : null;
+ }
+
}
/**
From 4466cca4a1021820586789e6bfeb885643451b58 Mon Sep 17 00:00:00 2001
From: Arturo Bernal
Date: Sun, 14 Sep 2025 18:37:09 +0200
Subject: [PATCH 16/29] HTTPCLIENT-2393 - remove rspauth from Authorization
(#716)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
RFC 7616 compliance: rspauth is server-side (Authentication-Info ยง3.5) only.
(cherry picked from commit 89da74220a11341b42ec455324b36094cc29f225)
---
.../org/apache/hc/client5/http/impl/auth/DigestScheme.java | 1 -
.../apache/hc/client5/http/impl/auth/TestDigestScheme.java | 5 +++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
index 0771f9bd3b..7f57e2f78a 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
@@ -471,7 +471,6 @@ private String createDigestResponse(final HttpRequest request) throws Authentica
params.add(new BasicNameValuePair("qop", qop == QualityOfProtection.AUTH_INT ? "auth-int" : "auth"));
params.add(new BasicNameValuePair("nc", nc));
params.add(new BasicNameValuePair("cnonce", cnonce));
- params.add(new BasicNameValuePair("rspauth", hasha2));
}
if (algorithm != null) {
params.add(new BasicNameValuePair("algorithm", algorithm));
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java
index 6b0fc5b59d..e44fe89558 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestDigestScheme.java
@@ -903,7 +903,7 @@ void testDigestAuthenticationWithNonAsciiUsername() throws Exception {
}
@Test
- void testRspAuthFieldAndQuoting() throws Exception {
+ void testRspAuthFieldNotPresentClient() throws Exception {
final ClassicHttpRequest request = new BasicClassicHttpRequest("POST", "/");
final HttpHost host = new HttpHost("somehost", 80);
final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
@@ -921,7 +921,8 @@ void testRspAuthFieldAndQuoting() throws Exception {
final Map table = parseAuthResponse(authResponse);
- Assertions.assertNotNull(table.get("rspauth"));
+ Assertions.assertNotNull(table);
+ Assertions.assertNull(table.get("rspauth"));
}
@Test
From cb2ccda8f2987a56eadfd500c94bc12d3be1e730 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sun, 14 Sep 2025 21:07:23 +0200
Subject: [PATCH 17/29] Bug fix: connections managers to ensure open
connections have socket timeout set based on ConnectionConfig upon lease
---
.../async/TestAsyncConnectionManagement.java | 62 ++++++++++++++++---
.../sync/TestConnectionManagement.java | 48 ++++++++++++++
.../io/BasicHttpClientConnectionManager.java | 3 +
.../PoolingHttpClientConnectionManager.java | 3 +
.../PoolingAsyncClientConnectionManager.java | 3 +
5 files changed, 110 insertions(+), 9 deletions(-)
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
index fa6f35ab55..b44df5d5c5 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncConnectionManagement.java
@@ -37,6 +37,7 @@
import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.impl.ConnectionHolder;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.nio.AsyncConnectionEndpoint;
@@ -45,6 +46,7 @@
import org.apache.hc.client5.testing.extension.async.ServerProtocolLevel;
import org.apache.hc.client5.testing.extension.async.TestAsyncClient;
import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
@@ -89,11 +91,9 @@ protected SimpleHttpResponse handle(
void testReleaseConnection() throws Exception {
final HttpHost target = startServer();
- final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
- .build();
- configureClient(builder -> builder.setConnectionManager(connManager));
final TestAsyncClient client = startClient();
+ final PoolingAsyncClientConnectionManager connManager = client.getConnectionManager();
connManager.setMaxTotal(1);
final HttpRoute route = new HttpRoute(target, null, false);
@@ -159,11 +159,9 @@ void testReleaseConnection() throws Exception {
void testReleaseConnectionWithTimeLimits() throws Exception {
final HttpHost target = startServer();
- final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
- .build();
- configureClient(builder -> builder.setConnectionManager(connManager));
final TestAsyncClient client = startClient();
+ final PoolingAsyncClientConnectionManager connManager = client.getConnectionManager();
connManager.setMaxTotal(1);
final HttpRoute route = new HttpRoute(target, null, false);
@@ -218,11 +216,9 @@ void testReleaseConnectionWithTimeLimits() throws Exception {
void testCloseExpiredIdleConnections() throws Exception {
final HttpHost target = startServer();
- final PoolingAsyncClientConnectionManager connManager = PoolingAsyncClientConnectionManagerBuilder.create()
- .build();
- configureClient(builder -> builder.setConnectionManager(connManager));
final TestAsyncClient client = startClient();
+ final PoolingAsyncClientConnectionManager connManager = client.getConnectionManager();
connManager.setMaxTotal(1);
final HttpRoute route = new HttpRoute(target, null, false);
@@ -312,4 +308,52 @@ void testCloseExpiredTTLConnections() throws Exception {
connManager.close();
}
+ @Test
+ void testConnectionTimeoutSetting() throws Exception {
+ final HttpHost target = startServer();
+
+ final TestAsyncClient client = startClient();
+
+ final Timeout connectionSocketTimeout = Timeout.ofMinutes(5);
+
+ final PoolingAsyncClientConnectionManager connManager = client.getConnectionManager();
+ connManager.setMaxTotal(1);
+ connManager.setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setSocketTimeout(connectionSocketTimeout)
+ .build());
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ final SimpleHttpRequest request = SimpleRequestBuilder.get()
+ .setHttpHost(target)
+ .setPath("/")
+ .addHeader(HttpHeaders.HOST, target.toHostString())
+ .build();
+ final HttpClientContext context = HttpClientContext.create();
+
+ final Future endpointFuture1 = connManager.lease("id1", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint1 = endpointFuture1.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+
+ final Future connectFuture1 = connManager.connect(endpoint1, client.getImplementation(), TIMEOUT, null, context, null);
+ final AsyncConnectionEndpoint openEndpoint1 = connectFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+ // Modify socket timeout of the endpoint
+ endpoint1.setSocketTimeout(Timeout.ofSeconds(30));
+
+ final Future responseFuture1 = openEndpoint1.execute("ex-1", SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), null);
+ final SimpleHttpResponse response1 = responseFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+
+ connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ final Future endpointFuture2 = connManager.lease("id2", route, null, TIMEOUT, null);
+ final AsyncConnectionEndpoint endpoint2 = endpointFuture2.get(LEASE_TIMEOUT.getDuration(), LEASE_TIMEOUT.getTimeUnit());
+ Assertions.assertTrue(endpoint2.isConnected());
+
+ final HttpConnection connection = ((ConnectionHolder) endpoint2).get();
+ Assertions.assertEquals(connectionSocketTimeout, connection.getSocketTimeout());
+
+ connManager.close();
+ }
+
}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
index c46f98e87c..9380ad25a3 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java
@@ -32,6 +32,7 @@
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.impl.ConnectionHolder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.ConnectionEndpoint;
@@ -42,6 +43,7 @@
import org.apache.hc.client5.testing.extension.sync.TestClient;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpConnection;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus;
@@ -326,4 +328,50 @@ void testCloseExpiredTTLConnections() throws Exception {
connManager.close();
}
+ @Test
+ void testConnectionTimeoutSetting() throws Exception {
+ configureServer(bootstrap -> bootstrap
+ .register("/random/*", new RandomHandler()));
+ final HttpHost target = startServer();
+
+ final Timeout connectionSocketTimeout = Timeout.ofMinutes(5);
+
+ final TestClient client = client();
+ final PoolingHttpClientConnectionManager connManager = client.getConnectionManager();
+ connManager.setMaxTotal(1);
+ connManager.setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setSocketTimeout(connectionSocketTimeout)
+ .build());
+
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ final ClassicHttpRequest request = new BasicClassicHttpRequest("GET", target, uri);
+ final HttpClientContext context = HttpClientContext.create();
+
+ final LeaseRequest leaseRequest1 = connManager.lease("id1", route, null);
+ final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS);
+
+ connManager.connect(endpoint1, null, context);
+
+ // Modify socket timeout of the endpoint
+ endpoint1.setSocketTimeout(Timeout.ofSeconds(30));
+
+ try (final ClassicHttpResponse response1 = endpoint1.execute("id1", request, exec, context)) {
+ Assertions.assertEquals(HttpStatus.SC_OK, response1.getCode());
+ }
+
+ connManager.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
+
+ final LeaseRequest leaseRequest2 = connManager.lease("id2", route, null);
+ final ConnectionEndpoint endpoint2 = leaseRequest2.get(Timeout.ZERO_MILLISECONDS);
+ Assertions.assertTrue(endpoint2.isConnected());
+
+ final HttpConnection connection = ((ConnectionHolder) endpoint2).get();
+ Assertions.assertEquals(connectionSocketTimeout, connection.getSocketTimeout());
+
+ connManager.close();
+ }
+
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
index 9ca40f9986..d634d94682 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java
@@ -401,6 +401,9 @@ ManagedHttpClientConnection getConnection(final HttpRoute route, final Object st
this.created = System.currentTimeMillis();
} else {
this.conn.activate();
+ if (connectionConfig.getSocketTimeout() != null) {
+ conn.setSocketTimeout(connectionConfig.getSocketTimeout());
+ }
}
this.leased = true;
if (LOG.isDebugEnabled()) {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
index e027f56a81..6d8ccea485 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
@@ -386,6 +386,9 @@ public ConnectionEndpoint get(
final ManagedHttpClientConnection conn = poolEntry.getConnection();
if (conn != null) {
conn.activate();
+ if (connectionConfig.getSocketTimeout() != null) {
+ conn.setSocketTimeout(connectionConfig.getSocketTimeout());
+ }
} else {
poolEntry.assignConnection(connFactory.createConnection(null));
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
index be7b5348e4..512276181d 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
@@ -326,6 +326,9 @@ void leaseCompleted(final PoolEntry poo
final ManagedAsyncClientConnection connection = poolEntry.getConnection();
if (connection != null) {
connection.activate();
+ if (connectionConfig.getSocketTimeout() != null) {
+ connection.setSocketTimeout(connectionConfig.getSocketTimeout());
+ }
}
if (LOG.isDebugEnabled()) {
LOG.debug("{} endpoint leased {}", id, ConnPoolSupport.formatStats(route, state, pool));
From 6675520feeaefd7d063d2db234b19e980918a6a4 Mon Sep 17 00:00:00 2001
From: ChangYong
Date: Sat, 23 Aug 2025 20:33:14 +0900
Subject: [PATCH 18/29] Clarify behavior of the protocol level responseTimeout
and the connection management level socketTimeout and their interrelation
---
.../hc/client5/http/config/ConnectionConfig.java | 10 +++++++++-
.../apache/hc/client5/http/config/RequestConfig.java | 4 ++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java
index b372d461aa..4be79544a8 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java
@@ -149,7 +149,15 @@ public Builder setSocketTimeout(final int soTimeout, final TimeUnit timeUnit) {
}
/**
- * Determines the default socket timeout value for I/O operations.
+ * Determines the default socket timeout value for I/O operations on
+ * connections created by this configuration.
+ * A timeout value of zero is interpreted as an infinite timeout.
+ *
+ * This value acts as a baseline at the connection management layer.
+ * This parameter overrides the socket timeout setting applied at the I/O layer
+ * and in its tuen can overridden by settings applied at the protocol layer
+ * for the duration of a message exchange.
+ *
*
* Default: {@code null} (undefined)
*
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java
index 186182c4f9..e5d791c56e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java
@@ -517,6 +517,10 @@ public Builder setConnectTimeout(final long connectTimeout, final TimeUnit timeU
* HTTP transports with message multiplexing.
*
*
+ * This parameter overrides the socket timeout setting applied at the connection
+ * management or I/O layers for the duration of a message exchange.
+ *
+ *
* Please note that response timeout is not a deadline. Its absolute value
* can be exceeded, for example, in case of automatic request re-execution.
* Please make sure the automatic request re-execution policy has been
From 038b74f09e014a5a35deb316ff97d48141096c3e Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Mon, 15 Sep 2025 19:15:07 +0200
Subject: [PATCH 19/29] Bug fix: Use 1 second timeout when closing out
connections inside a connection pool lock
---
.../http/impl/io/PoolingHttpClientConnectionManager.java | 2 ++
.../http/impl/nio/PoolingAsyncClientConnectionManager.java | 2 ++
2 files changed, 4 insertions(+)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
index 6d8ccea485..c16eddce0c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java
@@ -70,6 +70,7 @@
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.pool.ConnPoolControl;
+import org.apache.hc.core5.pool.DefaultDisposalCallback;
import org.apache.hc.core5.pool.LaxConnPool;
import org.apache.hc.core5.pool.ManagedConnPool;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
@@ -223,6 +224,7 @@ public PoolingHttpClientConnectionManager(
DEFAULT_MAX_TOTAL_CONNECTIONS,
timeToLive,
poolReusePolicy,
+ new DefaultDisposalCallback<>(),
null) {
@Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
index 512276181d..90d3df916c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
@@ -77,6 +77,7 @@
import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.pool.ConnPoolControl;
+import org.apache.hc.core5.pool.DefaultDisposalCallback;
import org.apache.hc.core5.pool.LaxConnPool;
import org.apache.hc.core5.pool.ManagedConnPool;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
@@ -181,6 +182,7 @@ public PoolingAsyncClientConnectionManager(
DEFAULT_MAX_TOTAL_CONNECTIONS,
timeToLive,
poolReusePolicy,
+ new DefaultDisposalCallback<>(),
null) {
@Override
From 694394ca8f5bb05eb36f9ec70ab8101a3859ed49 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sat, 13 Sep 2025 22:14:51 +0200
Subject: [PATCH 20/29] HTTPCLIENT-2391: improved GRACEGUL shutdown of
ExecutorService used internally by async clients
---
.../impl/async/AbstractHttpAsyncClientBase.java | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
index e6b859c33d..275b82b1c9 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
@@ -31,6 +31,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.core5.concurrent.FutureCallback;
@@ -133,7 +134,18 @@ public final void close(final CloseMode closeMode) {
}
ioReactor.initiateShutdown();
ioReactor.close(closeMode);
- executorService.shutdownNow();
+ if (closeMode == CloseMode.GRACEFUL) {
+ executorService.shutdown();
+ try {
+ if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+ } catch (final InterruptedException ignore) {
+ Thread.currentThread().interrupt();
+ }
+ } else {
+ executorService.shutdownNow();
+ }
internalClose(closeMode);
}
From 61f21a5ee5bb853ba3985939a8f93e54fbbe0183 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sat, 20 Sep 2025 12:52:36 +0200
Subject: [PATCH 21/29] Upgraded HttpCore to version 5.3.6
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 844fe25dbc..4647c68bc0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
1.8
1.8
- 5.3.5
+ 5.3.6
2.24.3
0.1.2
2.5.2
From 66dea80e40b48332f41722126a4ee7bef9a77d78 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sat, 20 Sep 2025 14:40:17 +0200
Subject: [PATCH 22/29] Updated release notes for HttpClient 5.5.1 release
---
RELEASE_NOTES.txt | 53 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index ef49b3a9f0..557d642b9f 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,9 +1,62 @@
Release 5.5.1
------------------
+This is a maintenance release that fixes several defects in the connection management
+code and a regression in the DIGEST authentication reported since the previous release.
+It also upgrades HttpCore to version 5.3.6.
+
+
Change Log
-------------------
+* HTTPCLIENT-2391: Improved GRACEGUL shutdown of ExecutorService used internally by async
+ clients.
+ Contributed by Oleg Kalnichevski
+
+* Bug fix: Use a 1 second timeout when closing out connections inside a connection pool lock.
+ Contributed by Oleg Kalnichevski
+
+* Clarified the behavior of the protocol-level responseTimeout and the connection management
+ level socketTimeout and their interrelation.
+ Contributed by ChangYong
+
+* Bug fix: Connection managers to ensure open connections have a socket timeout set based on
+ ConnectionConfig upon lease.
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2393: Remove `rspauth` attribute from `Authorization` DIGEST header (#716)
+ RFC 7616 compliance: rspauth is server-side (Authentication-Info 3.5) only.
+ Contributed by Arturo Bernal
+
+* HTTPCLIENT-2386: Classic transport to use the connect timeout as a default if the TLS timeout has
+ not been explicitly set.
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2384: Socket options related to TcpKeepAlive are ignored.
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2371: Logging of request re-execution at INFO priority.
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2379: Fixed a defect in H2SharingConnPool causing an IllegalStateException
+ when releasing the same connection from multiple threads. (#663)
+ Contributed by Arturo Bernal
+
+* Fixed the behavior of the `validateAfterInactivity` connection setting by the async
+ connection manager.
+ Contributed by Ryan Schmitt
+
+* HTTPCLIENT-2376: Fixed the problem with ContentCompressionExec not taking `acceptEncoding`
+ parameter into account.
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2372: Normalize HttpHost port comparison to treat implicit default ports as
+ equal (#643).
+ Contributed by Arturo Bernal
+
+* Maven wrapper
+ Contributed by Ryan Schmitt
+
* Bump org.junit:junit-bom from 5.12.2 to 5.13.0 #639.
Contributed by Gary Gregory
From 844f156a80ad914f5aec5a00db677b6f565ea0e6 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sat, 27 Sep 2025 18:17:50 +0200
Subject: [PATCH 23/29] Upgraded HttpClient version to 5.5.2-SNAPSHOT
---
httpclient5-cache/pom.xml | 2 +-
httpclient5-fluent/pom.xml | 2 +-
httpclient5-testing/pom.xml | 2 +-
httpclient5/pom.xml | 2 +-
pom.xml | 4 ++--
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/httpclient5-cache/pom.xml b/httpclient5-cache/pom.xml
index 5d19f7ec41..79dd5bc095 100644
--- a/httpclient5-cache/pom.xml
+++ b/httpclient5-cache/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
httpclient5-cache
Apache HttpClient Cache
diff --git a/httpclient5-fluent/pom.xml b/httpclient5-fluent/pom.xml
index e98aecda40..6fdf8aad7c 100644
--- a/httpclient5-fluent/pom.xml
+++ b/httpclient5-fluent/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
httpclient5-fluent
Apache HttpClient Fluent
diff --git a/httpclient5-testing/pom.xml b/httpclient5-testing/pom.xml
index 125e16d896..42e20440da 100644
--- a/httpclient5-testing/pom.xml
+++ b/httpclient5-testing/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
httpclient5-testing
Apache HttpClient Integration Tests
diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml
index 2cd51e18f2..4a0add9efb 100644
--- a/httpclient5/pom.xml
+++ b/httpclient5/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
httpclient5
Apache HttpClient
diff --git a/pom.xml b/pom.xml
index 4647c68bc0..aacbe62fd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
Apache HttpComponents Client Parent
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
Apache HttpComponents Client is a library of components for building client side HTTP services
https://hc.apache.org/httpcomponents-client-5.5.x/${project.version}/
1999
@@ -48,7 +48,7 @@
scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-client.git
scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-client.git
https://github.com/apache/httpcomponents-client/tree/${project.scm.tag}
- 5.5.1-SNAPSHOT
+ 5.5.2-SNAPSHOT
From eb2a831ceb989930f984a9c22b3ce5ea792ebe5b Mon Sep 17 00:00:00 2001
From: Istvan Toth
Date: Tue, 21 Oct 2025 17:19:01 +0200
Subject: [PATCH 24/29] HTTPCLIENT-2403: Mutual authentication check not
performed for proxies (#745)
---
.../hc/client5/http/impl/async/AsyncProtocolExec.java | 11 +++++------
.../client5/http/impl/auth/AuthenticationHandler.java | 4 ++--
.../hc/client5/http/impl/classic/ProtocolExec.java | 11 +++++------
3 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
index 0d8c504627..1b35f8cb34 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
@@ -334,26 +334,25 @@ private boolean needAuthentication(
}
}
+ boolean targetNeedsAuth = false;
+ boolean proxyNeedsAuth = false;
if (targetAuthRequested || targetMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
+ targetNeedsAuth = authenticator.handleResponse(target, ChallengeType.TARGET, response,
targetAuthStrategy, targetAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
}
-
- return updated;
}
if (proxyAuthRequested || proxyMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
+ proxyNeedsAuth = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
proxyAuthStrategy, proxyAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
}
-
- return updated;
}
+ return targetNeedsAuth || proxyNeedsAuth;
}
return false;
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
index 8d38ec8e75..fc7e4a22dd 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthenticationHandler.java
@@ -121,8 +121,8 @@ public boolean isChallenged(
}
/**
- * Determines whether the given response represents an authentication challenge, without
- * changing the {@link AuthExchange} state.
+ * Determines whether the given response represents an authentication challenge
+ * of challangeType, without changing the {@link AuthExchange} state.
*
* @param challengeType the challenge type (target or proxy).
* @param response the response message head.
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
index fe3d35281f..b927dbc798 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
@@ -295,26 +295,25 @@ private boolean needAuthentication(
}
}
+ boolean targetNeedsAuth = false;
+ boolean proxyNeedsAuth = false;
if (targetAuthRequested || targetMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(target, ChallengeType.TARGET, response,
+ targetNeedsAuth = authenticator.handleResponse(target, ChallengeType.TARGET, response,
targetAuthStrategy, targetAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
}
-
- return updated;
}
if (proxyAuthRequested || proxyMutualAuthRequired) {
- final boolean updated = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
+ proxyNeedsAuth = authenticator.handleResponse(proxy, ChallengeType.PROXY, response,
proxyAuthStrategy, proxyAuthExchange, context);
if (authCacheKeeper != null) {
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
}
-
- return updated;
}
+ return targetNeedsAuth || proxyNeedsAuth;
}
return false;
}
From 9c83a8e43fb82467351a6d16b1ae78e0f6320873 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Sun, 14 Dec 2025 23:08:24 +0100
Subject: [PATCH 25/29] Fixed incompatibility with HttpCore 5.4
---
.../DefaultHttpClientConnectionOperator.java | 29 +++++++++++++------
pom.xml | 2 +-
2 files changed, 21 insertions(+), 10 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
index cebfcfd4d6..a10c436b88 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java
@@ -31,11 +31,14 @@
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketAddress;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLSocket;
+import jdk.net.ExtendedSocketOptions;
+import jdk.net.Sockets;
import org.apache.hc.client5.http.ConnectExceptionSupport;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SchemePortResolver;
@@ -58,7 +61,6 @@
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.io.Closer;
-import org.apache.hc.core5.io.SocketSupport;
import org.apache.hc.core5.net.NamedEndpoint;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
@@ -79,6 +81,11 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpClientConnectionOperator.class);
+ @SuppressWarnings("Since15")
+ private static final boolean SUPPORTS_KEEPALIVE_OPTIONS = Sockets.supportedOptions(Socket.class)
+ .containsAll(Arrays.asList(ExtendedSocketOptions.TCP_KEEPIDLE, ExtendedSocketOptions.TCP_KEEPINTERVAL,
+ ExtendedSocketOptions.TCP_KEEPCOUNT));
+
static final DetachedSocketFactory PLAIN_SOCKET_FACTORY = socksProxy -> socksProxy == null ? new Socket() : new Socket(socksProxy);
private final DetachedSocketFactory detachedSocketFactory;
@@ -145,6 +152,7 @@ public void connect(
connect(conn, host, null, localAddress, timeout, socketConfig, null, context);
}
+ @SuppressWarnings("Since15")
@Override
public void connect(
final ManagedHttpClientConnection conn,
@@ -199,14 +207,17 @@ public void connect(
if (socketConfig.getSndBufSize() > 0) {
socket.setSendBufferSize(socketConfig.getSndBufSize());
}
- if (socketConfig.getTcpKeepIdle() > 0) {
- SocketSupport.setOption(socket, SocketSupport.TCP_KEEPIDLE, socketConfig.getTcpKeepIdle());
- }
- if (socketConfig.getTcpKeepInterval() > 0) {
- SocketSupport.setOption(socket, SocketSupport.TCP_KEEPINTERVAL, socketConfig.getTcpKeepInterval());
- }
- if (socketConfig.getTcpKeepCount() > 0) {
- SocketSupport.setOption(socket, SocketSupport.TCP_KEEPCOUNT, socketConfig.getTcpKeepCount());
+ if (SUPPORTS_KEEPALIVE_OPTIONS) {
+ if (socketConfig.getTcpKeepIdle() > 0) {
+ Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPIDLE, socketConfig.getTcpKeepIdle());
+ }
+ if (socketConfig.getTcpKeepInterval() > 0) {
+ Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPINTERVAL,
+ socketConfig.getTcpKeepInterval());
+ }
+ if (socketConfig.getTcpKeepCount() > 0) {
+ Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPCOUNT, socketConfig.getTcpKeepCount());
+ }
}
final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
if (linger >= 0) {
diff --git a/pom.xml b/pom.xml
index aacbe62fd9..407ab922ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,7 +76,7 @@
2.2.21
1.21.1
5.3
- javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer
+ javax.net.ssl.SSLEngine,javax.net.ssl.SSLParameters,java.nio.ByteBuffer,java.nio.CharBuffer,jdk.net.ExtendedSocketOptions,jdk.net.Sockets
From 15f9d4f83f2fc3fc74aaf1c7342694b6fb9bfcbb Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Mon, 15 Dec 2025 10:53:44 +0100
Subject: [PATCH 26/29] Bump org.junit:junit-bom from 5.13.0 to 5.13.3
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 407ab922ee..8eed3d18e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,7 +69,7 @@
3.10.8
2.12.3
1.7.36
- 5.13.0
+ 5.13.3
3.0
4.11.0
1
From 87d86a1c99d73cd45f71cd89809ffb7cfcec6feb Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Mon, 15 Dec 2025 10:23:19 +0100
Subject: [PATCH 27/29] Limit the length of content codec list that can be
processed automatically
---
.../impl/classic/ContentCompressionExec.java | 26 +++++++++++++--
.../classic/TestContentCompressionExec.java | 32 +++++++++++++++++++
2 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
index ac3cd5a559..0d88979479 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java
@@ -54,6 +54,7 @@
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.message.BasicHeaderValueParser;
@@ -77,14 +78,18 @@
@Internal
public final class ContentCompressionExec implements ExecChainHandler {
+ public static final int MAX_CODEC_LIST_LEN = 5;
+
private final Header acceptEncoding;
private final Lookup decoderRegistry;
private final boolean ignoreUnknown;
+ private final int maxCodecListLen;
public ContentCompressionExec(
final List acceptEncoding,
final Lookup decoderRegistry,
- final boolean ignoreUnknown) {
+ final boolean ignoreUnknown,
+ final int maxCodecListLen) {
final boolean brotliSupported = decoderRegistry == null && BrotliDecompressingEntity.isAvailable();
if (acceptEncoding != null) {
@@ -112,10 +117,22 @@ public ContentCompressionExec(
this.decoderRegistry = builder.build();
}
this.ignoreUnknown = ignoreUnknown;
+ this.maxCodecListLen = maxCodecListLen;
+ }
+
+ public ContentCompressionExec(
+ final List acceptEncoding,
+ final Lookup decoderRegistry,
+ final boolean ignoreUnknown) {
+ this(acceptEncoding, decoderRegistry, ignoreUnknown, MAX_CODEC_LIST_LEN);
}
public ContentCompressionExec(final boolean ignoreUnknown) {
- this(null, null, ignoreUnknown);
+ this(null, null, ignoreUnknown, MAX_CODEC_LIST_LEN);
+ }
+
+ public ContentCompressionExec(final int maxCodecListLen) {
+ this(null, null, true, maxCodecListLen);
}
/**
@@ -128,7 +145,7 @@ public ContentCompressionExec(final boolean ignoreUnknown) {
*
*/
public ContentCompressionExec() {
- this(null, null, true);
+ this(null, null, true, MAX_CODEC_LIST_LEN);
}
@@ -158,6 +175,9 @@ public ClassicHttpResponse execute(
if (contentEncoding != null) {
final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
final HeaderElement[] codecs = BasicHeaderValueParser.INSTANCE.parseElements(contentEncoding, cursor);
+ if (maxCodecListLen > 0 && codecs.length > maxCodecListLen) {
+ throw new ProtocolException("Codec list exceeds maximum of " + maxCodecListLen + " elements");
+ }
for (final HeaderElement codec : codecs) {
final String codecname = codec.getName().toLowerCase(Locale.ROOT);
final InputStreamFactory decoderFactory = decoderRegistry.lookup(codecname);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
index 60eaa27f6f..13df4e32c8 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java
@@ -40,6 +40,7 @@
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
@@ -236,4 +237,35 @@ void testContentEncodingRequestParameter() throws Exception {
Assertions.assertFalse(entity instanceof GzipDecompressingEntity);
}
+ @Test
+ void testContentEncodingExceedsCodecListLenMax() throws Exception {
+ impl = new ContentCompressionExec(5);
+
+ final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/");
+ final ClassicHttpResponse response1 = new BasicClassicHttpResponse(200, "OK");
+ final HttpEntity original1 = EntityBuilder.create()
+ .setText("encoded stuff")
+ .setContentEncoding("gzip,gzip,gzip,gzip,gzip")
+ .build();
+ response1.setEntity(original1);
+
+ Mockito.when(execChain.proceed(request, scope)).thenReturn(response1);
+
+ final HttpEntity entity = response1.getEntity();
+ Assertions.assertNotNull(entity);
+ Assertions.assertFalse(entity instanceof GzipDecompressingEntity);
+
+ final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
+ final HttpEntity original2 = EntityBuilder.create()
+ .setText("encoded stuff")
+ .setContentEncoding("gzip,gzip,gzip,gzip,gzip,gzip")
+ .build();
+ response2.setEntity(original2);
+
+ Mockito.when(execChain.proceed(request, scope)).thenReturn(response2);
+
+ final ProtocolException exception = Assertions.assertThrows(ProtocolException.class, () -> impl.execute(request, scope, execChain));
+ Assertions.assertEquals("Codec list exceeds maximum of 5 elements", exception.getMessage());
+ }
+
}
From 67a265eed4c6488fb7f1b99d65a66880ed869ed1 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Mon, 15 Dec 2025 21:59:36 +0100
Subject: [PATCH 28/29] Updated release notes for HttpClient 5.5.2 release
---
RELEASE_NOTES.txt | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 557d642b9f..9c592ad278 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,3 +1,24 @@
+Release 5.5.2
+------------------
+
+This is a maintenance release that fixes incompatibility with the 5.4 branch
+of HttpCore.
+
+
+Change Log
+-------------------
+
+* Limit the length of the content codec list that can be processed automatically.
+ Contributed by Oleg Kalnichevski
+
+* Fixed incompatibility with HttpCore 5.4
+ Contributed by Oleg Kalnichevski
+
+* HTTPCLIENT-2403: Mutual authentication check not performed for proxies (#745).
+ Contributed by Istvan Toth
+
+
+
Release 5.5.1
------------------
@@ -13,7 +34,7 @@ Change Log
clients.
Contributed by Oleg Kalnichevski
-* Bug fix: Use a 1 second timeout when closing out connections inside a connection pool lock.
+* Bug fix: Use a 1-second timeout when closing out connections inside a connection pool lock.
Contributed by Oleg Kalnichevski
* Clarified the behavior of the protocol-level responseTimeout and the connection management
@@ -146,7 +167,7 @@ Release 5.5 ALPHA1
------------------
This is the first ALPHA release in the 5.5 release series. It adds several experimental
-features and improvements such as request multiplexing over a shared HTTP/2 connection
+features and improvements, such as request multiplexing over a shared HTTP/2 connection
and the Classic API facade acting as a compatibility bridge between classic I/O client
services and the asynchronous message transport used internally.
From 7dfc2d3fc27e63c93f946de508a17c4b15ac6f49 Mon Sep 17 00:00:00 2001
From: Oleg Kalnichevski
Date: Fri, 19 Dec 2025 21:34:09 +0100
Subject: [PATCH 29/29] Upgraded HttpClient version to 5.5.3-SNAPSHOT
---
httpclient5-cache/pom.xml | 2 +-
httpclient5-fluent/pom.xml | 2 +-
httpclient5-testing/pom.xml | 2 +-
httpclient5/pom.xml | 2 +-
pom.xml | 4 ++--
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/httpclient5-cache/pom.xml b/httpclient5-cache/pom.xml
index 79dd5bc095..3ab32dc44b 100644
--- a/httpclient5-cache/pom.xml
+++ b/httpclient5-cache/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT
httpclient5-cache
Apache HttpClient Cache
diff --git a/httpclient5-fluent/pom.xml b/httpclient5-fluent/pom.xml
index 6fdf8aad7c..01eb1a1212 100644
--- a/httpclient5-fluent/pom.xml
+++ b/httpclient5-fluent/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT
httpclient5-fluent
Apache HttpClient Fluent
diff --git a/httpclient5-testing/pom.xml b/httpclient5-testing/pom.xml
index 42e20440da..7be2f43cbc 100644
--- a/httpclient5-testing/pom.xml
+++ b/httpclient5-testing/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT
httpclient5-testing
Apache HttpClient Integration Tests
diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml
index 4a0add9efb..c175c761b6 100644
--- a/httpclient5/pom.xml
+++ b/httpclient5/pom.xml
@@ -28,7 +28,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT
httpclient5
Apache HttpClient
diff --git a/pom.xml b/pom.xml
index 8eed3d18e0..eba25a72e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
org.apache.httpcomponents.client5
httpclient5-parent
Apache HttpComponents Client Parent
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT
Apache HttpComponents Client is a library of components for building client side HTTP services
https://hc.apache.org/httpcomponents-client-5.5.x/${project.version}/
1999
@@ -48,7 +48,7 @@
scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-client.git
scm:git:https://gitbox.apache.org/repos/asf/httpcomponents-client.git
https://github.com/apache/httpcomponents-client/tree/${project.scm.tag}
- 5.5.2-SNAPSHOT
+ 5.5.3-SNAPSHOT