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
Comment thread vertx-core/src/test/java/io/vertx/tests/http/HttpsProxyTest.java Outdated
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

@Override
public boolean isSsl() {
// Report the origin ssl, not the pipeline: in forward-proxy mode the TLS leg to an HTTPS proxy is

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.

so it means it should return false if the connection to the server uses a plain text connection over an HTTPS proxy ?

can we ensure that for both case there is an assertion for this ? I am thinking of

  1. a plain text connection through an HTTPS proxy
  2. an SSL connection through an HTTP proxy

}

proxySslProviderFuture.onComplete(ar -> {
if (ar.failed()) {

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.

we should encapsulate this code in a method instead of channelling it through the proxySslProviderFuture that is a succeeded future when there is no proxy to reduce the callback complexity in the normal case.

so when there is no https proxy we do call directly the method that setup the bootstrap, otherwise we do resolve the proxySslProviderFuture and continue from this.

pipeline.addLast("logging", new LoggingHandler(logging));
}
if (ssl || !vertx.transport().supportFileRegion()) {
boolean sslChannel = 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.

if we use this, shouldn't we remove the boolean ssl from the initChannel method ?

.setSsl(true)
.setUseAlpn(version == HttpVersion.HTTP_2)
.setKeyCertOptions(Cert.SERVER_JKS.get()));
server.requestHandler(req -> req.response().end("Hello from origin"));

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.

I think the server should respond with the boolean ssl status of its connection in its message and we should assert it in tests.

@vietj vietj left a comment

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.

A few comments have been made to this PR.

@vietj

vietj commented Jul 20, 2026

Copy link
Copy Markdown
Member

@gaganis ping, can you also look at the conflict to resolve ?

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