mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-23 09:06:29 +01:00
gzip: avoid unnecessary allocations when not compressing (#2396)
This commit is contained in:
parent
917534e35e
commit
f92a3aa0e5
3 changed files with 31 additions and 11 deletions
|
@ -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:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
// 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) {
|
||||||
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) {
|
||||||
|
|
||||||
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) {
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue