From f71e5432df3867f002d19bd5b71768bad40cc709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Wed, 16 May 2018 15:57:44 +0800 Subject: [PATCH 1/7] add render for ascii --- context.go | 6 ++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/context.go b/context.go index 462357e104..852b11a80c 100755 --- a/context.go +++ b/context.go @@ -708,6 +708,12 @@ func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } +// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. +// It also sets the Content-Type as "application/json". +func (c *Context) AsciiJSON(code int, obj interface{}) { + c.Render(code, render.AsciiJSON{Data: obj}) +} + // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { diff --git a/render/json.go b/render/json.go index 3a2e8b2fcf..772adc5035 100755 --- a/render/json.go +++ b/render/json.go @@ -8,6 +8,7 @@ import ( "bytes" "html/template" "net/http" + "strconv" "github.com/gin-gonic/gin/json" ) @@ -30,10 +31,15 @@ type JsonpJSON struct { Data interface{} } +type AsciiJSON struct { + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} +var jsonAsciiContentType = []string{"application/json"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -112,3 +118,29 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } + +func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + result := "" + for _, r := range ret { + cvt := "" + if r < 128 { + cvt = string(r) + } else { + cvt = `\u` + strconv.FormatInt(int64(r), 16) + } + result = result + cvt + } + + w.Write([]byte(result)) + return nil +} + +func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} From 98acb016cf17ad210f8f2798991d033c6c65a0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Wed, 16 May 2018 16:57:59 +0800 Subject: [PATCH 2/7] tests --- render/json.go | 8 ++++---- render/render_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/render/json.go b/render/json.go index 772adc5035..ceb0894f8c 100755 --- a/render/json.go +++ b/render/json.go @@ -6,9 +6,9 @@ package render import ( "bytes" + "fmt" "html/template" "net/http" - "strconv" "github.com/gin-gonic/gin/json" ) @@ -127,12 +127,12 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } result := "" - for _, r := range ret { + for _, r := range string(ret) { cvt := "" if r < 128 { cvt = string(r) } else { - cvt = `\u` + strconv.FormatInt(int64(r), 16) + cvt = fmt.Sprintf("\\u%04x", int64(r)) } result = result + cvt } @@ -142,5 +142,5 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonContentType) + writeContentType(w, jsonAsciiContentType) } diff --git a/render/render_test.go b/render/render_test.go index 40ec806ecc..2f728441d6 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -167,6 +167,35 @@ func TestRenderJsonpJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderAsciiJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data1 := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + err := (AsciiJSON{data1}).Render(w1) + + 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")) + + w2 := httptest.NewRecorder() + data2 := float64(3.1415926) + + err = (AsciiJSON{data2}).Render(w2) + assert.NoError(t, err) + assert.Equal(t, "3.1415926", w2.Body.String()) +} + +func TestRenderAsciiJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Error(t, (AsciiJSON{data}).Render(w)) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 7c8d6339c869af88196566fd12508922798dd540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Wed, 16 May 2018 17:12:55 +0800 Subject: [PATCH 3/7] add test for AsciiJSON in context --- context_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/context_test.go b/context_test.go index 12e02fa056..feded21cad 100644 --- a/context_test.go +++ b/context_test.go @@ -686,6 +686,17 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +func TestContextRenderNoContentAsciiJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.AsciiJSON(204, []string{"lang", "Go语言"}) + + assert.Equal(t, 204, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { From 64d474b2a207e01c425ef7d2dd9dc78267d2844f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Mon, 2 Jul 2018 12:04:13 +0800 Subject: [PATCH 4/7] change 204 to http.StatusNoContent --- context_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context_test.go b/context_test.go index feded21cad..6f37682ed9 100644 --- a/context_test.go +++ b/context_test.go @@ -690,9 +690,9 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AsciiJSON(204, []string{"lang", "Go语言"}) + c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) } From fe1abc529381e730e1a6beab2c68d6d51d3b31f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Mon, 2 Jul 2018 19:23:58 +0800 Subject: [PATCH 5/7] use buffer instead of concatenate --- render/json.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/render/json.go b/render/json.go index ceb0894f8c..6e5089a0c3 100755 --- a/render/json.go +++ b/render/json.go @@ -126,7 +126,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { return err } - result := "" + var buffer bytes.Buffer for _, r := range string(ret) { cvt := "" if r < 128 { @@ -134,10 +134,10 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } else { cvt = fmt.Sprintf("\\u%04x", int64(r)) } - result = result + cvt + buffer.WriteString(cvt) } - w.Write([]byte(result)) + w.Write(buffer.Bytes()) return nil } From 5748abf367f8b5a22e9c4c440b593fd9323a9beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Mon, 2 Jul 2018 21:02:58 +0800 Subject: [PATCH 6/7] add some comment in readme --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index e09a51f242..5cb81de342 100644 --- a/README.md +++ b/README.md @@ -900,6 +900,29 @@ func main() { } ``` +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Serving static files ```go From 9effe915249d00fd6196d044fdbf1f55c928abda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Mon, 2 Jul 2018 21:41:12 +0800 Subject: [PATCH 7/7] fix ident --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5cb81de342..93896469dc 100644 --- a/README.md +++ b/README.md @@ -914,7 +914,7 @@ func main() { "tag": "
", } - // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} c.AsciiJSON(http.StatusOK, data) })