Compare commits

...

7 Commits

Author SHA1 Message Date
Raju Ahmed
e10089305d
Merge bc46a095350725318e46839b7cd2ac4ae10d25c7 into 5c00df8afadd06cc5be530dde00fe6d9fa4a2e4a 2026-02-28 10:09:13 +08:00
Bo-Yi Wu
bc46a09535
Merge branch 'master' into master 2026-02-28 10:09:11 +08:00
Denis Galeev
5c00df8afa
fix(render): write content length in Data.Render (#4206)
* init test

* fix test

* fix assert.EqualValues usage

---------

Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-02-28 10:07:31 +08:00
Bo-Yi Wu
efdb77ebbd
Merge branch 'master' into master 2026-01-02 10:17:53 +08:00
Raju Ahmed
4d32cf970e
Merge branch 'master' into master 2025-12-28 18:17:49 +06:00
Raju Ahmed
dc128b7291 test(context): add tests for multiple JSON and SSE writes in debug mode 2025-12-28 18:02:10 +06:00
Raju Ahmed
8eb015d2bc fix(context): added warning for multiple writes to response body
warning works only in debug mode
2025-12-28 18:02:10 +06:00
4 changed files with 84 additions and 1 deletions

View File

@ -1157,6 +1157,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)

View File

@ -1396,6 +1396,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)

View File

@ -4,7 +4,10 @@
package render
import "net/http"
import (
"net/http"
"strconv"
)
// Data contains ContentType and bytes data.
type Data struct {
@ -15,6 +18,9 @@ type Data struct {
// Render (Data) writes data with custom ContentType.
func (r Data) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w)
if len(r.Data) > 0 {
w.Header().Set("Content-Length", strconv.Itoa(len(r.Data)))
}
_, err = w.Write(r.Data)
return
}

View File

@ -8,6 +8,7 @@ import (
"encoding/xml"
"errors"
"html/template"
"io"
"net"
"net/http"
"net/http/httptest"
@ -453,6 +454,36 @@ func TestRenderData(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "#!PNG some raw data", w.Body.String())
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
assert.Equal(t, "19", w.Header().Get("Content-Length"))
}
func TestRenderDataContentLength(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
size, err := strconv.Atoi(r.URL.Query().Get("size"))
assert.NoError(t, err)
data := Data{
ContentType: "application/octet-stream",
Data: make([]byte, size),
}
assert.NoError(t, data.Render(w))
}))
t.Cleanup(srv.Close)
for _, size := range []int{0, 1, 100, 100_000} {
t.Run(strconv.Itoa(size), func(t *testing.T) {
resp, err := http.Get(srv.URL + "?size=" + strconv.Itoa(size))
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, "application/octet-stream", resp.Header.Get("Content-Type"))
assert.Equal(t, strconv.Itoa(size), resp.Header.Get("Content-Length"))
actual, err := io.Copy(io.Discard, resp.Body)
require.NoError(t, err)
assert.EqualValues(t, size, actual)
})
}
}
func TestRenderString(t *testing.T) {