Преглед на файлове

Add Jsonp Support to Context (#1333)

senhtry преди 7 години
родител
ревизия
8c24018290
променени са 6 файла, в които са добавени 114 реда и са изтрити 0 реда
  1. 23 0
      README.md
  2. 7 0
      context.go
  3. 14 0
      context_test.go
  4. 32 0
      render/json.go
  5. 1 0
      render/render.go
  6. 37 0
      render/render_test.go

+ 23 - 0
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)
     - [Serving static files](#serving-static-files)
     - [HTML rendering](#html-rendering)
     - [Multitemplate](#multitemplate)
@@ -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
 

+ 7 - 0
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{}) {

+ 14 - 0
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()

+ 32 - 0
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)
+}

+ 1 - 0
render/render.go

@@ -15,6 +15,7 @@ var (
 	_ Render     = JSON{}
 	_ Render     = IndentedJSON{}
 	_ Render     = SecureJSON{}
+	_ Render     = JsonpJSON{}
 	_ Render     = XML{}
 	_ Render     = String{}
 	_ Render     = Redirect{}

+ 37 - 0
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 := (JsonpJSON{"x", data}).Render(w)
+	assert.Error(t, err)
+}
+
 type xmlmap map[string]interface{}
 
 // Allows type H to be used with xml.Marshal