Merge branch 'master' into master

This commit is contained in:
youngbloood 2018-08-22 18:08:16 +08:00 committed by GitHub
commit f96984d6df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 275 additions and 29 deletions

View File

@ -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 HTML checkboxes](#bind-html-checkboxes)
- [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)
- [Serving static files](#serving-static-files)
- [Serving data from reader](#serving-data-from-reader)
@ -750,9 +750,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco
```go
package main
import "log"
import "github.com/gin-gonic/gin"
import "time"
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string `form:"name"`
@ -871,7 +874,7 @@ Test it with:
$ curl -v --form user=user --form password=password http://localhost:8080/login
```
### XML, JSON and YAML rendering
### XML, JSON, YAML and ProtoBuf rendering
```go
func main() {
@ -905,6 +908,19 @@ func main() {
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
r.Run(":8080")
}
@ -978,6 +994,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
```go

View File

@ -74,16 +74,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
}
}
val.Field(i).Set(slice)
} else {
if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
return err
}
continue
}
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
continue
}
if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
return err
}
continue
}
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
}
}
return nil

View File

@ -668,9 +668,9 @@ func (c *Context) Status(code int) {
func (c *Context) Header(key, value string) {
if value == "" {
c.Writer.Header().Del(key)
} else {
c.Writer.Header().Set(key, value)
return
}
c.Writer.Header().Set(key, value)
}
// GetHeader returns value from request headers.
@ -758,9 +758,9 @@ func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
} else {
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
}
// JSON serializes the given struct as JSON into the response body.
@ -786,6 +786,11 @@ func (c *Context) YAML(code int, obj interface{}) {
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.
func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.String{Format: format, Data: values})

17
context_17.go Normal file
View 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
View 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"))
}

View File

@ -20,8 +20,11 @@ import (
"github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
testdata "github.com/gin-gonic/gin/testdata/protoexample"
)
var _ context.Context = &Context{}
@ -616,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) {
// Tests that the response is serialized as JSON
// and Content-Type is set to application/json
// and special HTML characters are escaped
func TestContextRenderJSON(t *testing.T) {
w := httptest.NewRecorder()
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, "{\"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"))
}
@ -954,6 +958,30 @@ func TestContextRenderYAML(t *testing.T) {
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) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Header("Content-Type", "text/plain")

View File

@ -12,4 +12,5 @@ var (
Marshal = json.Marshal
MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder
NewEncoder = json.NewEncoder
)

View File

@ -13,4 +13,5 @@ var (
Marshal = json.Marshal
MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder
NewEncoder = json.NewEncoder
)

6
render/json.go Executable file → Normal file
View File

@ -128,10 +128,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
var buffer bytes.Buffer
for _, r := range string(ret) {
cvt := ""
if r < 128 {
cvt = string(r)
} else {
cvt := string(r)
if r >= 128 {
cvt = fmt.Sprintf("\\u%04x", int64(r))
}
buffer.WriteString(cvt)

28
render/json_17.go Normal file
View 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)
}

View File

@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, msgpackContentType)
var h codec.Handle = new(codec.MsgpackHandle)
return codec.NewEncoder(w, h).Encode(obj)
var mh codec.MsgpackHandle
return codec.NewEncoder(w, &mh).Encode(obj)
}

33
render/protobuf.go Normal file
View 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)
}

View File

@ -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
import (

1
render/render.go Executable file → Normal file
View File

@ -27,6 +27,7 @@ var (
_ Render = MsgPack{}
_ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
)
func writeContentType(w http.ResponseWriter, value []string) {

26
render/render_17_test.go Normal file
View 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
View File

@ -15,8 +15,11 @@ import (
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
testdata "github.com/gin-gonic/gin/testdata/protoexample"
)
// TODO unit tests
@ -49,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) {
func TestRenderJSON(t *testing.T) {
w := httptest.NewRecorder()
data := map[string]interface{}{
"foo": "bar",
"foo": "bar",
"html": "<b>",
}
(JSON{data}).WriteContentType(w)
@ -58,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
err := (JSON{data}).Render(w)
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"))
}
@ -265,6 +269,35 @@ func TestRenderYAMLFail(t *testing.T) {
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) {
w := httptest.NewRecorder()
data := xmlmap{

View File

@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) {
writeContentType(w, plainContentType)
if len(data) > 0 {
fmt.Fprintf(w, format, data...)
} else {
io.WriteString(w, format)
return
}
io.WriteString(w, format)
}