diff --git a/context.go b/context.go index c42459ff..e3bad68b 100644 --- a/context.go +++ b/context.go @@ -1133,6 +1133,15 @@ func (c *Context) Render(code int, r render.Render) { return } + if c.Writer.Written() && IsDebugging() { + // Skip warning for SSE and streaming responses (status code -1) + if code != -1 { + if _, ok := r.(sse.Event); !ok { + 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 3080015c..5177a040 100644 --- a/context_test.go +++ b/context_test.go @@ -1379,6 +1379,43 @@ func TestContextRenderNoContentData(t *testing.T) { assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } +// Test multiple JSON writes in debug mode +func TestContextRenderMultipleJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + oldMode := os.Getenv("GIN_MODE") + defer os.Setenv("GIN_MODE", oldMode) + SetMode(DebugMode) + + output := captureOutput(t, func() { + c.JSON(http.StatusOK, H{"foo": "bar"}) + c.JSON(http.StatusOK, H{"baz": "qux"}) + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, output, "[WARNING] Response body already written") + assert.Contains(t, output, "status code 200") +} + +// Test multiple SSE writes in debug mode +func TestContextRenderMultipleSSE(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + oldMode := os.Getenv("GIN_MODE") + defer os.Setenv("GIN_MODE", oldMode) + SetMode(DebugMode) + + output := captureOutput(t, func() { + c.SSEvent("message", "test1") + c.SSEvent("message", "test2") + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.NotContains(t, output, "[WARNING] Response body already written") +} + func TestContextRenderSSE(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w)