From ec2c83899541c81bdb2063673d1f368117679726 Mon Sep 17 00:00:00 2001 From: xingzihai <1315258019@qq.com> Date: Mon, 30 Mar 2026 03:48:15 +0000 Subject: [PATCH] feat: add warning for multiple response writes When the response body is written multiple times in a single request, Gin now prints a warning in debug mode. This helps developers identify a common mistake where middleware or handlers accidentally write responses more than once, resulting in invalid concatenated output. The warning is printed via debugPrint() and only appears in debug mode, making this change non-breaking and backwards compatible. Fixes #4477 Example warning output: [GIN-debug] [WARNING] Response body already written. Attempting to write again with status code 200 Changes: - Added check in Context.Render() to detect multiple writes - Added unit tests to verify warning behavior in debug mode - Added test to verify no warning in release mode --- context.go | 4 ++++ context_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/context.go b/context.go index 5174033eb3..3024adde1e 100644 --- a/context.go +++ b/context.go @@ -1158,6 +1158,10 @@ func (c *Context) Render(code int, r render.Render) { return } + if c.Writer.Written() { + debugPrint("[WARNING] Response body already written. Attempting to write again with status code %d", code) + } + if err := r.Render(c.Writer); err != nil { // Pushing error to c.Errors _ = c.Error(err) diff --git a/context_test.go b/context_test.go index ef60379d77..879896c712 100644 --- a/context_test.go +++ b/context_test.go @@ -3808,3 +3808,47 @@ func BenchmarkGetMapFromFormData(b *testing.B) { }) } } + +func TestRenderMultipleWritesWarning(t *testing.T) { + // Test that a warning is printed when attempting to write response body multiple times + var w *httptest.ResponseRecorder + re := captureOutput(t, func() { + SetMode(DebugMode) + router := New() + router.GET("/test", func(c *Context) { + c.JSON(http.StatusOK, H{"first": "response"}) + c.JSON(http.StatusOK, H{"second": "response"}) // Should trigger warning + }) + w = httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/test", nil) + router.ServeHTTP(w, req) + SetMode(TestMode) + }) + + // Should contain the warning about multiple writes + assert.Contains(t, re, "[WARNING] Response body already written") + assert.Contains(t, re, "200") + + // Verify the response body contains both responses (behavior unchanged) + assert.Contains(t, w.Body.String(), "first") + assert.Contains(t, w.Body.String(), "second") +} + +func TestRenderMultipleWritesNoWarningInReleaseMode(t *testing.T) { + // Test that no warning is printed in release mode + re := captureOutput(t, func() { + SetMode(ReleaseMode) + router := New() + router.GET("/test", func(c *Context) { + c.JSON(http.StatusOK, H{"first": "response"}) + c.JSON(http.StatusOK, H{"second": "response"}) + }) + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/test", nil) + router.ServeHTTP(w, req) + SetMode(TestMode) + }) + + // Should not contain the warning in release mode + assert.NotContains(t, re, "[WARNING] Response body already written") +}