Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 16:45 UTC
reverseproxy: Fix double headers in response handlers (#4847)
Francis Lavoie committed
Jun 22, 2022 at 15:10 UTC
98468af8b6224d29431576fe30a7d92a8676030d
5 files changed
+34
-138
caddytest/integration/caddyfile_adapt/forward_auth_authelia.txt
-26
@@ -63,32 +63,6 @@ app.example.com {
63
]
64
}
65
]
66
- },
67
- {
68
- "routes": [
69
- {
70
- "handle": [
71
- {
72
- "exclude": [
73
- "Connection",
74
- "Keep-Alive",
75
- "Te",
76
- "Trailers",
77
- "Transfer-Encoding",
78
- "Upgrade"
79
- ],
80
- "handler": "copy_response_headers"
81
- }
82
- ]
83
- },
84
- {
85
- "handle": [
86
- {
87
- "handler": "copy_response"
88
- }
89
- ]
90
- }
91
- ]
66
}
67
],
68
"handler": "reverse_proxy",
caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.txt
-26
@@ -55,32 +55,6 @@ forward_auth localhost:9000 {
55
]
56
}
57
]
58
- },
59
- {
60
- "routes": [
61
- {
62
- "handle": [
63
- {
64
- "exclude": [
65
- "Connection",
66
- "Keep-Alive",
67
- "Te",
68
- "Trailers",
69
- "Transfer-Encoding",
70
- "Upgrade"
71
- ],
72
- "handler": "copy_response_headers"
73
- }
74
- ]
75
- },
76
- {
77
- "handle": [
78
- {
79
- "handler": "copy_response"
80
- }
81
- ]
82
- }
83
- ]
58
}
59
],
60
"handler": "reverse_proxy",
modules/caddyhttp/reverseproxy/copyresponse.go
+1
-1
@@ -80,7 +80,7 @@ func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request
80
hrc.isFinalized = true
81
82
// write the response
83
- return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger, false)
83
+ return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger)
84
}
85
86
// CopyResponseHeadersHandler is a special HTTP handler which may
modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+6
-41
@@ -59,13 +59,6 @@ func init() {
59
// Remote-Email {http.reverse_proxy.header.Remote-Email}
60
// }
61
// }
62
-//
63
-// handle_response {
64
-// copy_response_headers {
65
-// exclude Connection Keep-Alive Te Trailers Transfer-Encoding Upgrade
66
-// }
67
-// copy_response
68
-// }
62
// }
63
//
64
func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
@@ -217,41 +210,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
210
},
211
)
212
}
220
- rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
213
222
- // set up handler for denial responses; when a response
223
- // has any other status than 2xx, then we copy the response
224
- // back to the client, and terminate handling.
225
- denialResponseHandler := caddyhttp.ResponseHandler{
226
- Routes: []caddyhttp.Route{
227
- {
228
- HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
229
- &reverseproxy.CopyResponseHeadersHandler{
230
- Exclude: []string{
231
- "Connection",
232
- "Keep-Alive",
233
- "Te",
234
- "Trailers",
235
- "Transfer-Encoding",
236
- "Upgrade",
237
- },
238
- },
239
- "handler",
240
- "copy_response_headers",
241
- nil,
242
- )},
243
- },
244
- {
245
- HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
246
- &reverseproxy.CopyResponseHandler{},
247
- "handler",
248
- "copy_response",
249
- nil,
250
- )},
251
- },
252
- },
253
- }
254
- rpHandler.HandleResponse = append(rpHandler.HandleResponse, denialResponseHandler)
214
+ // note that when a response has any other status than 2xx, then we
215
+ // use the reverse proxy's default behaviour of copying the response
216
+ // back to the client, so we don't need to explicitly add a response
217
+ // handler specifically for that behaviour; we do need the 2xx handler
218
+ // though, to make handling fall through to handlers deeper in the chain.
219
+ rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
220
221
// the rest of the config is specified by the user
222
// using the reverse_proxy directive syntax
modules/caddyhttp/reverseproxy/reverseproxy.go
+27
-44
@@ -784,18 +784,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
784
res.Body = h.bufferedBody(res.Body)
785
}
786
787
- // the response body may get closed by a response handler,
788
- // and we need to keep track to make sure we don't try to copy
789
- // the response if it was already closed
790
- bodyClosed := false
791
-
787
// see if any response handler is configured for this response from the backend
788
for i, rh := range h.HandleResponse {
789
if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
790
continue
791
}
792
798
- // if configured to only change the status code, do that then continue regular proxy response
793
+ // if configured to only change the status code,
794
+ // do that then continue regular proxy response
795
if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
796
statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
797
if err != nil {
@@ -840,33 +836,29 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
836
// pass the request through the response handler routes
837
routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx))
838
843
- // if the response handler routes already finalized the response,
844
- // we can return early. It should be finalized if the routes executed
845
- // included a copy_response handler. If a fresh response was written
846
- // by the routes instead, then we still need to finalize the response
847
- // without copying the body.
848
- if routeErr == nil && hrc.isFinalized {
849
- return nil
839
+ // close the response body afterwards, since we don't need it anymore;
840
+ // either a route had 'copy_response' which already consumed the body,
841
+ // or some other terminal handler ran which doesn't need the response
842
+ // body after that point (e.g. 'file_server' for X-Accel-Redirect flow),
843
+ // or we fell through to subsequent handlers past this proxy
844
+ // (e.g. forward auth's 2xx response flow).
845
+ if !hrc.isFinalized {
846
+ res.Body.Close()
847
}
848
852
- // always close the response body afterwards, since it's expected
853
- // that the response handler routes will have written to the
854
- // response writer with a new body, if it wasn't already finalized.
855
- res.Body.Close()
856
- bodyClosed = true
857
-
849
+ // wrap any route error in roundtripSucceeded so caller knows that
850
+ // the roundtrip was successful and to not retry
851
if routeErr != nil {
859
- // wrap error in roundtripSucceeded so caller knows that
860
- // the roundtrip was successful and to not retry
852
return roundtripSucceeded{routeErr}
853
}
854
864
- // we've already closed the body, so there's no use allowing
865
- // another response handler to run as well
866
- break
855
+ // we're done handling the response, and we don't want to
856
+ // fall through to the default finalize/copy behaviour
857
+ return nil
858
}
859
869
- return h.finalizeResponse(rw, req, res, repl, start, logger, bodyClosed)
860
+ // copy the response body and headers back to the upstream client
861
+ return h.finalizeResponse(rw, req, res, repl, start, logger)
862
}
863
864
// finalizeResponse prepares and copies the response.
@@ -877,7 +869,6 @@ func (h Handler) finalizeResponse(
869
repl *caddy.Replacer,
870
start time.Time,
871
logger *zap.Logger,
880
- bodyClosed bool,
872
) error {
873
// deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
874
if res.StatusCode == http.StatusSwitchingProtocols {
@@ -891,13 +882,6 @@ func (h Handler) finalizeResponse(
882
res.Header.Del(h)
883
}
884
894
- // remove the content length if we're not going to be copying
895
- // from the response, because otherwise there'll be a mismatch
896
- // between bytes written and the advertised length
897
- if bodyClosed {
898
- res.Header.Del("Content-Length")
899
- }
900
-
885
// apply any response header operations
886
if h.Headers != nil && h.Headers.Response != nil {
887
if h.Headers.Response.Require == nil ||
@@ -920,17 +904,16 @@ func (h Handler) finalizeResponse(
904
}
905
906
rw.WriteHeader(res.StatusCode)
923
- if !bodyClosed {
924
- err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
925
- res.Body.Close() // close now, instead of defer, to populate res.Trailer
926
- if err != nil {
927
- // we're streaming the response and we've already written headers, so
928
- // there's nothing an error handler can do to recover at this point;
929
- // the standard lib's proxy panics at this point, but we'll just log
930
- // the error and abort the stream here
931
- h.logger.Error("aborting with incomplete response", zap.Error(err))
932
- return nil
933
- }
907
+
908
+ err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
909
+ res.Body.Close() // close now, instead of defer, to populate res.Trailer
910
+ if err != nil {
911
+ // we're streaming the response and we've already written headers, so
912
+ // there's nothing an error handler can do to recover at this point;
913
+ // the standard lib's proxy panics at this point, but we'll just log
914
+ // the error and abort the stream here
915
+ h.logger.Error("aborting with incomplete response", zap.Error(err))
916
+ return nil
917
}
918
919
if len(res.Trailer) > 0 {