Skip to content

Commit d6cb58b

Browse files
committed
Response should implement http.Flusher and http.Hijacker to work with older libraries that do not use http.NewResponseController()
1 parent ec92328 commit d6cb58b

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

response.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
package echo
55

66
import (
7+
"bufio"
78
"errors"
9+
"fmt"
810
"log/slog"
11+
"net"
912
"net/http"
1013
)
1114

@@ -72,6 +75,30 @@ func (r *Response) Write(b []byte) (n int, err error) {
7275
return
7376
}
7477

78+
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
79+
// buffered data to the client.
80+
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
81+
func (r *Response) Flush() {
82+
err := http.NewResponseController(r.ResponseWriter).Flush()
83+
if err != nil && errors.Is(err, http.ErrNotSupported) {
84+
panic(fmt.Errorf("echo: response writer %T does not support flushing (http.Flusher interface)", r.ResponseWriter))
85+
}
86+
}
87+
88+
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
89+
// take over the connection.
90+
// This method is relevant to Websocket connection upgrades, proxis, and other advanced use cases.
91+
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
92+
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
93+
// newer code should do response hijacking like that
94+
// http.NewResponseController(responseWriter).Hijack()
95+
//
96+
// but there are older libraries that are not aware of `http.NewResponseController` and try to hijack directly
97+
// `hj, ok := resp.(http.Hijacker)` <-- which would fail without Response directly implementing Hijack method
98+
// so for that purpose we need to implement http.Hijacker interface
99+
return http.NewResponseController(r.ResponseWriter).Hijack()
100+
}
101+
75102
// Unwrap returns the original http.ResponseWriter.
76103
// ResponseController can be used to access the original http.ResponseWriter.
77104
// See [https://go.dev/blog/go1.20]

response_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,45 @@ func TestResponse_Unwrap(t *testing.T) {
7373

7474
assert.Equal(t, rec, res.Unwrap())
7575
}
76+
77+
func TestResponse_isHijacker(t *testing.T) {
78+
var resp http.ResponseWriter = &Response{}
79+
80+
_, ok := resp.(http.Hijacker)
81+
assert.True(t, ok)
82+
}
83+
84+
func TestResponse_Flush(t *testing.T) {
85+
e := New()
86+
rec := httptest.NewRecorder()
87+
res := NewResponse(rec, e.Logger)
88+
89+
res.Write([]byte("test"))
90+
res.Flush()
91+
assert.True(t, rec.Flushed)
92+
}
93+
94+
type testResponseWriter struct {
95+
}
96+
97+
func (w *testResponseWriter) WriteHeader(statusCode int) {
98+
}
99+
100+
func (w *testResponseWriter) Write([]byte) (int, error) {
101+
return 0, nil
102+
}
103+
104+
func (w *testResponseWriter) Header() http.Header {
105+
return nil
106+
}
107+
108+
func TestResponse_FlushPanics(t *testing.T) {
109+
e := New()
110+
rw := new(testResponseWriter)
111+
res := NewResponse(rw, e.Logger)
112+
113+
// we test that we behave as before unwrapping flushers - flushing writer that does not support it causes panic
114+
assert.PanicsWithError(t, "echo: response writer *echo.testResponseWriter does not support flushing (http.Flusher interface)", func() {
115+
res.Flush()
116+
})
117+
}

0 commit comments

Comments
 (0)