Skip to content

Commit c75197e

Browse files
committed
fix: preserve final responses after informational headers
1 parent 9ce228d commit c75197e

3 files changed

Lines changed: 415 additions & 5 deletions

File tree

‎middleware/compress.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type gzipResponseWriter struct {
5656
}
5757

5858
// Gzip returns a middleware which compresses HTTP response using gzip compression scheme.
59+
// Informational responses are sent immediately without compression.
5960
func Gzip() echo.MiddlewareFunc {
6061
return GzipWithConfig(GzipConfig{})
6162
}
@@ -145,6 +146,12 @@ func (config GzipConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
145146
}
146147

147148
func (w *gzipResponseWriter) WriteHeader(code int) {
149+
if code >= 100 && code < 200 && code != http.StatusSwitchingProtocols {
150+
if !w.wroteHeader && !w.wroteBody {
151+
w.ResponseWriter.WriteHeader(code)
152+
}
153+
return
154+
}
148155
w.Header().Del(echo.HeaderContentLength) // Issue #444
149156

150157
w.wroteHeader = true

‎response.go‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
type Response struct {
2020
http.ResponseWriter
2121
logger *slog.Logger
22-
// beforeFuncs are functions that are called just before the response (status) is written. Happens only once, during WriteHeader call.
22+
// beforeFuncs are functions that are called just before the final response (status) is written. Happens only once, during WriteHeader call.
2323
beforeFuncs []func()
2424
// afterFuncs are functions that are called just after the response is written. During every `Write` method call.
2525
afterFuncs []func()
@@ -33,7 +33,7 @@ func NewResponse(w http.ResponseWriter, logger *slog.Logger) (r *Response) {
3333
return &Response{ResponseWriter: w, logger: logger}
3434
}
3535

36-
// Before registers a function which is called just before the response (status) is written.
36+
// Before registers a function which is called just before the final response (status) is written.
3737
func (r *Response) Before(fn func()) {
3838
r.beforeFuncs = append(r.beforeFuncs, fn)
3939
}
@@ -46,12 +46,18 @@ func (r *Response) After(fn func()) {
4646
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
4747
// not called explicitly, the first call to Write will trigger an implicit
4848
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
49-
// used to send error codes.
49+
// used to send error codes or informational responses.
50+
// Informational responses (100-199, except 101 Switching Protocols) are sent
51+
// immediately without changing Status or Committed or calling Before functions.
5052
func (r *Response) WriteHeader(code int) {
5153
if r.Committed {
5254
r.logger.Error("echo: response already written to client")
5355
return
5456
}
57+
if code >= 100 && code < 200 && code != http.StatusSwitchingProtocols {
58+
r.ResponseWriter.WriteHeader(code)
59+
return
60+
}
5561
r.Status = code
5662
for _, fn := range r.beforeFuncs {
5763
fn()
@@ -141,6 +147,10 @@ type delayedStatusWriter struct {
141147
}
142148

143149
func (w *delayedStatusWriter) WriteHeader(statusCode int) {
150+
if statusCode >= 100 && statusCode < 200 && statusCode != http.StatusSwitchingProtocols {
151+
w.ResponseWriter.WriteHeader(statusCode)
152+
return
153+
}
144154
// in case something else writes status code explicitly before us we need mark response committed
145155
w.committed = true
146156
w.ResponseWriter.WriteHeader(statusCode)
@@ -175,10 +185,10 @@ func (w *delayedStatusWriter) Unwrap() http.ResponseWriter {
175185
// headResponseWriter captures the response that a GET handler would produce for a
176186
// rewritten HEAD request, suppresses the body, and preserves response metadata.
177187
//
178-
// The writer buffers status until the downstream handler returns, so it
188+
// The writer buffers the final status until the downstream handler returns, so it
179189
// can compute a Content-Length value from the number of body bytes that would have
180190
// been written by the GET handler. If the handler already sets Content-Length
181-
// explicitly, that value is preserved.
191+
// explicitly, that value is preserved. Informational responses are forwarded immediately.
182192
//
183193
// Flush is intentionally a no-op because emitting headers early would prevent
184194
// finalizing Content-Length after the handler completes.
@@ -197,6 +207,10 @@ func (w *headResponseWriter) WriteHeader(code int) {
197207
if w.wroteStatus {
198208
return
199209
}
210+
if code >= 100 && code < 200 && code != http.StatusSwitchingProtocols {
211+
w.rw.WriteHeader(code)
212+
return
213+
}
200214
w.wroteStatus = true
201215
w.status = code
202216
if r, err := UnwrapResponse(w.rw); err == nil {

0 commit comments

Comments
 (0)