Skip to content

Commit

Permalink
Add Jsonp Support to Context (#1333)
Browse files Browse the repository at this point in the history
  • Loading branch information
w169q169 authored and appleboy committed Apr 26, 2018
1 parent 41f951e commit 8c24018
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 0 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [Serving static files](#serving-static-files)
- [HTML rendering](#html-rendering)
- [Multitemplate](#multitemplate)
Expand Down Expand Up @@ -861,6 +862,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

Expand Down
7 changes: 7 additions & 0 deletions context.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) {
Expand Down
14 changes: 14 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
32 changes: 32 additions & 0 deletions render/json.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package render

import (
"bytes"
"html/template"
"net/http"

"github.com/gin-gonic/gin/json"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions render/render.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
_ Render = JSON{}
_ Render = IndentedJSON{}
_ Render = SecureJSON{}
_ Render = JsonpJSON{}
_ Render = XML{}
_ Render = String{}
_ Render = Redirect{}
Expand Down
37 changes: 37 additions & 0 deletions render/render_test.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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 := (JsonpJSON{"x", data}).Render(w)
assert.Error(t, err)
}

type xmlmap map[string]interface{}

// Allows type H to be used with xml.Marshal
Expand Down

0 comments on commit 8c24018

Please sign in to comment.