gzip icon indicating copy to clipboard operation
gzip copied to clipboard

Mishandling 404?

Open rymurr opened this issue 10 months ago • 12 comments

I have a simple program

package main

import (

	"github.com/gin-contrib/gzip"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.New()
	r.Use( gzip.Gzip(gzip.DefaultCompression))

	r.GET("/x", func(c *gin.Context) {
		c.JSON(200, gin.H{"hello": "world"})
	})
	r.Run()
}

when i do curl localhost:8080 -H 'accept-encoding: gzip' -v I get a 200 response and when I do curl localhost:8080 -v I get a 404 response.

I am not sure what I am doing wrong but it seems like the gzip buffer is being closed before gin writes its 404 which somehow messes up the eventual response object.

hitting the localhost:8080/x endpoint works fine

rymurr avatar Jan 23 '25 15:01 rymurr

It looks like this was introduced between v1.0.1 and v1.1.0

rymurr avatar Jan 23 '25 15:01 rymurr

Same stuff

cloudberrystory avatar Jan 27 '25 21:01 cloudberrystory

Hello, same here Also with debug mode I got the following error with a 200 answer instead of 404 while hitting bad route (since v1.10) : [GIN-debug] cannot write message to writer during serve error: flate: closed writer I don't know if it's related

Kerguillec avatar Jan 28 '25 05:01 Kerguillec

module gingz

go 1.23.6

require (
	github.com/gin-contrib/gzip v1.2.2
	github.com/gin-gonic/gin v1.10.0
)

It's work

Image

aveyuan avatar Mar 12 '25 08:03 aveyuan

It's work

The bug is that http://localhost:8080/ should return a 404 but it returns a 200. Requests to http://localhost:8080/x work as expected.

joshelser avatar Mar 12 '25 19:03 joshelser

It's work

The bug is that http://localhost:8080/ should return a 404 but it returns a 200. Requests to http://localhost:8080/x work as expected.

Look at it's nothing?

Image

aveyuan avatar Mar 13 '25 01:03 aveyuan

?body response is large ,the gzip will error? Image

aveyuan avatar Mar 13 '25 03:03 aveyuan

I'm working on a test case:

func Test404WithGzip(t *testing.T) {
	req, _ := http.NewRequestWithContext(context.Background(), "GET", "/bar", nil)
	req.Header.Add(headerAcceptEncoding, "gzip")

	router := gin.New()
	router.Use(Gzip(DefaultCompression, WithDecompressFn(DefaultDecompressHandle)))
	router.GET("/foo", func(c *gin.Context) {
		c.String(200, "ok")
	})

	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusNotFound, w.Code)
	assert.Equal(t, "gzip", w.Header().Get(headerContentEncoding))
	assert.Equal(t, "Accept-Encoding", w.Header().Get(headerVary))
	assert.Equal(t, "23", w.Header().Get("Content-Length"))

	gr, err := gzip.NewReader(w.Body)
	assert.NoError(t, err)
	defer gr.Close()

	body, _ := io.ReadAll(gr)
	assert.Equal(t, "404 page not found", string(body))
}

I found that the setting of the 200 seems to be coming from gz.Reset(io.Discard)

sdemjanenko avatar Mar 27 '25 20:03 sdemjanenko

Same

DmitryPor avatar Apr 06 '25 10:04 DmitryPor

I try to add g.WriteHeaderNow(), it worked.

func (g *gzipWriter) Write(data []byte) (int, error) {
	g.Header().Del("Content-Length")
+	g.WriteHeaderNow()
	return g.writer.Write(data)
}

compare with default writer responseWriter:

func (w *responseWriter) Write(data []byte) (n int, err error) {
	w.WriteHeaderNow()
	n, err = w.ResponseWriter.Write(data)
	w.size += n
	return
}

TCP404 avatar May 14 '25 03:05 TCP404

Have added a test here to the existing suite which demonstrates the issue

func TestHandleGzip(t *testing.T) {
	gin.SetMode(gin.TestMode)

	tests := []struct {
		name                    string
		path                    string
		acceptEncoding          string
		expectedContentEncoding string
		expectedBody            string
		expectedStatus          int
	}{
		{
			name:                    "Gzip compression",
			path:                    "/",
			acceptEncoding:          "gzip",
			expectedContentEncoding: "gzip",
			expectedBody:            "Gzip Test Response",
			expectedStatus:          http.StatusOK,
		},
		{
			name:                    "No compression",
			path:                    "/",
			acceptEncoding:          "",
			expectedContentEncoding: "",
			expectedBody:            "Gzip Test Response",
			expectedStatus:          http.StatusOK,
		},
		{
			name:                    "Bad path, no compression",
			path:                    "/bad-path",
			acceptEncoding:          "",
			expectedContentEncoding: "",
			expectedBody:            "404 page not found",
			expectedStatus:          http.StatusNotFound,
		},
		{
			name:                    "Bad path, with compression",
			path:                    "/bad-path",
			acceptEncoding:          "gzip",
			expectedContentEncoding: "gzip",
			expectedBody:            "404 page not found",
			expectedStatus:          http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			router := gin.New()
			router.Use(Gzip(DefaultCompression))
			router.GET("/", func(c *gin.Context) {
				c.String(http.StatusOK, "Gzip Test Response")
			})

			req, _ := http.NewRequestWithContext(context.Background(), "GET", tt.path, nil)
			req.Header.Set(headerAcceptEncoding, tt.acceptEncoding)

			w := httptest.NewRecorder()
			router.ServeHTTP(w, req)

			assert.Equal(t, tt.expectedStatus, w.Code)
			assert.Equal(t, tt.expectedContentEncoding, w.Header().Get("Content-Encoding"))

			if tt.expectedContentEncoding == "gzip" {
				gr, err := gzip.NewReader(w.Body)
				assert.NoError(t, err)
				defer gr.Close()

				body, _ := io.ReadAll(gr)
				assert.Equal(t, tt.expectedBody, string(body))
			} else {
				assert.Equal(t, tt.expectedBody, w.Body.String())
			}
		})
	}
}

jschulte-maxmine avatar May 27 '25 04:05 jschulte-maxmine

[GIN-debug] cannot write message to writer during serve error: flate: closed writer

I also encountered this problem. Has this problem been fixed in version 1.2.3?

adcwb avatar Jun 20 '25 03:06 adcwb

@adcwb no, it still present in 1.2.3

radykal-com avatar Aug 07 '25 12:08 radykal-com

@appleboy any change to take a loot at this?

radykal-com avatar Aug 07 '25 12:08 radykal-com

Hi, i got the same issue.

geekswamp avatar Oct 09 '25 12:10 geekswamp

I will take it.

appleboy avatar Oct 09 '25 15:10 appleboy