mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-23 01:57:55 +08:00
Merge branch 'master' into features/form-binding-time-improve
This commit is contained in:
commit
db0a0de7ad
45
README.md
45
README.md
@ -40,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||||
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering)
|
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
|
||||||
- [JSONP rendering](#jsonp)
|
- [JSONP rendering](#jsonp)
|
||||||
- [Serving static files](#serving-static-files)
|
- [Serving static files](#serving-static-files)
|
||||||
- [Serving data from reader](#serving-data-from-reader)
|
- [Serving data from reader](#serving-data-from-reader)
|
||||||
@ -871,7 +871,7 @@ Test it with:
|
|||||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||||
```
|
```
|
||||||
|
|
||||||
### XML, JSON and YAML rendering
|
### XML, JSON, YAML and ProtoBuf rendering
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -905,6 +905,19 @@ func main() {
|
|||||||
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.GET("/someProtoBuf", func(c *gin.Context) {
|
||||||
|
reps := []int64{int64(1), int64(2)}
|
||||||
|
label := "test"
|
||||||
|
// The specific definition of protobuf is written in the testdata/protoexample file.
|
||||||
|
data := &protoexample.Test{
|
||||||
|
Label: &label,
|
||||||
|
Reps: reps,
|
||||||
|
}
|
||||||
|
// Note that data becomes binary data in the response
|
||||||
|
// Will output protoexample.Test protobuf serialized data
|
||||||
|
c.ProtoBuf(http.StatusOK, data)
|
||||||
|
})
|
||||||
|
|
||||||
// Listen and serve on 0.0.0.0:8080
|
// Listen and serve on 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
@ -978,6 +991,34 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### PureJSON
|
||||||
|
|
||||||
|
Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead.
|
||||||
|
This feature is unavailable in Go 1.6 and lower.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Serves unicode entities
|
||||||
|
r.GET("/json", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"html": "<b>Hello, world!</b>",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serves literal characters
|
||||||
|
r.GET("/purejson", func(c *gin.Context) {
|
||||||
|
c.PureJSON(200, gin.H{
|
||||||
|
"html": "<b>Hello, world!</b>",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Serving static files
|
### Serving static files
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -783,6 +783,11 @@ func (c *Context) YAML(code int, obj interface{}) {
|
|||||||
c.Render(code, render.YAML{Data: obj})
|
c.Render(code, render.YAML{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
|
||||||
|
func (c *Context) ProtoBuf(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.ProtoBuf{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// String writes the given string into the response body.
|
// String writes the given string into the response body.
|
||||||
func (c *Context) String(code int, format string, values ...interface{}) {
|
func (c *Context) String(code int, format string, values ...interface{}) {
|
||||||
c.Render(code, render.String{Format: format, Data: values})
|
c.Render(code, render.String{Format: format, Data: values})
|
||||||
|
17
context_17.go
Normal file
17
context_17.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PureJSON serializes the given struct as JSON into the response body.
|
||||||
|
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
||||||
|
func (c *Context) PureJSON(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.PureJSON{Data: obj})
|
||||||
|
}
|
27
context_17_test.go
Normal file
27
context_17_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that the response is serialized as JSON
|
||||||
|
// and Content-Type is set to application/json
|
||||||
|
// and special HTML characters are preserved
|
||||||
|
func TestContextRenderPureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
|
}
|
@ -20,8 +20,11 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-contrib/sse"
|
"github.com/gin-contrib/sse"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ context.Context = &Context{}
|
var _ context.Context = &Context{}
|
||||||
@ -616,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) {
|
|||||||
|
|
||||||
// Tests that the response is serialized as JSON
|
// Tests that the response is serialized as JSON
|
||||||
// and Content-Type is set to application/json
|
// and Content-Type is set to application/json
|
||||||
|
// and special HTML characters are escaped
|
||||||
func TestContextRenderJSON(t *testing.T) {
|
func TestContextRenderJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, H{"foo": "bar"})
|
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||||
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -954,6 +958,30 @@ func TestContextRenderYAML(t *testing.T) {
|
|||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
|
||||||
|
// and Content-Type is set to application/x-protobuf
|
||||||
|
// and we just use the example protobuf to check if the response is correct
|
||||||
|
func TestContextRenderProtoBuf(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
reps := []int64{int64(1), int64(2)}
|
||||||
|
label := "test"
|
||||||
|
data := &testdata.Test{
|
||||||
|
Label: &label,
|
||||||
|
Reps: reps,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ProtoBuf(http.StatusCreated, data)
|
||||||
|
|
||||||
|
protoData, err := proto.Marshal(data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Equal(t, string(protoData[:]), w.Body.String())
|
||||||
|
assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextHeaders(t *testing.T) {
|
func TestContextHeaders(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Header("Content-Type", "text/plain")
|
c.Header("Content-Type", "text/plain")
|
||||||
|
@ -12,4 +12,5 @@ var (
|
|||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
MarshalIndent = json.MarshalIndent
|
MarshalIndent = json.MarshalIndent
|
||||||
NewDecoder = json.NewDecoder
|
NewDecoder = json.NewDecoder
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
)
|
)
|
||||||
|
0
render/json.go
Executable file → Normal file
0
render/json.go
Executable file → Normal file
28
render/json_17.go
Normal file
28
render/json_17.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PureJSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
return encoder.Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
|
|||||||
|
|
||||||
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
||||||
writeContentType(w, msgpackContentType)
|
writeContentType(w, msgpackContentType)
|
||||||
var h codec.Handle = new(codec.MsgpackHandle)
|
var mh codec.MsgpackHandle
|
||||||
return codec.NewEncoder(w, h).Encode(obj)
|
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||||
}
|
}
|
||||||
|
33
render/protobuf.go
Normal file
33
render/protobuf.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtoBuf struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var protobufContentType = []string{"application/x-protobuf"}
|
||||||
|
|
||||||
|
func (r ProtoBuf) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := proto.Marshal(r.Data.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, protobufContentType)
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
1
render/render.go
Executable file → Normal file
1
render/render.go
Executable file → Normal file
@ -27,6 +27,7 @@ var (
|
|||||||
_ Render = MsgPack{}
|
_ Render = MsgPack{}
|
||||||
_ Render = Reader{}
|
_ Render = Reader{}
|
||||||
_ Render = AsciiJSON{}
|
_ Render = AsciiJSON{}
|
||||||
|
_ Render = ProtoBuf{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
26
render/render_17_test.go
Normal file
26
render/render_17_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2018 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenderPureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
|
}
|
||||||
|
err := (PureJSON{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
37
render/render_test.go
Executable file → Normal file
37
render/render_test.go
Executable file → Normal file
@ -15,8 +15,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
|
|
||||||
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO unit tests
|
// TODO unit tests
|
||||||
@ -49,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) {
|
|||||||
func TestRenderJSON(t *testing.T) {
|
func TestRenderJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
}
|
}
|
||||||
|
|
||||||
(JSON{data}).WriteContentType(w)
|
(JSON{data}).WriteContentType(w)
|
||||||
@ -58,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
|
|||||||
err := (JSON{data}).Render(w)
|
err := (JSON{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
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, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,6 +269,35 @@ func TestRenderYAMLFail(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test Protobuf rendering
|
||||||
|
func TestRenderProtoBuf(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reps := []int64{int64(1), int64(2)}
|
||||||
|
label := "test"
|
||||||
|
data := &testdata.Test{
|
||||||
|
Label: &label,
|
||||||
|
Reps: reps,
|
||||||
|
}
|
||||||
|
|
||||||
|
(ProtoBuf{data}).WriteContentType(w)
|
||||||
|
protoData, err := proto.Marshal(data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
err = (ProtoBuf{data}).Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, string(protoData[:]), w.Body.String())
|
||||||
|
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderProtoBufFail(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := &testdata.Test{}
|
||||||
|
err := (ProtoBuf{data}).Render(w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderXML(t *testing.T) {
|
func TestRenderXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := xmlmap{
|
data := xmlmap{
|
||||||
|
@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
|||||||
writeContentType(w, plainContentType)
|
writeContentType(w, plainContentType)
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
fmt.Fprintf(w, format, data...)
|
fmt.Fprintf(w, format, data...)
|
||||||
} else {
|
return
|
||||||
io.WriteString(w, format)
|
|
||||||
}
|
}
|
||||||
|
io.WriteString(w, format)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user