Commit f92a3aa0 authored by Jeffrey Zhao's avatar Jeffrey Zhao Committed by Matt Holt

gzip: avoid unnecessary allocations when not compressing (#2396)

parent 917534e3
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package gzip package gzip
import ( import (
"compress/gzip"
"io" "io"
"net/http" "net/http"
"strings" "strings"
...@@ -65,21 +66,31 @@ outer: ...@@ -65,21 +66,31 @@ outer:
} }
} }
// In order to avoid unused memory allocation, gzip.putWriter only be called when gzip compression happened.
// see https://github.com/mholt/caddy/issues/2395
gz := &gzipResponseWriter{
ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w},
newWriter: func() io.Writer {
// gzipWriter modifies underlying writer at init, // gzipWriter modifies underlying writer at init,
// use a discard writer instead to leave ResponseWriter in // use a discard writer instead to leave ResponseWriter in
// original form. // original form.
gzipWriter := getWriter(c.Level) return getWriter(c.Level)
defer putWriter(c.Level, gzipWriter) },
gz := &gzipResponseWriter{ }
Writer: gzipWriter,
ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w}, defer func() {
if gzWriter, ok := gz.internalWriter.(*gzip.Writer); ok {
putWriter(c.Level, gzWriter)
} }
}()
var rw http.ResponseWriter var rw http.ResponseWriter
// if no response filter is used // if no response filter is used
if len(c.ResponseFilters) == 0 { if len(c.ResponseFilters) == 0 {
// replace discard writer with ResponseWriter // replace discard writer with ResponseWriter
gzipWriter.Reset(w) if gzWriter, ok := gz.Writer().(*gzip.Writer); ok {
gzWriter.Reset(w)
}
rw = gz rw = gz
} else { } else {
// wrap gzip writer with ResponseFilterWriter // wrap gzip writer with ResponseFilterWriter
...@@ -106,9 +117,10 @@ outer: ...@@ -106,9 +117,10 @@ outer:
// gzipResponeWriter wraps the underlying Write method // gzipResponeWriter wraps the underlying Write method
// with a gzip.Writer to compress the output. // with a gzip.Writer to compress the output.
type gzipResponseWriter struct { type gzipResponseWriter struct {
io.Writer internalWriter io.Writer
*httpserver.ResponseWriterWrapper *httpserver.ResponseWriterWrapper
statusCodeWritten bool statusCodeWritten bool
newWriter func() io.Writer
} }
// WriteHeader wraps the underlying WriteHeader method to prevent // WriteHeader wraps the underlying WriteHeader method to prevent
...@@ -135,9 +147,17 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) { ...@@ -135,9 +147,17 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if !w.statusCodeWritten { if !w.statusCodeWritten {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
n, err := w.Writer.Write(b) n, err := w.Writer().Write(b)
return n, err return n, err
} }
//Writer use a lazy way to initialize Writer
func (w *gzipResponseWriter) Writer() io.Writer {
if w.internalWriter == nil {
w.internalWriter = w.newWriter()
}
return w.internalWriter
}
// Interface guards // Interface guards
var _ httpserver.HTTPInterfaces = (*gzipResponseWriter)(nil) var _ httpserver.HTTPInterfaces = (*gzipResponseWriter)(nil)
...@@ -82,7 +82,7 @@ func (r *ResponseFilterWriter) WriteHeader(code int) { ...@@ -82,7 +82,7 @@ func (r *ResponseFilterWriter) WriteHeader(code int) {
if r.shouldCompress { if r.shouldCompress {
// replace discard writer with ResponseWriter // replace discard writer with ResponseWriter
if gzWriter, ok := r.gzipResponseWriter.Writer.(*gzip.Writer); ok { if gzWriter, ok := r.gzipResponseWriter.Writer().(*gzip.Writer); ok {
gzWriter.Reset(r.ResponseWriter) gzWriter.Reset(r.ResponseWriter)
} }
// use gzip WriteHeader to include and delete // use gzip WriteHeader to include and delete
......
...@@ -47,7 +47,7 @@ func TestLengthFilter(t *testing.T) { ...@@ -47,7 +47,7 @@ func TestLengthFilter(t *testing.T) {
for j, filter := range filters { for j, filter := range filters {
r := httptest.NewRecorder() r := httptest.NewRecorder()
r.Header().Set("Content-Length", fmt.Sprint(ts.length)) r.Header().Set("Content-Length", fmt.Sprint(ts.length))
wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), &httpserver.ResponseWriterWrapper{ResponseWriter: r}, false}) wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), &httpserver.ResponseWriterWrapper{ResponseWriter: r}, false, nil})
if filter.ShouldCompress(wWriter) != ts.shouldCompress[j] { if filter.ShouldCompress(wWriter) != ts.shouldCompress[j] {
t.Errorf("Test %v: Expected %v found %v", i, ts.shouldCompress[j], filter.ShouldCompress(r)) t.Errorf("Test %v: Expected %v found %v", i, ts.shouldCompress[j], filter.ShouldCompress(r))
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment