In httputil.ReverseProxy, what does the Rewrite hook give you that a Director function does not?
answer
- two requests, not one
- In and Out
- helpers for the routine rewrites
- the client's forwarding headers are dropped first
- SetURL also decides the outbound Host
basics
~10 sRewrite receives a *httputil.ProxyRequest holding both In, the request the client sent, and Out, the one going upstream, plus the SetURL and SetXForwarded helpers. Director sees only the outbound request, and never the original.
solid answer
~40 sA `Director func(*http.Request)` is handed the outbound clone only; if it needs anything from the original — the client's address, whether the connection was TLS, the Host as sent — it has to close over that request, which forces a per-request Director. `Rewrite func(*httputil.ProxyRequest)` gets both: `r.In` is the inbound request, read-only, and `r.Out` is the one about to be sent. It also brings two helpers: `r.SetURL(target)` points `Out` at the target's scheme, host and base path and makes the outbound Host follow the target, and `r.SetXForwarded()` writes the forwarding headers from what the proxy actually observed. Before Rewrite runs, the proxy deletes the client-supplied `Forwarded` and `X-Forwarded-*` headers from `Out`, so nothing forged is passed on by accident. Set exactly one of the two hooks.
code
go · 9 linesproxy := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
// scheme, host and base path of the target *url.URL
r.SetURL(upstream)
// keep the client's Host; SetURL would use the target's
r.Out.Host = r.In.Host
r.SetXForwarded()
},
}go deeper
Know that a ReverseProxy is configured by a hook that rewrites the outbound request, and that the newer hook hands you both the inbound and the outbound request rather than one of them.
Be able to write both forms and explain the mechanics: what SetURL changes including the Host, what SetXForwarded writes, and that the client's forwarding headers are cleared before Rewrite runs.
Argue the migration in review terms — which existing Director behaviours silently change when you switch, and how you verify the upstream still sees the Host, path and forwarding headers it expects.
Own the default. Whichever hook your shared gateway uses becomes the convention every service behind it inherits, so choose the one whose omissions are safe rather than the one that is shorter to write.
## Two hooks, one job `httputil.ReverseProxy` has to turn the request a client sent into the request it will send upstream. It offers two ways to write that transformation, and at most one of them may be set on a proxy value. **`Director func(*http.Request)`** is the original. It receives the outbound request — a clone of the inbound one, carrying the inbound context — and mutates it in place. The usual body is two assignments: ```go Director: func(req *http.Request) { req.URL.Scheme = upstream.Scheme req.URL.Host = upstream.Host } ``` **`Rewrite func(*httputil.ProxyRequest)`** is the newer form. `ProxyRequest` has two fields, `In` and `Out`. `In` is the request the server received and must be treated as read-only. `Out` is the request that will be sent, and is yours to modify. ## Why the second parameter matters The practical limitation of Director is that the original request is not a parameter. Anything that lives only on the inbound request — `RemoteAddr`, `TLS`, the Host as the client wrote it, a value stashed in the request context by middleware — is either already gone from the clone or ambiguous once your hook has started rewriting. The workaround was to build a Director closure per request, which meant allocating a proxy value per request or wrapping in awkward ways. `ProxyRequest.In` makes the original a first-class, read-only input, so one proxy value configured at start-up can still make per-request decisions. ## The two helpers `(*ProxyRequest).SetURL(target *url.URL)` routes `Out` to the target's scheme, host and base path, joining the target's path in front of the inbound path the way the single-host Director does. The one behavioural difference to remember: `SetURL` also makes the outbound **Host** follow the target's host. If the upstream does name-based virtual hosting on the client's name, restore it explicitly: ```go r.SetURL(upstream) r.Out.Host = r.In.Host ``` `(*ProxyRequest).SetXForwarded()` sets `X-Forwarded-For` to the client's address, `X-Forwarded-Host` to the inbound Host, and `X-Forwarded-Proto` to `http` or `https` depending on whether the inbound connection used TLS. It appends to whatever `X-Forwarded-For` is already on `Out` — which, by default, is nothing. ## The safer default Before calling Rewrite, the proxy deletes `Forwarded`, `X-Forwarded-For`, `X-Forwarded-Host` and `X-Forwarded-Proto` from the outbound request. That is a deliberate reversal of the Director path, where the proxy appends the client's address to whatever chain the client sent. Under Rewrite nothing about the caller's identity is forwarded unless you asked for it, so a client cannot smuggle a fabricated chain through an edge proxy simply because the author of the hook did not think about it. Trusting an inbound chain becomes an explicit line of code. ## What both hooks share Whatever you do in either hook, the proxy still runs its own steps afterwards: it removes hop-by-hop headers from the outbound header map (so a `Connection` you set in the hook is dropped), performs the round trip, and copies the result back. The hook is a rewriting step, not a replacement for `ServeHTTP`. One more constraint worth stating in an interview: the hook is not the place to read or replace the request body. It runs on the request-construction path, and consuming the body there means the upstream gets an empty one. If you must inspect the body, buffer it in middleware in front of the proxy and hand a fresh reader down. ## Choosing For new code, prefer Rewrite: it makes the two requests distinguishable, gives you the helpers, and defaults to forwarding nothing you did not choose to forward. Director is still supported and still correct; it is simply the form that makes the risky things easy to do by omission.
- What does ProxyRequest.SetURL do to the outbound request's Host header?It makes the Host follow the target: after `SetURL`, the request is sent with the target URL's host. That differs from the single-host Director, which leaves the client's Host in place. If the upstream routes by the client's name, add `r.Out.Host = r.In.Host` after the call.
- Why can a Director not simply read the inbound request?Because it is not given one. Director's only parameter is the outbound clone it is meant to mutate. Reaching the original required closing over it, which meant constructing the hook per request. `ProxyRequest.In` makes the original an explicit, read-only input instead.
- Can you set both Director and Rewrite on the same ReverseProxy?No — at most one may be set, and the package documents that. Pick one hook and put all the outbound rewriting in it; splitting the work across both is a configuration error, not a supported composition.
- Is the hook a good place to read the request body?No. It runs while the outbound request is being constructed, and consuming the body there leaves the upstream with nothing to read. Buffer and inspect the body in middleware in front of the proxy, and hand a fresh reader downstream.
saying these in an interview costs you the question
- Thinks Director receives the request the client actually sent
- Expects SetURL to preserve the client's Host header
- Assumes Rewrite forwards the inbound X-Forwarded-* headers automatically
- Sets Director and Rewrite together and expects both to run
- Reads or replaces the request body inside the hook