Skip to content

Commit 0d0bbfe

Browse files
authoredJul 2, 2023
Auto add 'Vary' header after compression (#1585)
* Auto add 'Vary' header after compression Add config `SetAddVaryHeaderForCompression` to enable 'Vary: Accept-Encoding' header when compression is used. * feat: always set the Vary header * create and use `ResponseHeader.AddVaryBytes` * not export 'AddVaryBytes'
1 parent d229959 commit 0d0bbfe

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed
 

‎header.go

+12
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,18 @@ func (h *ResponseHeader) SetContentEncodingBytes(contentEncoding []byte) {
344344
h.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)
345345
}
346346

347+
// addVaryBytes add value to the 'Vary' header if it's not included
348+
func (h *ResponseHeader) addVaryBytes(value []byte) {
349+
v := h.peek(strVary)
350+
if len(v) == 0 {
351+
// 'Vary' is not set
352+
h.SetBytesV(HeaderVary, value)
353+
} else if !bytes.Contains(v, value) {
354+
// 'Vary' is set and not contains target value
355+
h.SetBytesV(HeaderVary, append(append(v, ','), value...))
356+
} // else: 'Vary' is set and contains target value
357+
}
358+
347359
// Server returns Server header value.
348360
func (h *ResponseHeader) Server() []byte {
349361
return h.server

‎header_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -3007,3 +3007,65 @@ func TestResponseHeader_Keys(t *testing.T) {
30073007
t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys)
30083008
}
30093009
}
3010+
3011+
func TestAddVaryHeader(t *testing.T) {
3012+
t.Parallel()
3013+
3014+
var h ResponseHeader
3015+
3016+
h.addVaryBytes([]byte("Accept-Encoding"))
3017+
got := string(h.Peek("Vary"))
3018+
expected := "Accept-Encoding"
3019+
if got != expected {
3020+
t.Errorf("expected %q got %q", expected, got)
3021+
}
3022+
3023+
var buf bytes.Buffer
3024+
h.WriteTo(&buf) //nolint:errcheck
3025+
3026+
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
3027+
t.Errorf("Vary occurred %d times", n)
3028+
}
3029+
}
3030+
3031+
func TestAddVaryHeaderExisting(t *testing.T) {
3032+
t.Parallel()
3033+
3034+
var h ResponseHeader
3035+
3036+
h.Set("Vary", "Accept")
3037+
h.addVaryBytes([]byte("Accept-Encoding"))
3038+
got := string(h.Peek("Vary"))
3039+
expected := "Accept,Accept-Encoding"
3040+
if got != expected {
3041+
t.Errorf("expected %q got %q", expected, got)
3042+
}
3043+
3044+
var buf bytes.Buffer
3045+
h.WriteTo(&buf) //nolint:errcheck
3046+
3047+
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
3048+
t.Errorf("Vary occurred %d times", n)
3049+
}
3050+
}
3051+
3052+
func TestAddVaryHeaderExistingAcceptEncoding(t *testing.T) {
3053+
t.Parallel()
3054+
3055+
var h ResponseHeader
3056+
3057+
h.Set("Vary", "Accept-Encoding")
3058+
h.addVaryBytes([]byte("Accept-Encoding"))
3059+
got := string(h.Peek("Vary"))
3060+
expected := "Accept-Encoding"
3061+
if got != expected {
3062+
t.Errorf("expected %q got %q", expected, got)
3063+
}
3064+
3065+
var buf bytes.Buffer
3066+
h.WriteTo(&buf) //nolint:errcheck
3067+
3068+
if n := strings.Count(buf.String(), "Vary: "); n != 1 {
3069+
t.Errorf("Vary occurred %d times", n)
3070+
}
3071+
}

‎http.go

+3
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,7 @@ func (resp *Response) brotliBody(level int) error {
17231723
resp.bodyRaw = nil
17241724
}
17251725
resp.Header.SetContentEncodingBytes(strBr)
1726+
resp.Header.addVaryBytes(strAcceptEncoding)
17261727
return nil
17271728
}
17281729

@@ -1778,6 +1779,7 @@ func (resp *Response) gzipBody(level int) error {
17781779
resp.bodyRaw = nil
17791780
}
17801781
resp.Header.SetContentEncodingBytes(strGzip)
1782+
resp.Header.addVaryBytes(strAcceptEncoding)
17811783
return nil
17821784
}
17831785

@@ -1833,6 +1835,7 @@ func (resp *Response) deflateBody(level int) error {
18331835
resp.bodyRaw = nil
18341836
}
18351837
resp.Header.SetContentEncodingBytes(strDeflate)
1838+
resp.Header.addVaryBytes(strAcceptEncoding)
18361839
return nil
18371840
}
18381841

‎server_test.go

+141
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,147 @@ func TestCompressHandler(t *testing.T) {
20352035
}
20362036
}
20372037

