From ce08e4508ac2ef7645abd51cda9cb3448285bca5 Mon Sep 17 00:00:00 2001 From: Abdullah Alqahtani <61029571+anqorithm@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:39:30 +0100 Subject: [PATCH] fix(middleware): finish gzip streams after flush-only handlers --- middleware/compress.go | 1 + middleware/compress_test.go | 42 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/middleware/compress.go b/middleware/compress.go index 7754d5db8..cc86cde70 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -181,6 +181,7 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) { } func (w *gzipResponseWriter) Flush() { + w.wroteBody = true if !w.minLengthExceeded { // Enforce compression because we will not know how much more data will come w.minLengthExceeded = true diff --git a/middleware/compress_test.go b/middleware/compress_test.go index 084ffc9c7..f35a34d45 100644 --- a/middleware/compress_test.go +++ b/middleware/compress_test.go @@ -165,6 +165,48 @@ func TestGzip_Empty(t *testing.T) { } } +func TestGzip_FlushWithoutWrite(t *testing.T) { + for _, tt := range []struct { + name string + minLength int + flushes int + }{ + {name: "default", flushes: 1}, + {name: "minimum length", minLength: 1024, flushes: 1}, + {name: "repeated flush", minLength: 1024, flushes: 2}, + } { + t.Run(tt.name, func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderAcceptEncoding, gzipScheme) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + h := GzipWithConfig(GzipConfig{MinLength: tt.minLength})(func(c *echo.Context) error { + controller := http.NewResponseController(c.Response()) + for i := 0; i < tt.flushes; i++ { + if err := controller.Flush(); err != nil { + return err + } + } + return nil + }) + if !assert.NoError(t, h(c)) { + return + } + assert.True(t, rec.Flushed) + assert.Equal(t, gzipScheme, rec.Result().Header.Get(echo.HeaderContentEncoding)) + reader, err := gzip.NewReader(rec.Body) + if !assert.NoError(t, err) { + return + } + body, err := io.ReadAll(reader) + assert.NoError(t, err) + assert.Empty(t, body) + assert.NoError(t, reader.Close()) + }) + } +} + func TestGzip_ErrorReturned(t *testing.T) { e := echo.New() e.Use(Gzip())