Skip to content

Commit 0973dfd

Browse files
committed
FEATURE: support Hijacker, CloseNotifier, Flusher; export Size() and Written()
Add support and tests for http's Hijacker, CloseNotifier, and Flusher interfaces in the ResponseWriter. Also, export some more methods on ResponseWriter: Size() and Written() which return how many bytes were written and whether the header was written yet.
1 parent 889502e commit 0973dfd

File tree

3 files changed

+155
-9
lines changed

3 files changed

+155
-9
lines changed

TODO

Lines changed: 0 additions & 4 deletions
This file was deleted.

response_writer.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,76 @@
11
package web
22

33
import (
4+
"bufio"
5+
"fmt"
6+
"net"
47
"net/http"
58
)
69

710
// ResponseWriter includes net/http's ResponseWriter and adds a StatusCode() method to obtain the written status code.
811
// A ResponseWriter is sent to handlers on each request.
912
type ResponseWriter interface {
1013
http.ResponseWriter
14+
http.Flusher
15+
http.Hijacker
16+
http.CloseNotifier
1117

1218
// StatusCode returns the written status code, or 0 if none has been written yet.
1319
StatusCode() int
20+
// Written returns whether the header has been written yet.
21+
Written() bool
22+
// Size returns the size in bytes of the body written so far.
23+
Size() int
1424
}
1525

1626
type appResponseWriter struct {
1727
http.ResponseWriter
1828
statusCode int
19-
written bool
29+
size int
2030
}
2131

2232
// Don't need this yet because we get it for free:
2333
func (w *appResponseWriter) Write(data []byte) (n int, err error) {
24-
if !w.written {
34+
if w.statusCode == 0 {
2535
w.statusCode = http.StatusOK
26-
w.written = true
2736
}
28-
return w.ResponseWriter.Write(data)
37+
size, err := w.ResponseWriter.Write(data)
38+
w.size += size
39+
return size, err
2940
}
3041

3142
func (w *appResponseWriter) WriteHeader(statusCode int) {
3243
w.statusCode = statusCode
33-
w.written = true
3444
w.ResponseWriter.WriteHeader(statusCode)
3545
}
3646

3747
func (w *appResponseWriter) StatusCode() int {
3848
return w.statusCode
3949
}
50+
51+
func (w *appResponseWriter) Written() bool {
52+
return w.statusCode != 0
53+
}
54+
55+
func (w *appResponseWriter) Size() int {
56+
return w.size
57+
}
58+
59+
func (w *appResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
60+
hijacker, ok := w.ResponseWriter.(http.Hijacker)
61+
if !ok {
62+
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
63+
}
64+
return hijacker.Hijack()
65+
}
66+
67+
func (w *appResponseWriter) CloseNotify() <-chan bool {
68+
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
69+
}
70+
71+
func (w *appResponseWriter) Flush() {
72+
flusher, ok := w.ResponseWriter.(http.Flusher)
73+
if ok {
74+
flusher.Flush()
75+
}
76+
}

response_writer_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package web
2+
3+
import (
4+
"bufio"
5+
"github.com/stretchr/testify/assert"
6+
"net"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
"time"
11+
)
12+
13+
type hijackableResponse struct {
14+
Hijacked bool
15+
}
16+
17+
func (h *hijackableResponse) Header() http.Header {
18+
return nil
19+
}
20+
func (h *hijackableResponse) Write(buf []byte) (int, error) {
21+
return 0, nil
22+
}
23+
func (h *hijackableResponse) WriteHeader(code int) {
24+
// no-op
25+
}
26+
func (h *hijackableResponse) Flush() {
27+
// no-op
28+
}
29+
func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) {
30+
h.Hijacked = true
31+
return nil, nil, nil
32+
}
33+
func (h *hijackableResponse) CloseNotify() <-chan bool {
34+
return nil
35+
}
36+
37+
type closeNotifyingRecorder struct {
38+
*httptest.ResponseRecorder
39+
closed chan bool
40+
}
41+
42+
func (c *closeNotifyingRecorder) close() {
43+
c.closed <- true
44+
}
45+
46+
func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
47+
return c.closed
48+
}
49+
50+
func TestResponseWriterWrite(t *testing.T) {
51+
rec := httptest.NewRecorder()
52+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: rec})
53+
54+
assert.Equal(t, rw.Written(), false)
55+
56+
n, err := rw.Write([]byte("Hello world"))
57+
assert.Equal(t, n, 11)
58+
assert.NoError(t, err)
59+
60+
assert.Equal(t, n, 11)
61+
assert.Equal(t, rec.Code, rw.StatusCode())
62+
assert.Equal(t, rec.Code, http.StatusOK)
63+
assert.Equal(t, rec.Body.String(), "Hello world")
64+
assert.Equal(t, rw.Size(), 11)
65+
assert.Equal(t, rw.Written(), true)
66+
}
67+
68+
func TestResponseWriterWriteHeader(t *testing.T) {
69+
rec := httptest.NewRecorder()
70+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: rec})
71+
72+
rw.WriteHeader(http.StatusNotFound)
73+
assert.Equal(t, rec.Code, rw.StatusCode())
74+
assert.Equal(t, rec.Code, http.StatusNotFound)
75+
}
76+
77+
func TestResponseWriterHijack(t *testing.T) {
78+
hijackable := &hijackableResponse{}
79+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: hijackable})
80+
hijacker, ok := rw.(http.Hijacker)
81+
assert.True(t, ok)
82+
_, _, err := hijacker.Hijack()
83+
assert.NoError(t, err)
84+
assert.True(t, hijackable.Hijacked)
85+
}
86+
87+
func TestResponseWriterHijackNotOK(t *testing.T) {
88+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: httptest.NewRecorder()})
89+
_, _, err := rw.Hijack()
90+
assert.Error(t, err)
91+
}
92+
93+
func TestResponseWriterFlush(t *testing.T) {
94+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: httptest.NewRecorder()})
95+
rw.Flush()
96+
}
97+
98+
func TestResponseWriterCloseNotify(t *testing.T) {
99+
rec := &closeNotifyingRecorder{
100+
httptest.NewRecorder(),
101+
make(chan bool, 1),
102+
}
103+
rw := ResponseWriter(&appResponseWriter{ResponseWriter: rec})
104+
closed := false
105+
notifier := rw.(http.CloseNotifier).CloseNotify()
106+
rec.close()
107+
select {
108+
case <-notifier:
109+
closed = true
110+
case <-time.After(time.Second):
111+
}
112+
assert.True(t, closed)
113+
}

0 commit comments

Comments
 (0)