diff --git a/render/data.go b/render/data.go index a653ea30..d652a65c 100644 --- a/render/data.go +++ b/render/data.go @@ -15,6 +15,9 @@ type Data struct { // Render (Data) writes data with custom ContentType. func (r Data) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) + + writeContentLength(w, len(r.Data)) + _, err = w.Write(r.Data) return } diff --git a/render/json.go b/render/json.go index fc8dea45..78b00f6f 100644 --- a/render/json.go +++ b/render/json.go @@ -62,6 +62,11 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// WriteContentType (JSON) writes JSON ContentType. +func (r JSON) WriteContentLength(w http.ResponseWriter, length int) { + writeContentLength(w, length) +} + // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj any) error { writeContentType(w, jsonContentType) @@ -69,6 +74,9 @@ func WriteJSON(w http.ResponseWriter, obj any) error { if err != nil { return err } + + writeContentLength(w, len(jsonBytes)) + _, err = w.Write(jsonBytes) return err } @@ -80,6 +88,9 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { if err != nil { return err } + + writeContentLength(w, len(jsonBytes)) + _, err = w.Write(jsonBytes) return err } @@ -96,13 +107,22 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { if err != nil { return err } + + length := len(jsonBytes) + // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes, bytesconv.StringToBytes("]")) { + if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil { return err } + length += len(r.Prefix) + } + + writeContentLength(w, length) + _, err = w.Write(jsonBytes) return err } @@ -115,18 +135,24 @@ func (r SecureJSON) WriteContentType(w http.ResponseWriter) { // Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) - ret, err := json.Marshal(r.Data) + jsonBytes, err := json.Marshal(r.Data) if err != nil { return err } + length := len(jsonBytes) + if r.Callback == "" { - _, err = w.Write(ret) + writeContentLength(w, length) + + _, err = w.Write(jsonBytes) return err } callback := template.JSEscapeString(r.Callback) - if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil { + callbackBytes := bytesconv.StringToBytes(callback) + + if _, err = w.Write(callbackBytes); err != nil { return err } @@ -134,7 +160,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { return err } - if _, err = w.Write(ret); err != nil { + if _, err = w.Write(jsonBytes); err != nil { return err } @@ -142,6 +168,10 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { return err } + length += len(callbackBytes) + 3 // 3 = len("();") + + writeContentLength(w, length) + return nil } @@ -167,7 +197,11 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { buffer.WriteString(cvt) } - _, err = w.Write(buffer.Bytes()) + jsonBytes := buffer.Bytes() + + writeContentLength(w, len(jsonBytes)) + + _, err = w.Write(jsonBytes) return err } diff --git a/render/protobuf.go b/render/protobuf.go index 9331c405..b9e78874 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -26,6 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return err } + writeContentLength(w, len(bytes)) + _, err = w.Write(bytes) return err } diff --git a/render/render.go b/render/render.go index 7955000c..410994fe 100644 --- a/render/render.go +++ b/render/render.go @@ -4,7 +4,10 @@ package render -import "net/http" +import ( + "net/http" + "strconv" +) // Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { @@ -39,3 +42,7 @@ func writeContentType(w http.ResponseWriter, value []string) { header["Content-Type"] = value } } + +func writeContentLength(w http.ResponseWriter, value int) { + w.Header().Set("Content-Length", strconv.Itoa(value)) +} diff --git a/render/render_test.go b/render/render_test.go index c9db635f..4ab94cbc 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -39,6 +39,8 @@ func TestRenderJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "36", w.Header().Get("Content-Length")) + } func TestRenderJSONError(t *testing.T) { @@ -61,6 +63,8 @@ func TestRenderIndentedJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "38", w.Header().Get("Content-Length")) + } func TestRenderIndentedJSONPanics(t *testing.T) { @@ -86,6 +90,7 @@ func TestRenderSecureJSON(t *testing.T) { assert.NoError(t, err1) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) + assert.Equal(t, "13", w1.Header().Get("Content-Length")) w2 := httptest.NewRecorder() datas := []map[string]any{{ @@ -98,6 +103,7 @@ func TestRenderSecureJSON(t *testing.T) { assert.NoError(t, err2) assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) + assert.Equal(t, "38", w2.Header().Get("Content-Length")) } func TestRenderSecureJSONFail(t *testing.T) { @@ -123,6 +129,7 @@ func TestRenderJsonpJSON(t *testing.T) { assert.NoError(t, err1) assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + assert.Equal(t, "17", w1.Header().Get("Content-Length")) w2 := httptest.NewRecorder() datas := []map[string]any{{ @@ -135,6 +142,8 @@ func TestRenderJsonpJSON(t *testing.T) { assert.NoError(t, err2) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) + assert.Equal(t, "33", w2.Header().Get("Content-Length")) + } type errorWriter struct { @@ -195,6 +204,7 @@ func TestRenderJsonpJSONError2(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "13", w.Header().Get("Content-Length")) } func TestRenderJsonpJSONFail(t *testing.T) { @@ -218,6 +228,7 @@ func TestRenderAsciiJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) + assert.Equal(t, "48", w1.Header().Get("Content-Length")) w2 := httptest.NewRecorder() data2 := 3.1415926 @@ -225,6 +236,8 @@ func TestRenderAsciiJSON(t *testing.T) { err = (AsciiJSON{data2}).Render(w2) assert.NoError(t, err) assert.Equal(t, "3.1415926", w2.Body.String()) + assert.Equal(t, "9", w2.Header().Get("Content-Length")) + } func TestRenderAsciiJSONFail(t *testing.T) { @@ -286,6 +299,8 @@ b: assert.NoError(t, err) assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "56", w.Header().Get("Content-Length")) + } type fail struct{} @@ -314,6 +329,8 @@ func TestRenderTOML(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "foo = 'bar'\nhtml = ''\n", w.Body.String()) assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "25", w.Header().Get("Content-Length")) + } func TestRenderTOMLFail(t *testing.T) { @@ -342,6 +359,8 @@ func TestRenderProtoBuf(t *testing.T) { assert.NoError(t, err) assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(string(protoData))), w.Header().Get("Content-Length")) + } func TestRenderProtoBufFail(t *testing.T) { @@ -419,6 +438,8 @@ func TestRenderData(t *testing.T) { assert.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 TestRenderString(t *testing.T) { @@ -451,6 +472,8 @@ func TestRenderStringLenZero(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "hola %s %d", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "10", w.Header().Get("Content-Length")) + } func TestRenderHTMLTemplate(t *testing.T) { diff --git a/render/text.go b/render/text.go index 77eafdfd..24a7c125 100644 --- a/render/text.go +++ b/render/text.go @@ -36,6 +36,12 @@ func WriteString(w http.ResponseWriter, format string, data []any) (err error) { _, err = fmt.Fprintf(w, format, data...) return } - _, err = w.Write(bytesconv.StringToBytes(format)) + + bytes := bytesconv.StringToBytes(format) + + writeContentLength(w, len(bytes)) + + _, err = w.Write(bytes) + return } diff --git a/render/toml.go b/render/toml.go index 40f044c8..2cd1f212 100644 --- a/render/toml.go +++ b/render/toml.go @@ -26,6 +26,8 @@ func (r TOML) Render(w http.ResponseWriter) error { return err } + writeContentLength(w, len(bytes)) + _, err = w.Write(bytes) return err } diff --git a/render/yaml.go b/render/yaml.go index fc927c1f..fbc1e623 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -26,6 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error { return err } + writeContentLength(w, len(bytes)) + _, err = w.Write(bytes) return err }