fix: support Host header rewrite - #523
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes WebApp upstream header rewriting so that rewriting Host (via webApp.requestHeaders or GAT RequestHeaderRewrites) actually changes the Host/:authority sent by Go’s net/http transport, by writing to http.Request.Host instead of the header map.
Changes:
- Route all outbound header rewrites through a new helper that special-cases
Hostby settingr.Out.Host. - Apply the same
Hosthandling to both config-driven and GAT-provided request header rewrites. - Add unit tests verifying config
Hostrewrites, GAT overrides, and case-insensitive (host) handling.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| internal/webapphandler/handler.go | Introduces setHeader() and uses it to ensure Host rewrites affect r.Out.Host (what net/http actually sends upstream). |
| internal/webapphandler/handler_test.go | Extends TestRewrite to validate Host rewrite behavior (config, GAT override, lowercase key). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
net/http ignores a Host entry in the header map and sends Request.Host instead, so a Host rewrite from config or the GAT was silently dropped.
e10e719 to
41d0f77
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #523 +/- ##
==========================================
- Coverage 93.74% 93.71% -0.04%
==========================================
Files 42 42
Lines 2684 2687 +3
==========================================
+ Hits 2516 2518 +2
- Misses 167 168 +1
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
| func setHeader(r *httputil.ProxyRequest, name, value string) { | ||
| switch http.CanonicalHeaderKey(name) { | ||
| case "Host": | ||
| r.Out.Host = value |
There was a problem hiding this comment.
We have the following comment on line 124-126:
// Per-resource request header rewrites from the GAT are applied last, so they override
// any config headers with the same name. Malformed or unsupported headers are skipped
// rather than failing the request.
So for GAT rewrites, we expect to proceed with the request if the header is malformed. From what I understand though the Host header is under additional validation:
HTTP/1.1: https://github.com/golang/go/blob/go1.27.0/src/net/http/request.go#L631
(changes it into an empty string "" if it does not validate)
HTTP/2:
https://github.com/golang/go/blob/go1.27.0/src/net/http/internal/httpcommon/httpcommon.go#L235-L237 (fails the request with 502)
So the question is, do we want to preserve this behavior? Do we want to validate the Host header ourselves up front and for Config rewrites, have our own error, and for GAT try to skip as we did before? Or are we OK with empty Host or 502 in this invalid cases (if it includes invalid symbols like @ / # etc)
There was a problem hiding this comment.
I think the comment is a bit misleading. We only skip the header if the template fails to parse or evaluate (updated in 7295c22). We never validate the header name and value according to the RFCs. Go would do those validations and fail the request instead.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
internal/webapphandler/handler.go:155
- When rewriting the Host header,
setHeaderupdatesr.Out.Hostbut leaves any existingHostentry inr.Out.Header. Even though net/http ignoresHeader["Host"]when writing the request, keeping it can create an inconsistent state for any later code/logging that inspectsr.Out.Header. Clear the header-map entry when settingr.Out.Host.
Changes
HostinwebApp.requestHeadersor a GAT request header rewrite now replaces the Host header sent to the upstreamnet/httpignores aHostentry in the header map and sendsRequest.Hostinstead, so the rewrite was silently dropped