Skip to content

grpc/client: HTTP CONNECT proxy support in transport#2698

Merged
arjan-bal merged 33 commits into
grpc:masterfrom
arjan-bal:http-proxy-transport
Jul 24, 2026
Merged

grpc/client: HTTP CONNECT proxy support in transport#2698
arjan-bal merged 33 commits into
grpc:masterfrom
arjan-bal:http-proxy-transport

Conversation

@arjan-bal

@arjan-bal arjan-bal commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

This PR introduces an HttpConnectHandshaker that wraps the ChannelCredentials to complete the HTTP CONNECT handshake based on the Address attributes introduced in #2679. This allows the subchannel to configure proxying transparently without requiring changes to the Transport API.

@arjan-bal
arjan-bal marked this pull request as draft June 22, 2026 08:08
arjan-bal added a commit that referenced this pull request Jul 3, 2026
…ration (#2679)

This PR implements a resolver wrapper that determines proxy routing by
checking the `HTTPS_PROXY` and `NO_PROXY` environment variables. By
default, `hyper-util` matches cURL's behavior by falling back to
`ALL_PROXY` when `HTTPS_PROXY` is unset. However, because Go's
[`FromEnvironment`](https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment)
and gRPC C++ ignore `ALL_PROXY`, gRPC Rust manually configures the
`Matcher` to bypass it, ensuring cross-language consistency.

**Resolution Flow**  
When `HttpsProxyResolver::Builder` builds a resolver for a target URI,
it uses `hyper_util::client::proxy::Matcher` to evaluate the environment
variables:

1. **Direct Connection:** If no proxy is required, it delegates
resolution entirely to the wrapped child builder, bypassing the proxy
resolver.
2. **Proxied Connection:** If a proxy is required, it creates an
`HttpsProxyResolver`, which uses the child DNS resolver to resolve the
*proxy server's* hostname instead of the target.
3. **Attribute Injection:** The `HttpsProxyResolver` intercepts
resolution updates from the child resolver. It attaches the necessary
proxy configuration (the original target authority and basic auth
credentials) as attributes to each resolved proxy address.

In follow-up PRs, the subchannel will read these address attributes to
wrap the channel credentials and carry out the HTTP `CONNECT` handshake
prior to the standard credential handshake.

Internal design doc:
[go/grpc-rust-http-connect](http://go/grpc-rust-http-connect)
Transport changes: #2698
@arjan-bal
arjan-bal force-pushed the http-proxy-transport branch from 1b5c061 to bbde691 Compare July 3, 2026 09:50
@arjan-bal
arjan-bal marked this pull request as ready for review July 3, 2026 09:58
@arjan-bal
arjan-bal requested review from dfawley and ejona86 July 3, 2026 09:58
@arjan-bal arjan-bal changed the title feat(grpc): HTTP CONNECT proxy support in transport grpc/client: HTTP CONNECT proxy support in transport Jul 3, 2026

@dfawley dfawley 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.

I still need to look at the tests, but here's a first round of comments (all pretty minor I think).

Comment thread grpc/src/client/channel.rs Outdated
Comment thread grpc/src/client/subchannel.rs Outdated
Comment thread grpc/src/rt/mod.rs Outdated
Comment thread grpc/src/client/transport/http_connect/rewind.rs Outdated
Comment thread grpc/src/client/transport/http_connect/mod.rs Outdated
@dfawley dfawley assigned arjan-bal and unassigned ejona86 and dfawley Jul 8, 2026
@arjan-bal
arjan-bal force-pushed the http-proxy-transport branch from e8496e4 to a0c54c7 Compare July 14, 2026 09:31
@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Jul 14, 2026
Comment on lines +154 to +169
let certs_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("examples/data/tls");

let server_cert = fs::read(certs_path.join("server.pem")).expect("failed to read server.pem");
let server_key = fs::read(certs_path.join("server.key")).expect("failed to read server.key");
let ca_cert = fs::read(certs_path.join("ca.pem")).expect("failed to read ca.pem");

let identity = Identity::from_pem(server_cert, server_key);
let identity_provider = StaticProvider::new(vec![identity]);
let server_tls_config = ServerTlsConfig::new(identity_provider);
let server_creds = RustlsServerCredendials::new(server_tls_config).unwrap();

let server_listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let server_addr = server_listener.local_addr().unwrap();

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.

Maybe we can make some helpers to avoid some of the duplication here? Even if it's broken up into a few smaller and reusable parts, like one function that creates the credentials, that could be helpful.

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.

Created helper functions to construct client/server credentials, perform the CONNECT handshake, and initialize the server.

@arjan-bal
arjan-bal merged commit dc9fed1 into grpc:master Jul 24, 2026
26 checks passed
@arjan-bal
arjan-bal deleted the http-proxy-transport branch July 24, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants