Fail fast when the scheme rules out HTTP/2 - #2334
Merged
Merged
Conversation
hyperxpro
force-pushed
the
fix/h2-connection-waiter-gate
branch
from
September 19, 2026 20:16
cadb948 to
5f7c0fc
Compare
hyperxpro
added a commit
that referenced
this pull request
Sep 20, 2026
Motivation: Four follow-ups from #2333 and #2334. 1. A 3xx whose `Location` is missing or empty NPEs in `UriParser` instead of reaching the handler. 2. A `ResponseFilter` that rebuilds the request drops the caller's redirect refusal, so the hops past a replay are no longer covered. 3. An over-cap request to an `https` origin whose handshake settled on HTTP/1.1 waits out `connectTimeout` — the half of #2334 left open. 4. `Uri.isSameBase` compares hosts with `String.equals`, so a redirect differing only in host case reads as cross-origin and strips `Authorization` and `Cookie`. Modification: 1. Treat a missing or empty `Location` as not a redirect. RFC 9110 section 15.4 only redirects when one is provided, and an empty one resolves back to the current URI. 2. Fold each hop's refusals into the exchange, monotonically: a rebuilt request can tighten the posture but not relax it. 3. Mark a host HTTP/1.1 when ALPN settles there, fail anything parked on it, and gate the waiter on that mark. Defer to a sibling that did negotiate HTTP/2, since ALPN is per-connection. 4. Fold the host with `AsciiString.contentEqualsIgnoreCase`, the comparison RFC 9110 section 4.2.3 asks for. A Unicode fold would make `k.example` and a host spelled with U+212A the same base. Result: 1. The 3xx is delivered as a response. 2. A refusal holds for the whole exchange, including hops a filter retargets. 3. The request fails with the permit exception it already had, instead of waiting out `connectTimeout`. 4. Credentials survive a case-differing same-origin redirect.
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.
Motivation:
A request refused a connection permit arms an
Http2ConnectionWaiterthat only a registered HTTP/2 connection can complete. On a cleartext origin with h2c disabled, no registration site can ever fire for that key, so the waiter can only expire: the request is held forconnectTimeoutand then fails with the permit exception it already had, ignoring theacquireFreeChannelTimeoutthe caller configured (0 by default: fail fast). A deferred request has noTimeoutsHoldereither, so its ownrequestTimeoutand absolute deadline go unenforced for the whole wait: a callerasking for 200ms waits 5 seconds.
Any client talking to a cleartext origin with
maxConnectionsset stalls on every over-cap request. Introduced in #2144 and carried through #2227, so present since 3.0.8, though only the non-blocking waiter shape in 3.0.12 and 3.0.13 is addressedhere; the earlier shapes blocked the caller thread and were fixed in 3.0.11.
Modification:
Return early, before arming the waiter, when the origin is not secured and cleartext HTTP/2 is disabled. With the default partitioning that is the exact complement of the sites that register an HTTP/2 connection: ALPN on a secured origin, h2c prior knowledge, and the post-CONNECT tunnel. The guard sits after
pollHttp2, so a cleartext request that does find a live HTTP/2 channel still multiplexes; only the waiting is gated.Result:
An over-cap request to such an origin fails immediately with
TooManyConnectionsExceptioninstead of waiting outconnectTimeout, and the failure is delivered on the caller's thread rather than on the client's shared timerthread.
NettyRequestThrottleTimeoutTestdrops from ~33s to ~7s.