`intercept` causing requests to be handled twice
Update: see this comment for the effect of this bug.
As the subject says, using the intercept handler with reverse_proxy causes duplication of headers received from upstream.
Using this Caddyfile:
localhost {
log
intercept {
handle_response {
header {
Header-Three Value-3
}
}
}
reverse_proxy localhost:8080
}
http://localhost:8080/ {
header {
Header-One Value-1
Header-Two Value-2
}
respond "Hello, World!"
}
Note the headers received by the client:
$ curl -v https://localhost/
* Request completely sent off
< HTTP/2 200
< alt-svc: h3=":443"; ma=2592000
< content-type: text/plain; charset=utf-8
< content-type: text/plain; charset=utf-8
< date: Mon, 28 Apr 2025 21:15:40 GMT
< date: Mon, 28 Apr 2025 21:15:40 GMT
< header-one: Value-1
< header-one: Value-1
< header-three: Value-3
< header-two: Value-2
< header-two: Value-2
< server: Caddy
< via: 1.1 Caddy
< via: 1.1 Caddy
< content-length: 13
<
* Connection #0 to host localhost left intact
Hello, World!%
The workaround for reverse_proxy is using handle_response.
I'm new to this repo, can I work on this?
Sure, go for it.
It's not just reverse_proxy, technically, all the headers that are added in the middleware will be duplicated. Headers that are set will not, can be tested by changing the reverse_proxy to file_server and request a file that can be compressed. Vary: Accept-Encoding will be duplicated.
This is because the intercepted response will be executed twice, once when intercepting the response, a second time when handling the response.
func TestName(t *testing.T) {
http.HandleFunc("/exit", func(writer http.ResponseWriter, request *http.Request) {
os.Exit(1)
})
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
header := writer.Header()
header.Set("User-Agent", "Test")
header.Add("User-Agent", "Test")
header.Add("User-Agent", "Test")
t.Log("new request")
})
panic(http.ListenAndServe("127.0.0.1:5002", nil))
}
Using this simple backend, new request will appear twice for each request. And the User-Agent will appear 6 times in the response.
This is unexpected, from the caddy docs,
This directive allows you to match responses, and the first matching handle_response route or replace_status will be invoked. When invoked, the original response body is held back, giving the opportunity to that route to write a different response body, with a new status code or with any necessary response header manipulations. If the route does not write a new response body, then original response body is written instead.
The original response body is not written, it's the new response body from the second request. This behavior is buggy for requests that's not idempotent.