Skip to content

Incorrect response code with gzip #2480

Closed
@thomascriley

Description

@thomascriley

Issue Description

The status code is not written to the response writer when there is no content in the response. This is new as of 4.11.0 and possibly a result of #2267. Previously, writing of the header happened in the write header function. This was removed in favor of a delayed write.

echo/middleware/compress.go

Lines 139 to 146 in 60af056

func (w *gzipResponseWriter) WriteHeader(code int) {
w.Header().Del(echo.HeaderContentLength) // Issue #444
w.wroteHeader = true
// Delay writing of the header until we know if we'll actually compress the response
w.code = code
}

This delayed write only happens when a body is written. i.e. wroteBody == true

echo/middleware/compress.go

Lines 110 to 127 in 60af056

if !grw.wroteBody {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
res.Header().Del(echo.HeaderContentEncoding)
}
// We have to reset response to it's pristine state when
// nothing is written to body or error is returned.
// See issue #424, #407.
res.Writer = rw
w.Reset(io.Discard)
} else if !grw.minLengthExceeded {
// Write uncompressed response
res.Writer = rw
if grw.wroteHeader {
grw.ResponseWriter.WriteHeader(grw.code)
}
grw.buffer.WriteTo(rw)
w.Reset(io.Discard)
}

Checklist

  • Dependencies installed
  • No typos
  • Searched existing issues and docs

Expected behaviour

The status code should be written to the response writer even though a body is not written to the response when gzip middleware is used

Actual behaviour

The status code is lost within the gzip middleware when a body is not written, resulting in a 200 response

Steps to reproduce

  1. Create a handler with the Gzip middleware
  2. Within the handler, write a non-200 response without writing a body

Working code to debug

package main

import (
	"encoding/json"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"log"
	"net/http"
)

func main() {
	e := echo.New()

	some := map[string]string{"error": "missing"}
	someBytes, err := json.Marshal(some)
	if err != nil {
		panic(err)
	}

	// status is 200 not 404
	e.GET("/get/default", func(ctx echo.Context) error {
		return ctx.NoContent(http.StatusNotFound)
	}, middleware.Gzip())

	// status is 200 not 307
	e.GET("/redirect", func(ctx echo.Context) error {
		return ctx.Redirect(http.StatusTemporaryRedirect, "/get/config/some")
	}, middleware.Gzip())

	// works
	e.GET("/get/config/some", func(ctx echo.Context) error {
		return ctx.JSON(http.StatusBadRequest, some)
	}, middleware.GzipWithConfig(middleware.GzipConfig{MinLength: len(someBytes) + 10}))

	// status is 200 not 404
	e.GET("/get/config/zero", func(ctx echo.Context) error {
		return ctx.NoContent(http.StatusNotFound)
	}, middleware.GzipWithConfig(middleware.GzipConfig{MinLength: 0}))

	log.Fatal(e.Start(":8080"))
}

Version/commit

4.11.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions