Skip to content

fix gzip not sending response code for no content responses (404, 301/302 redirects etc) #2481

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

Merged
merged 1 commit into from
Jul 16, 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
6 changes: 6 additions & 0 deletions middleware/compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,16 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {

grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw, minLength: config.MinLength, buffer: buf}
defer func() {
// There are different reasons for cases when we have not yet written response to the client and now need to do so.
// a) handler response had only response code and no response body (ala 404 or redirects etc). Response code need to be written now.
// b) body is shorter than our minimum length threshold and being buffered currently and needs to be written
if !grw.wroteBody {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
res.Header().Del(echo.HeaderContentEncoding)
}
if grw.wroteHeader {
rw.WriteHeader(grw.code)
}
// We have to reset response to it's pristine state when
// nothing is written to body or error is returned.
// See issue #424, #407.
Expand Down
52 changes: 32 additions & 20 deletions middleware/compress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ func TestGzip(t *testing.T) {
}

func TestGzipWithMinLength(t *testing.T) {
assert := assert.New(t)

e := echo.New()
// Minimal response length
e.Use(GzipWithConfig(GzipConfig{MinLength: 10}))
Expand All @@ -103,19 +101,17 @@ func TestGzipWithMinLength(t *testing.T) {
req.Header.Set(echo.HeaderAcceptEncoding, gzipScheme)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))
assert.Equal(t, gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))
r, err := gzip.NewReader(rec.Body)
if assert.NoError(err) {
if assert.NoError(t, err) {
buf := new(bytes.Buffer)
defer r.Close()
buf.ReadFrom(r)
assert.Equal("foobarfoobar", buf.String())
assert.Equal(t, "foobarfoobar", buf.String())
}
}

func TestGzipWithMinLengthTooShort(t *testing.T) {
assert := assert.New(t)

e := echo.New()
// Minimal response length
e.Use(GzipWithConfig(GzipConfig{MinLength: 10}))
Expand All @@ -127,13 +123,29 @@ func TestGzipWithMinLengthTooShort(t *testing.T) {
req.Header.Set(echo.HeaderAcceptEncoding, gzipScheme)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal("", rec.Header().Get(echo.HeaderContentEncoding))
assert.Contains(rec.Body.String(), "test")
assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding))
assert.Contains(t, rec.Body.String(), "test")
}

func TestGzipWithMinLengthChunked(t *testing.T) {
assert := assert.New(t)
func TestGzipWithResponseWithoutBody(t *testing.T) {
e := echo.New()

e.Use(Gzip())
e.GET("/", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "http://localhost")
})

req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set(echo.HeaderAcceptEncoding, gzipScheme)
rec := httptest.NewRecorder()

e.ServeHTTP(rec, req)

assert.Equal(t, http.StatusMovedPermanently, rec.Code)
assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding))
}

func TestGzipWithMinLengthChunked(t *testing.T) {
e := echo.New()

// Gzip chunked
Expand All @@ -155,36 +167,36 @@ func TestGzipWithMinLengthChunked(t *testing.T) {
c.Response().Flush()

// Read the first part of the data
assert.True(rec.Flushed)
assert.Equal(gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))
assert.True(t, rec.Flushed)
assert.Equal(t, gzipScheme, rec.Header().Get(echo.HeaderContentEncoding))

var err error
r, err = gzip.NewReader(rec.Body)
assert.NoError(err)
assert.NoError(t, err)

_, err = io.ReadFull(r, chunkBuf)
assert.NoError(err)
assert.Equal("test\n", string(chunkBuf))
assert.NoError(t, err)
assert.Equal(t, "test\n", string(chunkBuf))

// Write and flush the second part of the data
c.Response().Write([]byte("test\n"))
c.Response().Flush()

_, err = io.ReadFull(r, chunkBuf)
assert.NoError(err)
assert.Equal("test\n", string(chunkBuf))
assert.NoError(t, err)
assert.Equal(t, "test\n", string(chunkBuf))

// Write the final part of the data and return
c.Response().Write([]byte("test"))
return nil
})(c)

assert.NotNil(r)
assert.NotNil(t, r)

buf := new(bytes.Buffer)

buf.ReadFrom(r)
assert.Equal("test", buf.String())
assert.Equal(t, "test", buf.String())

r.Close()
}
Expand Down