Skip to content

Add https proxy support#6215

Open
gaganis wants to merge 8 commits into
eclipse-vertx:masterfrom
gaganis:https-proxy-support
Open

Add https proxy support#6215
gaganis wants to merge 8 commits into
eclipse-vertx:masterfrom
gaganis:https-proxy-support

Conversation

@gaganis

@gaganis gaganis commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Motivation:

Closes: #6207

Add HTTPS proxy support (ProxyType.HTTPS)

What this adds

Support for connecting to a forward proxy over TLS — i.e. the connection to
the proxy itself (leg 1) is encrypted. Enabled via:

new ProxyOptions()
  .setType(ProxyType.HTTPS)
  .setHost(...).setPort(...)
  .setSslOptions(new ClientSSLOptions()...);   // TLS options for the proxy connection

The proxying semantics are unchanged from ProxyType.HTTP:

  • Plain origin → forward mode (absolute-URI GET, single TLS leg to the
    proxy).
  • TLS origin → CONNECT tunnel (leg-1 TLS to the proxy, then a second
    nested TLS to the origin). HTTP/2 over the tunnel negotiates via ALPN
    end-to-end. Works for both HttpClient and NetClient (upgradeToSsl).

Key design decisions

  1. ConnectionBase.isSsl() keeps meaning "the origin is TLS", not "the wire
    is TLS".
    Existing code and the public contract rely on
    ConnectionBase.isSsl() reflecting the origin. Rather than overload it,
    transport-level TLS (TLS to an HTTPS proxy) is computed separately via the
    transportSsl/proxyHttps helpers in TcpHttpClientTransport. The new
    override Http1ClientConnection.isSsl() returns the origin flag.

  2. Transport-level "how to connect" calculations live in the transport.
    HttpConnectParams carries intent (forwardProxy flag, origin ssl,
    original proxyOptions); TcpHttpClientTransport derives peer/SNI, TLS
    options, ALPN eligibility, and proxy hostname verification at the point of
    use. The forward-vs-CONNECT decision stays at endpoint-creation time because
    it selects the pooling/connect target (EndpointKey).

  3. Zero-copy file region is gated on the actual pipeline, not
    ConnectionBase.isSsl().
    Because ConnectionBase.isSsl() now strictly
    means "origin", VertxConnection.supportsFileRegion() checks for an
    SslHandler in the pipeline instead of calling the inherited
    ConnectionBase.isSsl() — zero-copy must be disabled whenever the wire is
    encrypted (e.g. leg-1 TLS to the proxy), regardless of the logical origin
    flag.

  4. Two SslHandlers in the CONNECT-tunnel pipeline. NetSocketImpl inserts
    the origin (leg-2) handler after the proxy (leg-1) handler (proxy-ssl
    ssl), and applicationLayerProtocol() reads the innermost
    SslHandler, since ALPN is negotiated on the origin handshake.

  5. Secure defaults for the proxy leg. Hostname verification defaults to
    "HTTPS" for the proxy connection; ALPN is never offered on the leg-1
    (proxy) handshake — it belongs to the origin; there is no plaintext
    fallback
    for an HTTPS proxy (a TLS handshake failure against a plaintext
    proxy fails cleanly rather than downgrading). Mutual TLS to the proxy is
    supported.

  6. Proxy SSL options participate in pool identity. EndpointKey includes
    ProxyOptions.getSslOptions() in equals/hashCode so connections with
    different proxy TLS config aren't pooled together.

Tests

  • New HttpsProxyTest: forward (plain origin), CONNECT tunnel (HTTP/1 and
    HTTP/2 origins), proxy auth, mutual TLS, and negative cases (untrusted proxy
    cert, hostname mismatch, plaintext-proxy no-downgrade).
  • New NetTest cases for CONNECT over a TLS proxy (NetClient +
    upgradeToSsl).
  • ProxyOptionsTest for the new sslOptions field.

Issues encountered & how they were resolved

  • Flaky empty body on the HTTP/2-over-CONNECT test
    (testHttpsProxy_Http2Origin_tunnelled): the response body was read across
    the await() boundary, so body chunks could arrive with no handler attached.
    Fixed by reading the body inside the same future composition
    (resp.body().map(...)), which also folds the negotiated version into the
    asserted value. (Commit 2b8895f51.)
  • Port leak in HttpsProxyTest: fixed (commit 93b7ef36b).

