Skip to content

Commit d453eff

Browse files
author
Muir Manders
authored
Fix compression of *os.Files. (#197)
After using httpsnoop to preserve interfaces, the compress response writer now implemented ReaderFrom. ReaderFrom is used by net/http to use sendfile when serving *os.Files. This breaks compression because it serves directly to the underlying response writer, skipping the compressor. Fix by implementing ReadFrom on our resposne writer to copy to our compressor. Fixes #194.
1 parent d6944a4 commit d453eff

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

compress.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func (cw *compressResponseWriter) Write(b []byte) (int, error) {
3636
return cw.compressor.Write(b)
3737
}
3838

39+
func (cw *compressResponseWriter) ReadFrom(r io.Reader) (int64, error) {
40+
return io.Copy(cw.compressor, r)
41+
}
42+
3943
type flusher interface {
4044
Flush() error
4145
}
@@ -129,6 +133,9 @@ func CompressHandlerLevel(h http.Handler, level int) http.Handler {
129133
Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc {
130134
return cw.Flush
131135
},
136+
ReadFrom: func(rff httpsnoop.ReadFromFunc) httpsnoop.ReadFromFunc {
137+
return cw.ReadFrom
138+
},
132139
})
133140

134141
h.ServeHTTP(w, r)

compress_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ package handlers
66

77
import (
88
"bufio"
9+
"bytes"
10+
"compress/gzip"
911
"io"
12+
"io/ioutil"
1013
"net"
1114
"net/http"
1215
"net/http/httptest"
16+
"net/url"
17+
"os"
18+
"path/filepath"
1319
"strconv"
1420
"testing"
1521
)
@@ -158,6 +164,54 @@ func TestCompressHandlerGzipDeflate(t *testing.T) {
158164
}
159165
}
160166

167+
// Make sure we can compress and serve an *os.File properly. We need
168+
// to use a real http server to trigger the net/http sendfile special
169+
// case.
170+
func TestCompressFile(t *testing.T) {
171+
dir, err := ioutil.TempDir("", "gorilla_compress")
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
defer os.RemoveAll(dir)
176+
177+
err = ioutil.WriteFile(filepath.Join(dir, "hello.txt"), []byte("hello"), 0644)
178+
if err != nil {
179+
t.Fatal(err)
180+
}
181+
182+
s := httptest.NewServer(CompressHandler(http.FileServer(http.Dir(dir))))
183+
defer s.Close()
184+
185+
url := &url.URL{Scheme: "http", Host: s.Listener.Addr().String(), Path: "/hello.txt"}
186+
req, err := http.NewRequest("GET", url.String(), nil)
187+
if err != nil {
188+
t.Fatal(err)
189+
}
190+
req.Header.Set(acceptEncoding, "gzip")
191+
res, err := http.DefaultClient.Do(req)
192+
if err != nil {
193+
t.Fatal(err)
194+
}
195+
196+
if res.StatusCode != http.StatusOK {
197+
t.Fatalf("expected OK, got %q", res.Status)
198+
}
199+
200+
var got bytes.Buffer
201+
gr, err := gzip.NewReader(res.Body)
202+
if err != nil {
203+
t.Fatal(err)
204+
}
205+
_, err = io.Copy(&got, gr)
206+
if err != nil {
207+
t.Fatal(err)
208+
}
209+
210+
if got.String() != "hello" {
211+
t.Errorf("expected hello, got %q", got.String())
212+
}
213+
}
214+
161215
type fullyFeaturedResponseWriter struct{}
162216

163217
// Header/Write/WriteHeader implement the http.ResponseWriter interface.

0 commit comments

Comments
 (0)