Skip to content

Commit 8171cd3

Browse files
committed
Merge pull request #66 from wathiede/flusher
[feature] http.Flusher/http.CloseNotifier support for CompressHandler.
2 parents ab2276f + 0838180 commit 8171cd3

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

compress.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type compressResponseWriter struct {
1616
io.Writer
1717
http.ResponseWriter
1818
http.Hijacker
19+
http.Flusher
20+
http.CloseNotifier
1921
}
2022

2123
func (w *compressResponseWriter) WriteHeader(c int) {
@@ -37,6 +39,21 @@ func (w *compressResponseWriter) Write(b []byte) (int, error) {
3739
return w.Writer.Write(b)
3840
}
3941

42+
type flusher interface {
43+
Flush() error
44+
}
45+
46+
func (w *compressResponseWriter) Flush() {
47+
// Flush compressed data if compressor supports it.
48+
if f, ok := w.Writer.(flusher); ok {
49+
f.Flush()
50+
}
51+
// Flush HTTP response.
52+
if w.Flusher != nil {
53+
w.Flusher.Flush()
54+
}
55+
}
56+
4057
// CompressHandler gzip compresses HTTP responses for clients that support it
4158
// via the 'Accept-Encoding' header.
4259
func CompressHandler(h http.Handler) http.Handler {
@@ -70,10 +87,22 @@ func CompressHandlerLevel(h http.Handler, level int) http.Handler {
7087
h = nil
7188
}
7289

90+
f, fok := w.(http.Flusher)
91+
if !fok {
92+
f = nil
93+
}
94+
95+
cn, cnok := w.(http.CloseNotifier)
96+
if !cnok {
97+
cn = nil
98+
}
99+
73100
w = &compressResponseWriter{
74101
Writer: gw,
75102
ResponseWriter: w,
76103
Hijacker: h,
104+
Flusher: f,
105+
CloseNotifier: cn,
77106
}
78107

79108
break L
@@ -89,10 +118,22 @@ func CompressHandlerLevel(h http.Handler, level int) http.Handler {
89118
h = nil
90119
}
91120

121+
f, fok := w.(http.Flusher)
122+
if !fok {
123+
f = nil
124+
}
125+
126+
cn, cnok := w.(http.CloseNotifier)
127+
if !cnok {
128+
cn = nil
129+
}
130+
92131
w = &compressResponseWriter{
93132
Writer: fw,
94133
ResponseWriter: w,
95134
Hijacker: h,
135+
Flusher: f,
136+
CloseNotifier: cn,
96137
}
97138

98139
break L

compress_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package handlers
66

77
import (
8+
"bufio"
89
"io"
10+
"net"
911
"net/http"
1012
"net/http/httptest"
1113
"strconv"
@@ -88,3 +90,65 @@ func TestCompressHandlerGzipDeflate(t *testing.T) {
8890
t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
8991
}
9092
}
93+
94+
type fullyFeaturedResponseWriter struct{}
95+
96+
// Header/Write/WriteHeader implement the http.ResponseWriter interface.
97+
func (fullyFeaturedResponseWriter) Header() http.Header {
98+
return http.Header{}
99+
}
100+
func (fullyFeaturedResponseWriter) Write([]byte) (int, error) {
101+
return 0, nil
102+
}
103+
func (fullyFeaturedResponseWriter) WriteHeader(int) {}
104+
105+
// Flush implements the http.Flusher interface.
106+
func (fullyFeaturedResponseWriter) Flush() {}
107+
108+
// Hijack implements the http.Hijacker interface.
109+
func (fullyFeaturedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
110+
return nil, nil, nil
111+
}
112+
113+
// CloseNotify implements the http.CloseNotifier interface.
114+
func (fullyFeaturedResponseWriter) CloseNotify() <-chan bool {
115+
return nil
116+
}
117+
118+
func TestCompressHandlerPreserveInterfaces(t *testing.T) {
119+
// Compile time validation fullyFeaturedResponseWriter implements all the
120+
// interfaces we're asserting in the test case below.
121+
var (
122+
_ http.Flusher = fullyFeaturedResponseWriter{}
123+
_ http.CloseNotifier = fullyFeaturedResponseWriter{}
124+
_ http.Hijacker = fullyFeaturedResponseWriter{}
125+
)
126+
var h http.Handler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
127+
comp := r.Header.Get("Accept-Encoding")
128+
if _, ok := rw.(*compressResponseWriter); !ok {
129+
t.Fatalf("ResponseWriter wasn't wrapped by compressResponseWriter, got %T type", rw)
130+
}
131+
if _, ok := rw.(http.Flusher); !ok {
132+
t.Errorf("ResponseWriter lost http.Flusher interface for %q", comp)
133+
}
134+
if _, ok := rw.(http.CloseNotifier); !ok {
135+
t.Errorf("ResponseWriter lost http.CloseNotifier interface for %q", comp)
136+
}
137+
if _, ok := rw.(http.Hijacker); !ok {
138+
t.Errorf("ResponseWriter lost http.Hijacker interface for %q", comp)
139+
}
140+
})
141+
h = CompressHandler(h)
142+
var (
143+
rw fullyFeaturedResponseWriter
144+
)
145+
r, err := http.NewRequest("GET", "/", nil)
146+
if err != nil {
147+
t.Fatalf("Failed to create test request: %v", err)
148+
}
149+
r.Header.Set("Accept-Encoding", "gzip")
150+
h.ServeHTTP(rw, r)
151+
152+
r.Header.Set("Accept-Encoding", "deflate")
153+
h.ServeHTTP(rw, r)
154+
}

0 commit comments

Comments
 (0)