Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto add 'Vary' header after compression #1585

Merged
merged 4 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,18 @@ func (h *ResponseHeader) SetContentEncodingBytes(contentEncoding []byte) {
h.contentEncoding = append(h.contentEncoding[:0], contentEncoding...)
}

// addVaryBytes add value to the 'Vary' header if it's not included
func (h *ResponseHeader) addVaryBytes(value []byte) {
v := h.peek(strVary)
if len(v) == 0 {
// 'Vary' is not set
h.SetBytesV(HeaderVary, value)
} else if !bytes.Contains(v, value) {
// 'Vary' is set and not contains target value
h.SetBytesV(HeaderVary, append(append(v, ','), value...))
} // else: 'Vary' is set and contains target value
}

// Server returns Server header value.
func (h *ResponseHeader) Server() []byte {
return h.server
Expand Down
62 changes: 62 additions & 0 deletions header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3007,3 +3007,65 @@ func TestResponseHeader_Keys(t *testing.T) {
t.Fatalf("Unexpected value %q. Expected %q", actualTrailerKeys, expectedTrailerKeys)
}
}

func TestAddVaryHeader(t *testing.T) {
t.Parallel()

var h ResponseHeader

h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}

var buf bytes.Buffer
h.WriteTo(&buf) //nolint:errcheck

if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}

func TestAddVaryHeaderExisting(t *testing.T) {
t.Parallel()

var h ResponseHeader

h.Set("Vary", "Accept")
h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept,Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}

var buf bytes.Buffer
h.WriteTo(&buf) //nolint:errcheck

if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}

func TestAddVaryHeaderExistingAcceptEncoding(t *testing.T) {
t.Parallel()

var h ResponseHeader

h.Set("Vary", "Accept-Encoding")
h.addVaryBytes([]byte("Accept-Encoding"))
got := string(h.Peek("Vary"))
expected := "Accept-Encoding"
if got != expected {
t.Errorf("expected %q got %q", expected, got)
}

var buf bytes.Buffer
h.WriteTo(&buf) //nolint:errcheck

if n := strings.Count(buf.String(), "Vary: "); n != 1 {
t.Errorf("Vary occurred %d times", n)
}
}
3 changes: 3 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,7 @@ func (resp *Response) brotliBody(level int) error {
resp.bodyRaw = nil
}
resp.Header.SetContentEncodingBytes(strBr)
resp.Header.addVaryBytes(strAcceptEncoding)
return nil
}

Expand Down Expand Up @@ -1778,6 +1779,7 @@ func (resp *Response) gzipBody(level int) error {
resp.bodyRaw = nil
}
resp.Header.SetContentEncodingBytes(strGzip)
resp.Header.addVaryBytes(strAcceptEncoding)
return nil
}

Expand Down Expand Up @@ -1833,6 +1835,7 @@ func (resp *Response) deflateBody(level int) error {
resp.bodyRaw = nil
}
resp.Header.SetContentEncodingBytes(strDeflate)
resp.Header.addVaryBytes(strAcceptEncoding)
return nil
}

Expand Down
141 changes: 141 additions & 0 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,147 @@ func TestCompressHandler(t *testing.T) {
}
}

func TestCompressHandlerVary(t *testing.T) {
t.Parallel()

expectedBody := string(createFixedBody(2e4))

h := CompressHandlerBrotliLevel(func(ctx *RequestCtx) {
ctx.WriteString(expectedBody) //nolint:errcheck
}, CompressBrotliBestSpeed, CompressBestSpeed)

var ctx RequestCtx
var resp Response

// verify uncompressed response
h(&ctx)
s := ctx.Response.String()
br := bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
ce := resp.Header.ContentEncoding()
if string(ce) != "" {
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "")
}
vary := resp.Header.Peek("Vary")
if string(vary) != "" {
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "")
}
body := resp.Body()
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}

// verify gzip-compressed response
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")

h(&ctx)
s = ctx.Response.String()
br = bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
ce = resp.Header.ContentEncoding()
if string(ce) != "gzip" {
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
}
vary = resp.Header.Peek("Vary")
if string(vary) != "Accept-Encoding" {
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
}
body, err := resp.BodyGunzip()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}

// an attempt to compress already compressed response
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.Set("Accept-Encoding", "gzip, deflate, sdhc")
hh := CompressHandler(h)
hh(&ctx)
s = ctx.Response.String()
br = bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
ce = resp.Header.ContentEncoding()
if string(ce) != "gzip" {
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "gzip")
}
vary = resp.Header.Peek("Vary")
if string(vary) != "Accept-Encoding" {
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
}
body, err = resp.BodyGunzip()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}

// verify deflate-compressed response
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.Set(HeaderAcceptEncoding, "foobar, deflate, sdhc")

h(&ctx)
s = ctx.Response.String()
br = bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
ce = resp.Header.ContentEncoding()
if string(ce) != "deflate" {
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "deflate")
}
vary = resp.Header.Peek("Vary")
if string(vary) != "Accept-Encoding" {
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
}
body, err = resp.BodyInflate()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}

// verify br-compressed response
ctx.Request.Reset()
ctx.Response.Reset()
ctx.Request.Header.Set(HeaderAcceptEncoding, "gzip, deflate, br")

h(&ctx)
s = ctx.Response.String()
br = bufio.NewReader(bytes.NewBufferString(s))
if err := resp.Read(br); err != nil {
t.Fatalf("unexpected error: %v", err)
}
ce = resp.Header.ContentEncoding()
if string(ce) != "br" {
t.Fatalf("unexpected Content-Encoding: %q. Expecting %q", ce, "br")
}
vary = resp.Header.Peek("Vary")
if string(vary) != "Accept-Encoding" {
t.Fatalf("unexpected Vary: %q. Expecting %q", vary, "Accept-Encoding")
}
body, err = resp.BodyUnbrotli()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != expectedBody {
t.Fatalf("unexpected body %q. Expecting %q", body, expectedBody)
}
}

func TestRequestCtxWriteString(t *testing.T) {
t.Parallel()

Expand Down
1 change: 1 addition & 0 deletions strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var (
strProxyAuthenticate = []byte(HeaderProxyAuthenticate)
strProxyAuthorization = []byte(HeaderProxyAuthorization)
strWWWAuthenticate = []byte(HeaderWWWAuthenticate)
strVary = []byte(HeaderVary)

strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
Expand Down