Skip to content

Commit cb5de65

Browse files
committed
Added MinSizeFunc and streamline names.
1 parent 0f838c1 commit cb5de65

File tree

5 files changed

+182
-77
lines changed

5 files changed

+182
-77
lines changed

adapter.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package httpcompression // import "github.com/CAFxX/httpcompression"
1+
package httpcompression
22

33
import (
44
"net/http"
@@ -23,15 +23,15 @@ const (
2323
// is a no-op.
2424
// An error will be returned if invalid options are given.
2525
func Adapter(opts ...Option) (func(http.Handler) http.Handler, error) {
26-
wrapper, err := NewResponseWriterWrapper(opts...)
26+
f, err := NewResponseWriterFactory(opts...)
2727
if err != nil {
2828
return nil, err
2929
}
30-
return adapter(wrapper)
30+
return adapter(f)
3131
}
3232

33-
func adapter(wrapper *ResponseWriterWrapper) (func(http.Handler) http.Handler, error) {
34-
if wrapper.AmountOfCompressors() == 0 {
33+
func adapter(f *ResponseWriterFactoryFactory) (func(http.Handler) http.Handler, error) {
34+
if f.AmountOfCompressors() == 0 {
3535
// No compressors have been configured, so there is no useful work
3636
// that this adapter can do.
3737
return func(h http.Handler) http.Handler {
@@ -41,15 +41,15 @@ func adapter(wrapper *ResponseWriterWrapper) (func(http.Handler) http.Handler, e
4141

4242
return func(h http.Handler) http.Handler {
4343
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
44-
ww, _, finalizer, err := wrapper.Wrap(rw, req)
44+
ww, finalizer, err := f.Create(rw, req)
4545
if err != nil {
46-
wrapper.config.handleError(rw, req, err)
46+
f.config.handleError(rw, req, err)
4747
return
4848
}
4949

5050
defer func() {
5151
if err := finalizer(); err != nil {
52-
wrapper.config.handleError(rw, req, err)
52+
f.config.handleError(rw, req, err)
5353
}
5454
}()
5555

@@ -74,9 +74,9 @@ func addVaryHeader(h http.Header, value string) {
7474
// The defaults are not guaranteed to remain constant over time: if you want to avoid this
7575
// use Adapter directly.
7676
func DefaultAdapter(opts ...Option) (func(http.Handler) http.Handler, error) {
77-
wrapper, err := NewDefaultResponseWriterWrapper(opts...)
77+
f, err := NewDefaultResponseWriterFactory(opts...)
7878
if err != nil {
7979
return nil, err
8080
}
81-
return adapter(wrapper)
81+
return adapter(f)
8282
}

adapter_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,71 @@ func TestGzipHandlerMinSize(t *testing.T) {
600600
}
601601
}
602602

603+
func TestGzipHandlerMinSizeRequestFunc(t *testing.T) {
604+
t.Parallel()
605+
606+
responseLength := 0
607+
b := []byte{'x'}
608+
609+
adapter, _ := DefaultAdapter(MinSizeRequestFunc(func(req *http.Request) (int, error) {
610+
return 128, nil
611+
}))
612+
handler := adapter(http.HandlerFunc(
613+
func(w http.ResponseWriter, r *http.Request) {
614+
// Write responses one byte at a time to ensure that the flush
615+
// mechanism, if used, is working properly.
616+
for i := 0; i < responseLength; i++ {
617+
n, err := w.Write(b)
618+
assert.Equal(t, 1, n)
619+
assert.Nil(t, err)
620+
}
621+
},
622+
))
623+
624+
r, _ := http.NewRequest("GET", "/whatever", &bytes.Buffer{})
625+
r.Header.Add("Accept-Encoding", "gzip")
626+
627+
// Short response is not compressed
628+
responseLength = 127
629+
w := httptest.NewRecorder()
630+
handler.ServeHTTP(w, r)
631+
if w.Result().Header.Get(contentEncoding) == "gzip" {
632+
t.Error("Expected uncompressed response, got compressed")
633+
}
634+
635+
// Long response is compressed
636+
responseLength = 128
637+
w = httptest.NewRecorder()
638+
handler.ServeHTTP(w, r)
639+
if w.Result().Header.Get(contentEncoding) != "gzip" {
640+
t.Error("Expected compressed response, got uncompressed")
641+
}
642+
}
643+
644+
func TestFailGzipHandlerMinSizeRequestFunc(t *testing.T) {
645+
t.Parallel()
646+
647+
expectedError := errors.New("expected")
648+
var actualError error
649+
adapter, _ := DefaultAdapter(
650+
MinSizeRequestFunc(func(req *http.Request) (int, error) {
651+
return 0, expectedError
652+
}),
653+
ErrorHandler(func(_ http.ResponseWriter, _ *http.Request, err error) {
654+
actualError = err
655+
}),
656+
)
657+
658+
handler := adapter(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
659+
r, _ := http.NewRequest("GET", "/whatever", &bytes.Buffer{})
660+
r.Header.Add("Accept-Encoding", "gzip")
661+
w := httptest.NewRecorder()
662+
663+
handler.ServeHTTP(w, r)
664+
665+
assert.ErrorIs(t, actualError, expectedError)
666+
}
667+
603668
type panicOnSecondWriteHeaderWriter struct {
604669
http.ResponseWriter
605670
headerWritten bool

option.go renamed to config.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import (
1010
"net/http"
1111
)
1212

13+
const (
14+
// DefaultMinSize is the default minimum response body size for which we enable compression.
15+
//
16+
// 200 is a somewhat arbitrary number; in experiments compressing short text/markup-like sequences
17+
// with different compressors we saw that sequences shorter that ~180 the output generated by the
18+
// compressor would sometime be larger than the input.
19+
// This default may change between versions.
20+
// In general there can be no one-size-fits-all value: you will want to measure if a different
21+
// minimum size improves end-to-end performance for your workloads.
22+
DefaultMinSize = 200
23+
)
24+
1325
// Option can be passed to Handler to control its configuration.
1426
type Option func(c *config) error
1527

@@ -20,11 +32,30 @@ func MinSize(size int) Option {
2032
if size < 0 {
2133
return fmt.Errorf("minimum size can not be negative: %d", size)
2234
}
35+
if c.minSizeFunc != nil {
36+
return fmt.Errorf("cannot use MinSize and MinSizeRequestFunc together")
37+
}
2338
c.minSize = size
2439
return nil
2540
}
2641
}
2742

43+
// MinSizeRequestFunc is an option that controls the minimum size of payloads that
44+
// should be compressed. The provided func can select this minimum based on
45+
// the provided http.Request. The default is DefaultMinSize.
46+
func MinSizeRequestFunc(f func(*http.Request) (int, error)) Option {
47+
return func(c *config) error {
48+
if f == nil {
49+
return fmt.Errorf("there was no minSizeFunc provided")
50+
}
51+
if c.minSize > 0 {
52+
return fmt.Errorf("cannot use MinSize and MinSizeRequestFunc together")
53+
}
54+
c.minSizeFunc = f
55+
return nil
56+
}
57+
}
58+
2859
// DeflateCompressionLevel is an option that controls the Deflate compression
2960
// level to be used when compressing payloads.
3061
// The default is flate.DefaultCompression.
@@ -97,6 +128,8 @@ func errorOption(err error) Option {
97128
}
98129
}
99130

131+
// ErrorHandler defines what should happen if an unexpected error happens
132+
// within the httpcompression execution chain.
100133
func ErrorHandler(handler func(w http.ResponseWriter, r *http.Request, err error)) Option {
101134
return func(c *config) error {
102135
c.errorHandler = handler
@@ -106,15 +139,16 @@ func ErrorHandler(handler func(w http.ResponseWriter, r *http.Request, err error
106139

107140
// Used for functional configuration.
108141
type config struct {
109-
minSize int // Specifies the minimum response size to gzip. If the response length is bigger than this value, it is compressed.
110-
contentTypes []parsedContentType // Only compress if the response is one of these content-types. All are accepted if empty.
142+
minSize int // Specifies the minimum response size to gzip. If the response length is bigger than this value, it is compressed.
143+
minSizeFunc func(r *http.Request) (int, error) // Similar to minSize but selects the value based on given request.
144+
contentTypes []parsedContentType // Only compress if the response is one of these content-types. All are accepted if empty.
111145
blacklist bool
112146
prefer PreferType
113147
compressor comps
114148
errorHandler func(w http.ResponseWriter, r *http.Request, err error)
115149
}
116150

117-
func (c config) handleError(w http.ResponseWriter, r *http.Request, err error) {
151+
func (c *config) handleError(w http.ResponseWriter, r *http.Request, err error) {
118152
if c.errorHandler != nil {
119153
c.errorHandler(w, r, err)
120154
} else {

response_writer.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ var (
2323
type compressWriter struct {
2424
http.ResponseWriter
2525

26-
config *config
27-
accept codings
28-
common []string
29-
pool *sync.Pool // pool of buffers (buf []byte); max size of each buf is maxBuf
26+
config *config
27+
minSize int
28+
accept codings
29+
common []string
30+
pool *sync.Pool // pool of buffers (buf []byte); max size of each buf is maxBuf
3031

3132
w io.Writer
3233
enc string
@@ -58,15 +59,22 @@ var (
5859

5960
const maxBuf = 1 << 16 // maximum size of recycled buffer
6061

61-
func (w *compressWriter) configure(rw http.ResponseWriter, accept codings, common []string) {
62+
func (w *compressWriter) configure(
63+
rw http.ResponseWriter,
64+
minSize int,
65+
accept codings,
66+
common []string,
67+
) {
6268
w.ResponseWriter = rw
69+
w.minSize = minSize
6370
w.accept = accept
6471
w.common = common
6572
w.w = nil
6673
}
6774

6875
func (w *compressWriter) clean() {
6976
w.ResponseWriter = nil
77+
w.minSize = 0
7078
w.accept = nil
7179
w.common = nil
7280
w.w = nil
@@ -91,8 +99,8 @@ func (w *compressWriter) Write(b []byte) (int, error) {
9199
// Fast path: we have enough information to know whether we will compress
92100
// or not this response from the first write, so we don't need to buffer
93101
// writes to defer the decision until we have more data.
94-
if w.buf == nil && (ct != "" || len(w.config.contentTypes) == 0) && (cl > 0 || len(b) >= w.config.minSize) {
95-
if ce == "" && (cl >= w.config.minSize || len(b) >= w.config.minSize) && handleContentType(ct, w.config.contentTypes, w.config.blacklist) {
102+
if w.buf == nil && (ct != "" || len(w.config.contentTypes) == 0) && (cl > 0 || len(b) >= w.minSize) {
103+
if ce == "" && (cl >= w.minSize || len(b) >= w.minSize) && handleContentType(ct, w.config.contentTypes, w.config.blacklist) {
96104
enc := preferredEncoding(w.accept, w.config.compressor, w.common, w.config.prefer)
97105
if err := w.startCompress(enc, b); err != nil {
98106
return 0, err
@@ -113,13 +121,13 @@ func (w *compressWriter) Write(b []byte) (int, error) {
113121
*w.buf = append(*w.buf, b...)
114122

115123
// Only continue if they didn't already choose an encoding or a known unhandled content length or type.
116-
if ce == "" && (cl == 0 || cl >= w.config.minSize) && (ct == "" || handleContentType(ct, w.config.contentTypes, w.config.blacklist)) {
124+
if ce == "" && (cl == 0 || cl >= w.minSize) && (ct == "" || handleContentType(ct, w.config.contentTypes, w.config.blacklist)) {
117125
// If the current buffer is less than minSize and a Content-Length isn't set, then wait until we have more data.
118-
if len(*w.buf) < w.config.minSize && cl == 0 {
126+
if len(*w.buf) < w.minSize && cl == 0 {
119127
return len(b), nil
120128
}
121129
// If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue.
122-
if cl >= w.config.minSize || len(*w.buf) >= w.config.minSize {
130+
if cl >= w.minSize || len(*w.buf) >= w.minSize {
123131
// If a Content-Type wasn't specified, infer it from the current buffer.
124132
if ct == "" {
125133
ct = http.DetectContentType(*w.buf)

0 commit comments

Comments
 (0)