From bded85d347f4603c6547ad2f76ab573225d83d0a Mon Sep 17 00:00:00 2001 From: Ryan Schmitt Date: Sat, 21 Jun 2025 19:04:52 -0700 Subject: [PATCH] Add support for HTTPS over UDS --- .../async/UdsAsyncIntegrationTests.java | 9 +++++ .../testing/sync/UdsIntegrationTests.java | 3 -- .../org/apache/hc/client5/http/HttpRoute.java | 21 ++++++++-- .../apache/hc/client5/http/RouteTracker.java | 2 +- .../DefaultHttpClientConnectionOperator.java | 40 ++++++++++++------- .../impl/routing/DefaultRoutePlanner.java | 4 +- .../apache/hc/client5/http/TestHttpRoute.java | 3 +- 7 files changed, 55 insertions(+), 27 deletions(-) diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/UdsAsyncIntegrationTests.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/UdsAsyncIntegrationTests.java index bb2f0be083..15c5b02b16 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/UdsAsyncIntegrationTests.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/UdsAsyncIntegrationTests.java @@ -46,6 +46,15 @@ public Http1() { } } + @Nested + @DisplayName("Fundamentals (HTTP/1.1, TLS)") + class Http1Tls extends TestHttp1Async { + public Http1Tls() { + super(URIScheme.HTTPS, true); + checkForUdsSupport(); + } + } + @Nested @DisplayName("Request re-execution (HTTP/1.1)") class Http1RequestReExecution extends TestHttp1RequestReExecution { diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/UdsIntegrationTests.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/UdsIntegrationTests.java index 75c7259bde..b492575fd5 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/UdsIntegrationTests.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/UdsIntegrationTests.java @@ -30,8 +30,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - class UdsIntegrationTests { @Nested @DisplayName("Request execution (HTTP/1.1)") @@ -46,7 +44,6 @@ public RequestExecution() { class RequestExecutionTls extends TestClientRequestExecution { public RequestExecutionTls() { super(URIScheme.HTTPS, true); - assumeTrue(false, "HTTPS is not currently supported over Unix domain sockets"); } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRoute.java b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRoute.java index 02d6418d65..abb6c787dd 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRoute.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRoute.java @@ -109,9 +109,7 @@ public final class HttpRoute implements RouteInfo, Cloneable { } private void validateUdsArguments() { - if (this.secure) { - throw new UnsupportedOperationException("HTTPS is not supported over a UDS connection"); - } else if (this.localAddress != null) { + if (this.localAddress != null) { throw new UnsupportedOperationException("A localAddress cannot be specified for a UDS connection"); } else if (this.proxyChain != null) { throw new UnsupportedOperationException("Proxies are not supported over a UDS connection"); @@ -225,7 +223,22 @@ public HttpRoute(final HttpHost target, final NamedEndpoint targetName, final In * @since 5.6 */ public HttpRoute(final HttpHost target, final Path unixDomainSocket) { - this(target, null, null, Collections.emptyList(), unixDomainSocket, false, TunnelType.PLAIN, LayerType.PLAIN); + this(target, false, unixDomainSocket); + } + + /** + * Creates a new direct route that connects over a Unix domain socket rather than TCP. + * + * @param target the host to which to route + * @param secure {@code true} if the route is (to be) secure, + * {@code false} otherwise + * @param unixDomainSocket the path to the Unix domain socket + * + * @since 5.6 + */ + public HttpRoute(final HttpHost target, final boolean secure, final Path unixDomainSocket) { + this(target, null, null, Collections.emptyList(), unixDomainSocket, secure, + TunnelType.PLAIN, LayerType.PLAIN); } /** diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/RouteTracker.java b/httpclient5/src/main/java/org/apache/hc/client5/http/RouteTracker.java index 173d941c07..d0e6ba08b0 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/RouteTracker.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/RouteTracker.java @@ -292,7 +292,7 @@ public HttpRoute toRoute() { if (!this.connected) { return null; } else if (this.unixDomainSocket != null) { - return new HttpRoute(this.targetHost, this.unixDomainSocket); + return new HttpRoute(this.targetHost, this.secure, this.unixDomainSocket); } else { return new HttpRoute(this.targetHost, this.localAddress, this.proxyChain, this.secure, 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 bb764c9533..f50698679b 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 @@ -26,6 +26,7 @@ */ package org.apache.hc.client5.http.impl.io; +import javax.net.ssl.SSLSocket; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; @@ -36,8 +37,6 @@ import java.util.Collections; import java.util.List; -import javax.net.ssl.SSLSocket; - import org.apache.hc.client5.http.ConnectExceptionSupport; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.SchemePortResolver; @@ -182,7 +181,7 @@ public void connect( final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress(); final Proxy socksProxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null; if (unixDomainSocket != null) { - connectToUnixDomainSocket(conn, endpointHost, unixDomainSocket, connectTimeout, socketConfig, context, soTimeout); + connectToUnixDomainSocket(conn, endpointHost, endpointName, attachment, unixDomainSocket, connectTimeout, socketConfig, context, soTimeout); return; } @@ -218,17 +217,7 @@ public void connect( conn.setSocketTimeout(soTimeout); final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null; if (tlsSocketStrategy != null) { - final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost; - onBeforeTlsHandshake(context, endpointHost); - if (LOG.isDebugEnabled()) { - LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName); - } - final SSLSocket sslSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context); - conn.bind(sslSocket, socket); - onAfterTlsHandshake(context, endpointHost); - if (LOG.isDebugEnabled()) { - LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName); - } + upgradeToTls(conn, endpointHost, endpointName, attachment, context, tlsSocketStrategy, socket); } return; } catch (final RuntimeException ex) { @@ -249,9 +238,27 @@ public void connect( } } + private void upgradeToTls(final ManagedHttpClientConnection conn, final HttpHost endpointHost, + final NamedEndpoint endpointName, final Object attachment, final HttpContext context, + final TlsSocketStrategy tlsSocketStrategy, final Socket socket) throws IOException { + final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost; + onBeforeTlsHandshake(context, endpointHost); + if (LOG.isDebugEnabled()) { + LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName); + } + final SSLSocket sslSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context); + conn.bind(sslSocket, socket); + onAfterTlsHandshake(context, endpointHost); + if (LOG.isDebugEnabled()) { + LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName); + } + } + private void connectToUnixDomainSocket( final ManagedHttpClientConnection conn, final HttpHost endpointHost, + final NamedEndpoint endpointName, + final Object attachment, final Path unixDomainSocket, final Timeout connectTimeout, final SocketConfig socketConfig, @@ -273,6 +280,11 @@ private void connectToUnixDomainSocket( LOG.debug("{} {} connected to {}", ConnPoolSupport.getId(conn), endpointHost, unixDomainSocket); } conn.setSocketTimeout(soTimeout); + + final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null; + if (tlsSocketStrategy != null) { + upgradeToTls(conn, endpointHost, endpointName, attachment, context, tlsSocketStrategy, socket); + } } catch (final RuntimeException ex) { Closer.closeQuietly(newSocket); throw ex; diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/routing/DefaultRoutePlanner.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/routing/DefaultRoutePlanner.java index 578fa55a97..98b12d57f6 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/routing/DefaultRoutePlanner.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/routing/DefaultRoutePlanner.java @@ -100,10 +100,8 @@ public final HttpRoute determineRoute(final HttpHost host, final HttpRequest req if (unixDomainSocket != null) { if (proxy != null) { throw new UnsupportedOperationException("Proxies are not supported over Unix domain sockets"); - } else if (secure) { - throw new UnsupportedOperationException("HTTPS is not supported over Unix domain sockets"); } - return new HttpRoute(target, unixDomainSocket); + return new HttpRoute(target, secure, unixDomainSocket); } final InetAddress inetAddress = determineLocalAddress(target, context); if (proxy == null) { diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/TestHttpRoute.java b/httpclient5/src/test/java/org/apache/hc/client5/http/TestHttpRoute.java index 1281f4e603..7faa19b6e0 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/TestHttpRoute.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/TestHttpRoute.java @@ -487,8 +487,7 @@ void testUnixDomainSocketValidation() { final List noProxies = Collections.emptyList(); final List oneProxy = Collections.singletonList(PROXY1); new HttpRoute(TARGET1, null, null, noProxies, uds, false, TunnelType.PLAIN, LayerType.PLAIN); - Assertions.assertThrows(RuntimeException.class, () -> - new HttpRoute(TARGET1, null, null, noProxies, uds, true, TunnelType.PLAIN, LayerType.PLAIN)); + new HttpRoute(TARGET1, null, null, null, uds, true, TunnelType.PLAIN, LayerType.PLAIN); Assertions.assertThrows(RuntimeException.class, () -> new HttpRoute(TARGET1, null, LOCAL41, noProxies, uds, false, TunnelType.PLAIN, LayerType.PLAIN)); Assertions.assertThrows(RuntimeException.class, () ->