Skip to content

Mishandling 404? #98

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

Open
rymurr opened this issue Jan 23, 2025 · 11 comments
Open

Mishandling 404? #98

rymurr opened this issue Jan 23, 2025 · 11 comments

Comments

@rymurr
Copy link

rymurr commented Jan 23, 2025

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
Copy link
Author

rymurr commented Jan 23, 2025

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

@cloudberrystory
Copy link

Same stuff

@Kerguillec
Copy link

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

@aveyuan
Copy link

aveyuan commented Mar 12, 2025

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

@joshelser
Copy link

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.

@aveyuan
Copy link

aveyuan commented Mar 13, 2025

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
Copy link

aveyuan commented Mar 13, 2025

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

@sdemjanenko
Copy link

sdemjanenko commented Mar 27, 2025

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)

@DmitryPor
Copy link

Same

@TCP404
Copy link

TCP404 commented May 14, 2025

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
}

@jschulte-maxmine
Copy link

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 pushed a commit to jschulte-maxmine/gzip that referenced this issue May 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants