From d75fcd4c9ab260e5225de590f1f0f8c0e0e12d11 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Tue, 2 Jun 2026 07:02:14 -0700 Subject: [PATCH] fix(response): panic on Hijack/CloseNotify when wrapper unsupported (#4645) * response_writer: don't panic on Hijack/CloseNotify when wrapper unsupported Closes #4638. http.TimeoutHandler's writer doesn't implement http.Hijacker/CloseNotifier; mirror Flush's graceful degradation. * response_writer: keep Written() false when Hijack is unsupported Signed-off-by: Sai Asish Y --------- Signed-off-by: Sai Asish Y Co-authored-by: Bo-Yi Wu --- response_writer.go | 11 +++++++++-- response_writer_test.go | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/response_writer.go b/response_writer.go index 9035e6f1..8e2d8b30 100644 --- a/response_writer.go +++ b/response_writer.go @@ -114,15 +114,22 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size > 0 { return nil, nil, errHijackAlreadyWritten } + hijacker, ok := w.ResponseWriter.(http.Hijacker) + if !ok { + return nil, nil, http.ErrNotSupported + } if w.size < 0 { w.size = 0 } - return w.ResponseWriter.(http.Hijacker).Hijack() + return hijacker.Hijack() } // CloseNotify implements the http.CloseNotifier interface. func (w *responseWriter) CloseNotify() <-chan bool { - return w.ResponseWriter.(http.CloseNotifier).CloseNotify() + if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { + return cn.CloseNotify() + } + return nil } // Flush implements the http.Flusher interface. diff --git a/response_writer_test.go b/response_writer_test.go index dfc1d2c6..83d3fc8b 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,15 +113,18 @@ func TestResponseWriterHijack(t *testing.T) { writer.reset(testWriter) w := ResponseWriter(writer) - assert.Panics(t, func() { - _, _, err := w.Hijack() - require.NoError(t, err) - }) - assert.True(t, w.Written()) + // httptest.ResponseRecorder doesn't implement http.Hijacker; return + // http.ErrNotSupported instead of panicking (#4638). On unsupported the + // writer state stays untouched so the handler can still emit a normal + // HTTP response as a fallback. + conn, buf, err := w.Hijack() + assert.Nil(t, conn) + assert.Nil(t, buf) + require.ErrorIs(t, err, http.ErrNotSupported) + assert.False(t, w.Written()) - assert.Panics(t, func() { - w.CloseNotify() - }) + // CloseNotify on a non-CloseNotifier returns nil instead of panicking. + assert.Nil(t, w.CloseNotify()) w.Flush() }