feat: honour Raw-Request-URI as the HTTP/2 :path - #1280
Open
pjfanning wants to merge 1 commit into
Open
Conversation
The HTTP/1.1 request renderer lets a caller supply the request target verbatim through a `Raw-Request-URI` header, bypassing `Uri` rendering. The HTTP/2 renderer built `:path` from `request.uri` unconditionally, so the same request produced a different target depending on the protocol negotiated. Take the header into account when building `:path`, as HTTP/1.1 does. The header is a `SyntheticHeader`, so it was already excluded from the rendered header block by the `renderInRequests` filter and is only consumed here. As in HTTP/1.1 the value is used as given -- supplying a valid origin-form target is the caller's responsibility. This matters because `Uri` cannot round-trip a percent-encoded path: it decodes segments when parsing and re-encodes them with a keep-set that leaves sub-delims raw, so `%2B` renders back as `+`. Callers that must reproduce a target byte-for-byte -- AWS SigV4 signs the encoded path, so an S3 key containing `+` or `=` fails with SignatureDoesNotMatch otherwise -- had no way to do so over HTTP/2. Refs apache#1273. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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
Refs #1273.
That issue asks for a way to send a request whose path renders exactly as provided, because
Uricannot round-trip a percent-encoded path — it decodes segments when parsing and re-encodes them with thepchar-basekeep-set, which leaves sub-delims raw, so%2Brenders back as+. AWS SigV4 signs the encoded path, so an S3 object key containing+or=is rejected withSignatureDoesNotMatchafter the round trip.While investigating I found the issue's premise is only half right. The escape hatch it proposes (option 2, "a pre-rendered request target that the client renderer honors") already exists for HTTP/1.1:
HttpRequestRendererFactoryspecial-cases aRaw-Request-URIheader and emits it verbatim as the request target. It is aSyntheticHeader, so it is not also rendered as a header line; it is covered by tests inRequestRendererSpecand documented under "synthetic headers" inhttp-model.md, whose example is literally an S3 path.What is genuinely missing is HTTP/2.
RequestRendering.initialHeaderPairsbuilt:pathfromrequest.uri.toHttpRequestTargetOriginFormunconditionally and ignored the header, so the sameHttpRequestproduced a different request target depending on which protocol was negotiated.Modification
Take
Raw-Request-URIinto account when building:path, mirroring HTTP/1.1.The header needs no new suppression logic: being a
SyntheticHeaderit was already excluded from the rendered header block by the existingrenderInRequestsfilter, so it was silently dropped rather than mis-sent. As in HTTP/1.1, the value is used exactly as given — supplying a valid origin-form target is the caller's responsibility, and this is stated in the docs.Also documents, in the existing synthetic-headers section of
http-model.md, that the header is honoured by both clients, why it is needed (theUriround-trip loss), and the SigV4 case.Result
aws-spi-pekko-http, and any proxy or pass-through use case that must not alter the request target, can passSdkHttpRequest.encodedPath()through unchanged over HTTP/2 as well as HTTP/1.1.This does not change the
Urimodel, so the round-trip limitation described in #1273 remains as such; it makes the existing escape hatch work uniformly across protocols. I'd suggest #1273 stays open if a rawUri.Pathrepresentation is still wanted — that is a much larger change, sincePathis a sealed ADT matched exhaustively across the codebase and in user code.Tests
Two cases added to
Http2ClientSpec:Raw-Request-URI("/a%2Bb%20c")sends that value as:path. The assertion compares the whole pseudo/header set, so it also pins that the header itself is not rendered. Verified this fails onmain—:pathcomes out as/a+b%20c— and passes with the change.Uri. This one passes both before and after; it is a characterization test that pins the round-trip loss (/a%2Bb%20cin,/a+b%20cout) which motivates the escape hatch.sbt "http2-tests/testOnly *Http2ClientSpec *Http2ClientServerSpec *Http2PersistentClientSpec *Http2ServerSpec"— 183 pass.sbt http-core/mimaReportBinaryIssues— clean (the change is confined to an@InternalApi private[http2]class). Nativescalafmtclean.References
Refs #1273.
🤖 Generated with Claude Code