Repository navigation
fix!: never send HTTP/3 requests past the proxy - #598
Merged
Merged
Conversation
reqwest sends HTTP/3 requests through its own QUIC connector, which ignores proxies. With `with_http3()` and a proxy, the first request to a host went through the proxy, and once the host advertised h3 (Alt-Svc or its HTTPS DNS record), later requests went out over QUIC from the machine's own IP address. HTTP/3 is now disabled (with a warning) when a proxy is set, either with `with_proxy` or in `HTTPS_PROXY` / `ALL_PROXY`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148RYR4BTGyCYHENSRZjC3S
metalwarrior665
marked this pull request as ready for review
October 24, 2026 10:27
barjin
pushed a commit
that referenced
this pull request
Oct 26, 2026
Found out about this during profiling my Rust Actors based on [crawlee-rs](https://github.com/apify/crawlee-rs). The feature in cargo.toml is [causing this in reqwest](https://github.com/seanmonstar/reqwest/blob/8e1c8061e5dd936762dcef08a64ef2cff584b295/src/async_impl/client.rs#L653) ``` TlsBackend::BuiltRustls(conn) => { #[cfg(feature = "http3")] { let mut h3_tls = conn.clone(); h3_tls.alpn_protocols = vec!["h3".into()]; h3_connector = build_h3_connector(resolver.clone(), h3_tls, ...)?; } ``` ## AI Summary reqwest's `http3` feature makes every client that uses `use_preconfigured_tls` build an HTTP/3 connector, even when HTTP/3 is never used. That means a quinn endpoint per client: a UDP socket, a driver task, and a zeroed receive buffer of at least 2 MB. In a CPU profile of a Rust Instagram scraper that builds one impit client per proxy session, this endpoint setup took **~6% of all CPU**, for a protocol it never uses. HTTP/3 support is now behind a new `http3` feature of the `impit` crate: - With the feature off, clients skip the QUIC setup, and `hickory-*` and `tokio` aren't needed. - The crate builds without `--cfg reqwest_unstable`. - `impit-node` and `impit-python` enable the feature, so their `http3` option behaves as before. - For Rust users, `with_http3()` now needs `features = ["http3"]`. It's removed from the README and doc examples. CI now also runs `cargo test -p impit --features http3`, and the docs are built with the feature on. The proxy fix that used to be part of this PR is now #598. Whichever of the two lands second needs `#[cfg(feature = "http3")]` on #598's test, which calls `with_http3()`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0148RYR4BTGyCYHENSRZjC3S Co-authored-by: Claude <noreply@anthropic.com>
barjin
approved these changes
Oct 26, 2026
barjin
left a comment
Member
There was a problem hiding this comment.
lgtm, thanks @metalwarrior665 !
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
My agent got freaked by the weird http3 + proxy behavior. This is an attempt to clean it up so it returns an error and doesn't silently change behavior.
AI Summary
HTTP/3 through a proxy isn't supported, and the docs say so. That was decided in #246 and #364, documented in #373, and reqwest tracks it upstream as seanmonstar/reqwest#2833. This PR doesn't try to add that support. It makes the unsupported combination fail safe instead of quietly sending traffic from the machine's own IP address.
The change only matters when HTTP/3 is enabled and a proxy is set, whether through
with_proxy/proxyUrlor throughHTTPS_PROXY/ALL_PROXY. Everything else behaves as before, including the default, where HTTP/3 is off.What
masterdoes with HTTP/3 and a proxymasterhttp3_prior_knowledgeH3Enginelooks the record up directly at8.8.8.8, which already exposes the hostname from the real IP. The request then goes out over QUIC from the real IP too.Alt-SvcNone of these cases logs anything or returns an error.
I reproduced this on
masterwith a local logging CONNECT proxy andhttps://www.google.com/. The forced request never reached the proxy. strace showed QUIC packets going directly to142.251.153.119:443, and the DNS lookups going to8.8.8.8/8.8.4.4.With this PR
8.8.8.8.http3_prior_knowledgefails withHttp3Disabled, whose message now mentions the proxy. It no longer goes out directly.HTTPS_PROXY/ALL_PROXYis covered too. Onmasterit leaks the same way, and the docs don't mention it.The PR also removes
.with_http3()from theImpitBuilderdoc example, which combined it with a proxy.Not changed
NO_PROXYisn't considered. If any proxy is set, HTTP/3 is off for all hosts. That's a deliberately conservative choice.http3: true+ proxy setups keep working over HTTP/2. That's easy to flip to an error if you'd prefer.http3feature is compiled in is handled separately in feat: make HTTP/3 opt-in in the Rust crate #595.🤖 Generated with Claude Code
https://claude.ai/code/session_0148RYR4BTGyCYHENSRZjC3S