diff --git a/response_writer.go b/response_writer.go index 9035e6f1fd..8e2d8b3099 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 dfc1d2c60d..83d3fc8b06 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() }