From 15a6d567a2fb8aa48d0d8b2fa0396630efddaf30 Mon Sep 17 00:00:00 2001 From: w169q169 Date: Wed, 25 Apr 2018 14:06:45 +0800 Subject: [PATCH 1/6] add jsonp support --- context.go | 7 +++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) mode change 100644 => 100755 context.go mode change 100644 => 100755 render/json.go mode change 100644 => 100755 render/render.go mode change 100644 => 100755 render/render_test.go diff --git a/context.go b/context.go old mode 100644 new mode 100755 index 90d4c6e59e..be205f45be --- a/context.go +++ b/context.go @@ -670,6 +670,13 @@ func (c *Context) SecureJSON(code int, obj interface{}) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) } +// JSONP serializes the given struct as JSON into the response body. +// It add padding to response body to request data from a server residing in a different domain than the client. +// It also sets the Content-Type as "application/javascript". +func (c *Context) JSONP(code int, obj interface{}) { + c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) +} + // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { diff --git a/render/json.go b/render/json.go old mode 100644 new mode 100755 index eb2548e2ad..3a2e8b2fcf --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "html/template" "net/http" "github.com/gin-gonic/gin/json" @@ -24,9 +25,15 @@ type SecureJSON struct { Data interface{} } +type JsonpJSON struct { + Callback string + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} +var jsonpContentType = []string{"application/javascript; charset=utf-8"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -80,3 +87,28 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } + +func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + if r.Callback == "" { + w.Write(ret) + return nil + } + + callback := template.JSEscapeString(r.Callback) + w.Write([]byte(callback)) + w.Write([]byte("(")) + w.Write(ret) + w.Write([]byte(")")) + + return nil +} + +func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonpContentType) +} diff --git a/render/render.go b/render/render.go old mode 100644 new mode 100755 index 7185236426..578f2784a4 --- a/render/render.go +++ b/render/render.go @@ -15,6 +15,7 @@ var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} + _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} diff --git a/render/render_test.go b/render/render_test.go old mode 100644 new mode 100755 index d825d04184..0ca1355e37 --- a/render/render_test.go +++ b/render/render_test.go @@ -128,6 +128,43 @@ func TestRenderSecureJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderJsonpJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (JsonpJSON{"x", data}).WriteContentType(w1) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + err1 := (JsonpJSON{"x", data}).Render(w1) + + 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")) + + w2 := httptest.NewRecorder() + datas := []map[string]interface{}{{ + "foo": "bar", + }, { + "bar": "foo", + }} + + err2 := (JsonpJSON{"x", datas}).Render(w2) + 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")) +} + +func TestRenderJsonpJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (SecureJSON{"x", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 55fb204001b435ed3d7ae9729f484f99cb94226b Mon Sep 17 00:00:00 2001 From: w169q169 Date: Wed, 25 Apr 2018 14:41:02 +0800 Subject: [PATCH 2/6] document jsonp on the README --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 6c8988e779..481ef36c9e 100644 --- a/README.md +++ b/README.md @@ -861,6 +861,28 @@ func main() { r.Run(":8080") } ``` +#### JSONP + +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. + +```go +func main() { + r := gin.Default() + + r.GET("/JSONP?callback=x", func(c *gin.Context) { + data := map[string]interface{}{ + "foo": "bar", + } + + //callback is x + // Will output : x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` ### Serving static files From abd2ec4372de2adcd03984bba20ed528aad8e60d Mon Sep 17 00:00:00 2001 From: w169q169 Date: Thu, 26 Apr 2018 08:42:55 +0800 Subject: [PATCH 3/6] add api document on the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 481ef36c9e..2806d979e5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [JSONP rendering](#jsonp-rendering) - [Serving static files](#serving-static-files) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) From c5325c190e5836da0b48d26fc8aa1fc31707873d Mon Sep 17 00:00:00 2001 From: w169q169 Date: Thu, 26 Apr 2018 08:48:43 +0800 Subject: [PATCH 4/6] add api document on the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2806d979e5..aba9ef3155 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - - [JSONP rendering](#jsonp-rendering) + - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) From db85d1227e997a55862ff652ea9eaa28f4115d03 Mon Sep 17 00:00:00 2001 From: w169q169 Date: Thu, 26 Apr 2018 09:24:29 +0800 Subject: [PATCH 5/6] add jsonp test on the context --- context_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/context_test.go b/context_test.go index 9024cfc127..841d8af052 100644 --- a/context_test.go +++ b/context_test.go @@ -581,6 +581,20 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/javascript +func TestContextRenderJSONP(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() From 5985384809bd0ad415d124bc18b30ba2b4718f7e Mon Sep 17 00:00:00 2001 From: w169q169 Date: Thu, 26 Apr 2018 09:41:11 +0800 Subject: [PATCH 6/6] add jsonp fail test --- render/render_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/render_test.go b/render/render_test.go index 0ca1355e37..c7b51ef468 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -161,7 +161,7 @@ func TestRenderJsonpJSONFail(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - err := (SecureJSON{"x", data}).Render(w) + err := (JsonpJSON{"x", data}).Render(w) assert.Error(t, err) }