2038+
func TestCompressHandlerVary(t *testing.T) {
2039+
t.Parallel()
2040+
2041+
expectedBody := string(createFixedBody(2e4))
2042+
2043+
h := CompressHandlerBrotliLevel(func(ctx *RequestCtx) {
2044+
ctx.WriteString(expectedBody) //nolint:errcheck
2045+
}, CompressBrotliBestSpeed, CompressBestSpeed)
2046+
2047+
var ctx RequestCtx
2048+
var resp Response
2049+
2050+
// verify uncompressed response
2051+
h(&ctx)
2052+
s := ctx.Response.String()
2053+
br := bufio.NewReader(bytes.NewBufferString(s))
2054+
if err := resp.Read(br); err != nil {
2055+
t.Fatalf("unexpected error: %v", err)
2056+
}
2057+
ce := resp.Header.ContentEncoding()
2058+
if string(ce) != "" {
2059+
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "")
2060+
}
2061+
vary := resp.Header.Peek("Vary")
2062+
if string(vary) != "" {
2063+
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "")
2064+
}
2065+
body := resp.Body()
2066+
if string(body) != expectedBody {
2067+
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
2068+
}
2069+
2070+
// verify gzip-compressed response
2071+
ctx.Request.Reset()
2072+
ctx.Response.Reset()
2073+
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")
2074+
2075+
h(&ctx)
2076+
s = ctx.Response.String()
2077+
br = bufio.NewReader(bytes.NewBufferString(s))
2078+
if err := resp.Read(br); err != nil {
2079+
t.Fatalf("unexpected error: %v", err)
2080+
}
2081+
ce = resp.Header.ContentEncoding()
2082+
if string(ce) != "gzip" {
2083+
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
2084+
}
2085+
vary = resp.Header.Peek("Vary")
2086+
if string(vary) != "Accept-Encoding" {
2087+
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
2088+
}
2089+
body, err := resp.BodyGunzip()
2090+
if err != nil {
2091+
t.Fatalf("unexpected error: %v", err)
2092+
}
2093+
if string(body) != expectedBody {
2094+
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
2095+
}
2096+
2097+
// an attempt to compress already compressed response
2098+
ctx.Request.Reset()
2099+
ctx.Response.Reset()
2100+
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")
2101+
hh := CompressHandler(h)
2102+
hh(&ctx)
2103+
s = ctx.Response.String()
2104+
br = bufio.NewReader(bytes.NewBufferString(s))
2105+
if err := resp.Read(br); err != nil {
2106+
t.Fatalf("unexpected error: %v", err)
2107+
}
2108+
ce = resp.Header.ContentEncoding()
2109+
if string(ce) != "gzip" {
2110+
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
2111+
}
2112+
vary = resp.Header.Peek("Vary")
2113+
if string(vary) != "Accept-Encoding" {
2114+
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
2115+
}
2116+
body, err = resp.BodyGunzip()
2117+
if err != nil {
2118+
t.Fatalf("unexpected error: %v", err)
2119+
}
2120+
if string(body) != expectedBody {
2121+
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
2122+
}
2123+
2124+
// verify deflate-compressed response
2125+
ctx.Request.Reset()
2126+
ctx.Response.Reset()
2127+
ctx.Request.Header.Set(HeaderAcceptEncoding, "foobar, deflate, sdhc")
2128+
2129+
h(&ctx)
2130+
s = ctx.Response.String()
2131+
br = bufio.NewReader(bytes.NewBufferString(s))
2132+
if err := resp.Read(br); err != nil {
2133+
t.Fatalf("unexpected error: %v", err)
2134+
}
2135+
ce = resp.Header.ContentEncoding()
2136+
if string(ce) != "deflate" {
2137+
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "deflate")
2138+
}
2139+
vary = resp.Header.Peek("Vary")
2140+
if string(vary) != "Accept-Encoding" {
2141+
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
2142+
}
2143+
body, err = resp.BodyInflate()
2144+
if err != nil {
2145+
t.Fatalf("unexpected error: %v", err)
2146+
}
2147+
if string(body) != expectedBody {
2148+
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
2149+
}
2150+
2151+
// verify br-compressed response
2152+
ctx.Request.Reset()
2153+
ctx.Response.Reset()
2154+
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, deflate, br")
2155+
2156+
h(&ctx)
2157+
s = ctx.Response.String()
2158+
br = bufio.NewReader(bytes.NewBufferString(s))
2159+
if err := resp.Read(br); err != nil {
2160+
t.Fatalf("unexpected error: %v", err)
2161+
}
2162+
ce = resp.Header.ContentEncoding()
2163+
if string(ce) != "br" {
2164+
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "br")
2165+
}
2166+
vary = resp.Header.Peek("Vary")
2167+
if string(vary) != "Accept-Encoding" {
2168+
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
2169+
}
2170+
body, err = resp.BodyUnbrotli()
2171+
if err != nil {
2172+
t.Fatalf("unexpected error: %v", err)
2173+
}
2174+
if string(body) != expectedBody {
2175+
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
2176+
}
2177+
}
2178+
20382179
func TestRequestCtxWriteString(t *testing.T) {
20392180
t.Parallel()
20402181

‎strings.go

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ var (
5757
strProxyAuthenticate = []byte(HeaderProxyAuthenticate)
5858
strProxyAuthorization = []byte(HeaderProxyAuthorization)
5959
strWWWAuthenticate = []byte(HeaderWWWAuthenticate)
60+
strVary = []byte(HeaderVary)
6061

6162
strCookieExpires = []byte("expires")
6263
strCookieDomain = []byte("domain")

0 commit comments

Comments
 (0)
Please sign in to comment.