You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2025/12/06 17:41:20 UTC
(httpcomponents-core) branch 5.4.x created (now 8c5966adc)
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a change to branch 5.4.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
at 8c5966adc Ensure connection is closed immediately upon socket timeout
This branch includes the following new commits:
new 2b5f513d6 H2 Priority example clean-up
new 8c5966adc Ensure connection is closed immediately upon socket timeout
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(httpcomponents-core) 01/02: H2 Priority example clean-up
Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch 5.4.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
commit 2b5f513d67c15e2f146b481e4c0d2ba2122e302f
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Oct 11 21:13:19 2025 +0200
H2 Priority example clean-up
---
.../http2/examples/H2RequestExecutionWithPriorityExample.java | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.java
index 53dc597db..36edd41d6 100644
--- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.java
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2RequestExecutionWithPriorityExample.java
@@ -49,15 +49,14 @@
import org.apache.hc.core5.http2.frame.RawFrame;
import org.apache.hc.core5.http2.impl.H2Processors;
import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
+import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
import org.apache.hc.core5.http2.priority.PriorityFormatter;
import org.apache.hc.core5.http2.priority.PriorityValue;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.util.Timeout;
/**
- * Example: HTTP/2 request that sets RFC 9218 Priority via context and emits the "Priority" header.
- * <p>
- * Requires H2Processors to include H2RequestPriority (client chain) and an HTTP/2 connection.
+ * HTTP/2 request that emits RFC 9218 Priority via "Priority" header.
*/
@Experimental
public class H2RequestExecutionWithPriorityExample {
@@ -69,8 +68,7 @@ public static void main(final String[] args) throws Exception {
.setPushEnabled(false)
.build();
- // Ensure the client processor chain has H2RequestPriority inside (see H2Processors.customClient)
- final HttpAsyncRequester requester = org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap.bootstrap()
+ final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap()
.setH2Config(h2Config)
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
.setHttpProcessor(H2Processors.client()) // includes H2RequestPriority
(httpcomponents-core) 02/02: Ensure connection is closed immediately upon socket timeout
Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch 5.4.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git
commit 8c5966adc06bad2ba1a8f81ee3cade045bfc7fde
Author: Alexis Le Dantec <al...@palantir.com>
AuthorDate: Mon Oct 20 15:49:43 2025 +0200
Ensure connection is closed immediately upon socket timeout
---
.../testing/classic/ClassicIntegrationTest.java | 97 ++++++++++++++++++++++
.../hc/core5/http/impl/io/HttpRequestExecutor.java | 8 ++
2 files changed, 105 insertions(+)
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
index 22f27425c..73ff66f55 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
@@ -32,11 +32,16 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
@@ -52,6 +57,11 @@
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Http1Config;
+import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.io.HttpClientConnection;
+import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
@@ -62,18 +72,23 @@
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.RequestConnControl;
import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestExpectContinue;
import org.apache.hc.core5.http.protocol.RequestTE;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.testing.SSLTestContexts;
import org.apache.hc.core5.testing.extension.classic.ClassicTestResources;
import org.apache.hc.core5.util.Timeout;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import javax.net.ssl.SSLSocket;
+
abstract class ClassicIntegrationTest {
private static final Timeout TIMEOUT = Timeout.ofMinutes(1);
@@ -775,4 +790,86 @@ void testHeaderTooLargePost() throws Exception {
}
}
+ @Test
+ void testImmediateCloseUponSocketTimeout() throws Exception {
+ final int socketTimeoutMillis = 1000;
+ final int serverDelayMillis = 5 * socketTimeoutMillis;
+
+ final ClassicTestServer server = testResources.server();
+
+ final CountDownLatch serverDelayStarted = new CountDownLatch(1);
+
+ // Configure server to delay significantly before responding
+ server.register("*", (request, response, context) -> {
+ serverDelayStarted.countDown();
+ try {
+ // Delay much longer than socket timeout
+ Thread.sleep(serverDelayMillis);
+ } catch (final InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ response.setCode(HttpStatus.SC_OK);
+ response.setEntity(new StringEntity("Delayed response", ContentType.TEXT_PLAIN));
+ });
+
+ server.start();
+
+ final HttpHost host = new HttpHost(scheme.id, "localhost", server.getPort());
+ final HttpCoreContext context = HttpCoreContext.create();
+ final HttpConnectionFactory<? extends HttpClientConnection> connFactory =
+ DefaultBHttpClientConnectionFactory.builder().build();
+
+ final HttpRequestExecutor requestExecutor = new HttpRequestExecutor();
+ final HttpProcessor processor = HttpProcessors.client();
+
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ request.setAuthority(new URIAuthority(host));
+ request.setScheme(host.getSchemeName());
+
+ runWithSocket(host, socketTimeoutMillis, socket -> {
+ try {
+ final HttpClientConnection connection = connFactory.createConnection(socket);
+
+ requestExecutor.preProcess(request, processor, context);
+
+ final long startTime = System.currentTimeMillis();
+ try (final ClassicHttpResponse response = requestExecutor.execute(
+ request, connection, context)) {
+ Assertions.fail("Expected SocketTimeoutException not thrown");
+ } catch (final SocketTimeoutException e) {
+ // Expected due to server delay exceeding socket timeout
+ }
+
+ final long endTime = System.currentTimeMillis();
+ final long durationMillis = endTime - startTime;
+ // Assert that the timeout occurred around the socket timeout duration
+ Assertions.assertTrue(durationMillis >= socketTimeoutMillis && durationMillis < socketTimeoutMillis * 2,
+ String.format("Socket timeout should occur around %dms (took %dms)",
+ socketTimeoutMillis, durationMillis));
+ } catch (final IOException | HttpException e) {
+ Assertions.fail("IOException during request execution: " + e.getMessage());
+ }
+ });
+ }
+
+ private static void runWithSocket(
+ final HttpHost host, final int socketTimeoutMillis, final Consumer<Socket> socketConsumer)
+ throws IOException {
+ try (final Socket clientSocket = new Socket()) {
+ clientSocket.setSoTimeout(socketTimeoutMillis);
+ clientSocket.connect(new InetSocketAddress(host.getHostName(), host.getPort()));
+ if (host.getSchemeName().equalsIgnoreCase("http")) {
+ socketConsumer.accept(clientSocket);
+ return;
+ }
+
+ try (final SSLSocket sslSocket = (SSLSocket) SSLTestContexts.createClientSSLContext()
+ .getSocketFactory()
+ .createSocket(clientSocket, host.getHostName(), -1, true)) {
+ sslSocket.startHandshake();
+
+ socketConsumer.accept(sslSocket);
+ }
+ }
+ }
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java
index 9bff2058e..5208c0fc9 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpRequestExecutor.java
@@ -28,6 +28,7 @@
package org.apache.hc.core5.http.impl.io;
import java.io.IOException;
+import java.net.SocketTimeoutException;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
@@ -217,6 +218,13 @@ public ClassicHttpResponse execute(
} catch (final HttpException | SSLException ex) {
Closer.closeQuietly(conn);
throw ex;
+ } catch (final SocketTimeoutException ex) {
+ // If the server isn't responsive, we want to close the connection immediately
+ // We set the socket timeout to a minimal value in such a case, because in some cases, the connection
+ // might only have access to an SSLSocket that it will try to close gracefully.
+ conn.setSocketTimeout(Timeout.ONE_MILLISECOND);
+ Closer.close(conn, CloseMode.IMMEDIATE);
+ throw ex;
} catch (final IOException | RuntimeException ex) {
Closer.close(conn, CloseMode.IMMEDIATE);
throw ex;