Browse Source

caddyhttp: Fix trailers when recording responses (fixes #3236)

master
Matthew Holt 5 years ago
parent
commit
026937fab5
No known key found for this signature in database GPG Key ID: 2A349DD577D586A5
  1. 13
      modules/caddyhttp/caddyhttp.go
  2. 37
      modules/caddyhttp/responsewriter.go

13
modules/caddyhttp/caddyhttp.go

@ -165,19 +165,6 @@ func (ws WeakString) String() string {
return string(ws) return string(ws)
} }
// CopyHeader copies HTTP headers by completely
// replacing dest with src. (This allows deletions
// to be propagated, assuming src started as a
// consistent copy of dest.)
func CopyHeader(dest, src http.Header) {
for field := range dest {
delete(dest, field)
}
for field, val := range src {
dest[field] = val
}
}
// StatusCodeMatches returns true if a real HTTP status code matches // StatusCodeMatches returns true if a real HTTP status code matches
// the configured status code, which may be either a real HTTP status // the configured status code, which may be either a real HTTP status
// code or an integer representing a class of codes (e.g. 4 for all // code or an integer representing a class of codes (e.g. 4 for all

37
modules/caddyhttp/responsewriter.go

@ -80,7 +80,6 @@ type responseRecorder struct {
buf *bytes.Buffer buf *bytes.Buffer
shouldBuffer ShouldBufferFunc shouldBuffer ShouldBufferFunc
size int size int
header http.Header
wroteHeader bool wroteHeader bool
stream bool stream bool
} }
@ -122,46 +121,34 @@ type responseRecorder struct {
// } // }
// // process the buffered response here // // process the buffered response here
// //
// After a response has been buffered, remember that any upstream header // The header map is not buffered; i.e. the ResponseRecorder's Header()
// manipulations are only manifest in the recorder's Header(), not the // method returns the same header map of the underlying ResponseWriter.
// Header() of the underlying ResponseWriter. Thus if you wish to inspect // This is a crucial design decision to allow HTTP trailers to be
// or change response headers, you either need to use rec.Header(), or // flushed properly (https://github.com/caddyserver/caddy/issues/3236).
// copy rec.Header() into w.Header() first (see caddyhttp.CopyHeader).
// //
// Once you are ready to write the response, there are two ways you can do // Once you are ready to write the response, there are two ways you can
// it. The easier way is to have the recorder do it: // do it. The easier way is to have the recorder do it:
// //
// rec.WriteResponse() // rec.WriteResponse()
// //
// This writes the recorded response headers as well as the buffered body. // This writes the recorded response headers as well as the buffered body.
// Or, you may wish to do it yourself, especially if you manipulated the // Or, you may wish to do it yourself, especially if you manipulated the
// buffered body. First you will need to copy the recorded headers, then // buffered body. First you will need to write the headers with the
// write the headers with the recorded status code, then write the body // recorded status code, then write the body (this example writes the
// (this example writes the recorder's body buffer, but you might have // recorder's body buffer, but you might have your own body to write
// your own body to write instead): // instead):
// //
// caddyhttp.CopyHeader(w.Header(), rec.Header())
// w.WriteHeader(rec.Status()) // w.WriteHeader(rec.Status())
// io.Copy(w, rec.Buffer()) // io.Copy(w, rec.Buffer())
// //
func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder { func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shouldBuffer ShouldBufferFunc) ResponseRecorder {
// copy the current response header into this buffer so
// that any header manipulations on the buffered header
// are consistent with what would be written out
hdr := make(http.Header)
CopyHeader(hdr, w.Header())
return &responseRecorder{ return &responseRecorder{
ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w}, ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
buf: buf, buf: buf,
shouldBuffer: shouldBuffer, shouldBuffer: shouldBuffer,
header: hdr,
} }
} }
func (rr *responseRecorder) Header() http.Header {
return rr.header
}
func (rr *responseRecorder) WriteHeader(statusCode int) { func (rr *responseRecorder) WriteHeader(statusCode int) {
if rr.wroteHeader { if rr.wroteHeader {
return return
@ -173,12 +160,11 @@ func (rr *responseRecorder) WriteHeader(statusCode int) {
if rr.shouldBuffer == nil { if rr.shouldBuffer == nil {
rr.stream = true rr.stream = true
} else { } else {
rr.stream = !rr.shouldBuffer(rr.statusCode, rr.header) rr.stream = !rr.shouldBuffer(rr.statusCode, rr.ResponseWriterWrapper.Header())
} }
// if not buffered, immediately write header // if not buffered, immediately write header
if rr.stream { if rr.stream {
CopyHeader(rr.ResponseWriterWrapper.Header(), rr.header)
rr.ResponseWriterWrapper.WriteHeader(rr.statusCode) rr.ResponseWriterWrapper.WriteHeader(rr.statusCode)
} }
} }
@ -224,7 +210,6 @@ func (rr *responseRecorder) WriteResponse() error {
if rr.stream { if rr.stream {
return nil return nil
} }
CopyHeader(rr.ResponseWriterWrapper.Header(), rr.header)
if rr.statusCode == 0 { if rr.statusCode == 0 {
// could happen if no handlers actually wrote anything, // could happen if no handlers actually wrote anything,
// and this prevents a panic; status must be > 0 // and this prevents a panic; status must be > 0

Loading…
Cancel
Save