Note on CI failures

CI runs on this branch intermittently failed on tests that I have seen failing
also in unrelated PRs suggesting these are flaky tests.

  • HttpSendFileTest.testSendOpenRangeFileFromClasspath (empty HTTP body), and
  • NetTest.testListenDomainSocketAddress (empty receive, -PNativeEpoll).

Conformance:

You should have signed the Eclipse Contributor Agreement as explained in https://github.com/eclipse/vert.x/blob/master/CONTRIBUTING.md
Please also make sure you adhere to the code style guidelines: https://github.com/vert-x3/wiki/wiki/Vert.x-code-style-guidelines

@gaganis gaganis force-pushed the https-proxy-support branch 2 times, most recently from 01a7c60 to 93b7ef3 Compare June 26, 2026 08:20
@gaganis gaganis force-pushed the https-proxy-support branch from 93b7ef3 to d0021d1 Compare June 26, 2026 11:50
@gaganis gaganis marked this pull request as ready for review June 26, 2026 11:51
@vietj vietj force-pushed the https-proxy-support branch from d0021d1 to 0b5db35 Compare June 28, 2026 21:24
@vietj vietj self-requested a review June 29, 2026 12:19
@vietj vietj added this to the 5.2.0 milestone Jun 30, 2026
public class HttpsProxyTest extends HttpTestBase {

private Vertx proxyVertx;
private HttpProxy proxy;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of manually setting up the proxy, we should instead support that in the io.vertx.test.proxy.Proxy rule


protected boolean supportsFileRegion() {
return vertx.transport().supportFileRegion() && !isSsl() &&!isTrafficShaped();
boolean sslChannel = chctx.pipeline().get(SslHandler.class) != null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the motivation of this change ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used in sendFile to decide whether the connection is encrypted and if the connection is encrypted then it desides not to use zero-copy. The reasoning to my understanding is the the way zero copy writes to the socket bypasses the encryption which is why it is disabled.

In my PR I have opted for isSsl() to refer to how we connect to the origin and not if the connection is encrypted.
So the addition covers the case where we have an SSL connection to the proxy but the origin is plain http. In this case we would have encryption on the pipeline. In that case isSsl() would be false but encryption would exist in the pipeline.

Maybe we should write a test to actually see how this is working. @vietj Are you aware of any tests that exercise this zero-copy function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comment @vietj. It led to finding multiple problems with my implementation that I have now hopefully fixed. I have added a test to verify my motivation for the zero copy io.vertx.tests.http.HttpsProxyTest#testHttpsProxy_sendFileThroughTunnel and I have found the way that I addressed it above was not correct. Instead the fix should have been in NetClientImpl.initChannel. I have fixed that in the latest commited code.

This work also motivated me that I needed to be more precise with how I address my intention to keep isSsl() independent of the existence of an https proxy. The way this was working for not consistent before. I have added io.vertx.tests.http.ProxyIsSslConsistencyTest to confirm this and fixed the cases that were not working correctly.

Comment thread vertx-core/src/main/java/io/vertx/core/net/ProxyOptions.java Outdated
Comment thread vertx-core/src/test/java/io/vertx/tests/http/HttpsProxyTest.java Outdated
Comment thread vertx-core/src/test/java/io/vertx/tests/http/HttpsProxyTest.java Outdated
Comment thread vertx-core/src/test/java/io/vertx/tests/http/HttpsProxyTest.java Outdated
gaganis and others added 2 commits July 1, 2026 11:15
@gaganis gaganis force-pushed the https-proxy-support branch from e0ad60d to 8ae9b16 Compare July 1, 2026 09:16
gaganis added 6 commits July 1, 2026 11:55
We want isSsl to return consistent results regardless
of whether we connect via a http or https proxy. A proxy
is a connection detail that should not affect how origin
connections are viewed
@gaganis gaganis requested a review from vietj July 3, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support HTTPS proxy configuration for Vert.x HTTP client

2 participants