add sync.Pool to manager JSON,XML,YAML,ProtoBuf,MsgPack Render object.

add sync.Pool to manager JSON,XML,YAML,ProtoBuf,MsgPack Render object.
we can move JSON,XML,YAML,ProtoBuf,MsgPack Render implement to sub-module in Gin or other repository module based on this change.
This commit is contained in:
Michael Li 2018-12-30 02:32:54 +08:00
parent 85b92cdf4b
commit b0275eda05
14 changed files with 600 additions and 81 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ count.out
test
profile.out
tmp.out
.idea/
*.iml

View File

@ -757,6 +757,14 @@ func (c *Context) Render(code int, r render.Render) {
}
}
// RenderWith writes the response headers and calls render.Render to render data and opts
func (c *Context) RenderWith(name int, code int, data interface{}, opts ...interface{}) {
r := render.Default(name)
r.Setup(data, opts...)
c.Render(code, r)
render.Recycle(name, r)
}
// HTML renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/
@ -770,14 +778,14 @@ func (c *Context) HTML(code int, name string, obj interface{}) {
// WARNING: we recommend to use this only for development purposes since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead.
func (c *Context) IndentedJSON(code int, obj interface{}) {
c.Render(code, render.IndentedJSON{Data: obj})
c.RenderWith(render.IntendedJSONRenderType, code, obj)
}
// SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json".
func (c *Context) SecureJSON(code int, obj interface{}) {
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
c.RenderWith(render.SecureJSONRenderType, code, obj, c.engine.secureJsonPrefix)
}
// JSONP serializes the given struct as JSON into the response body.
@ -785,39 +793,44 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
// It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
if callback != "" {
c.RenderWith(render.JsonpJSONRenderType, code, obj, callback)
} else {
c.RenderWith(render.JSONRenderType, code, obj)
}
c.Render(code, render.JsonpJSON{Callback: 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{}) {
c.Render(code, render.JSON{Data: obj})
c.RenderWith(render.JSONRenderType, code, obj)
}
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
// It also sets the Content-Type as "application/json".
func (c *Context) AsciiJSON(code int, obj interface{}) {
c.Render(code, render.AsciiJSON{Data: obj})
c.RenderWith(render.AsciiJSONRenderType, code, obj)
}
// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj})
c.RenderWith(render.XMLRenderType, code, obj)
}
// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj})
c.RenderWith(render.YAMLRenderType, code, 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})
c.RenderWith(render.ProtoBufRenderType, code, obj)
}
// MsgPack serializes the given struct as ProtoBuf into the response body.
func (c *Context) MsgPack(code int, obj interface{}) {
c.RenderWith(render.MsgPackRenderType, code, obj)
}
// String writes the given string into the response body.

View File

@ -15,7 +15,7 @@ import (
// 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})
c.RenderWith(render.PureJSONRenderType, code, obj)
}
/************************************/

View File

@ -8,6 +8,7 @@ import (
"bytes"
"errors"
"fmt"
"github.com/ugorji/go/codec"
"html/template"
"io"
"mime/multipart"
@ -840,6 +841,25 @@ func TestContextRenderXML(t *testing.T) {
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextRenderMsgPack(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
data := H{"foo": "bar"}
c.MsgPack(http.StatusCreated, H{"foo": "bar"})
h := new(codec.MsgpackHandle)
assert.NotNil(t, h)
buf := bytes.NewBuffer([]byte{})
assert.NotNil(t, buf)
err := codec.NewEncoder(buf, h).Encode(data)
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, w.Body.String(), string(buf.Bytes()))
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no XML is rendered if code is 204
func TestContextRenderNoContentXML(t *testing.T) {
w := httptest.NewRecorder()

40
render/empty.go Normal file
View File

@ -0,0 +1,40 @@
package render
import (
"fmt"
"net/http"
)
type EmptyRenderFactory struct{}
type EmptyRender struct{}
func init() {
Register(EmptyRenderType, EmptyRenderFactory{})
}
// Instance apply opts to build a new EmptyRender instance
func (EmptyRenderFactory) Instance() RenderRecycler {
return &EmptyRender{}
}
// Render writes data with custom ContentType.
func (r *EmptyRender) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
return fmt.Errorf("empty render,you need register one first")
}
// WriteContentType writes custom ContentType.
func (r *EmptyRender) WriteContentType(w http.ResponseWriter) {
// Empty
}
// Setup set data and opts
func (r *EmptyRender) Setup(data interface{}, opts ...interface{}) {
// Empty
}
// Reset clean data and opts
func (r *EmptyRender) Reset() {
// Empty
}

View File

@ -13,6 +13,14 @@ import (
"github.com/gin-gonic/gin/internal/json"
)
func init() {
Register(JSONRenderType, JSONFactory{})
Register(IntendedJSONRenderType, IndentedJSONFactory{})
Register(JsonpJSONRenderType, JsonpJSONFactory{})
Register(SecureJSONRenderType, SecureJSONFactory{})
Register(AsciiJSONRenderType, AsciiJSONFactory{})
}
// JSON contains the given interface object.
type JSON struct {
Data interface{}
@ -40,6 +48,21 @@ type AsciiJSON struct {
Data interface{}
}
// JSONFactory instance the JSON object.
type JSONFactory struct{}
// IndentedJSONFactory instance the IndentedJSON object.
type IndentedJSONFactory struct{}
// SecureJSONFactory instance the SecureJSON object.
type SecureJSONFactory struct{}
// JsonpJSONFactory instance the JsonpJSON object.
type JsonpJSONFactory struct{}
// AsciiJSONFactory instance the AsciiJSON object.
type AsciiJSONFactory struct{}
// SecureJSONPrefix is a string which represents SecureJSON prefix.
type SecureJSONPrefix string
@ -47,8 +70,18 @@ var jsonContentType = []string{"application/json; charset=utf-8"}
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
var jsonAsciiContentType = []string{"application/json"}
// Setup set data and opts
func (r *JSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *JSON) Reset() {
r.Data = nil
}
// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) {
func (r *JSON) Render(w http.ResponseWriter) (err error) {
if err = WriteJSON(w, r.Data); err != nil {
panic(err)
}
@ -56,7 +89,7 @@ func (r JSON) Render(w http.ResponseWriter) (err error) {
}
// WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) {
func (r *JSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
@ -71,8 +104,18 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
return nil
}
// Setup set data and opts
func (r *IndentedJSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *IndentedJSON) Reset() {
r.Data = nil
}
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
func (r IndentedJSON) Render(w http.ResponseWriter) error {
func (r *IndentedJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
if err != nil {
@ -83,12 +126,26 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
}
// WriteContentType (IndentedJSON) writes JSON ContentType.
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
func (r *IndentedJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
// Setup set data and opts
func (r *SecureJSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
if len(opts) == 1 {
r.Prefix, _ = opts[0].(string)
}
}
// Reset clean data and opts
func (r *SecureJSON) Reset() {
r.Data = nil
r.Prefix = ""
}
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
func (r SecureJSON) Render(w http.ResponseWriter) error {
func (r *SecureJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
jsonBytes, err := json.Marshal(r.Data)
if err != nil {
@ -103,12 +160,26 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
}
// WriteContentType (SecureJSON) writes JSON ContentType.
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
func (r *SecureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
// Setup set data and opts
func (r *JsonpJSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
if len(opts) == 1 {
r.Callback, _ = opts[0].(string)
}
}
// Reset clean data and opts
func (r *JsonpJSON) Reset() {
r.Data = nil
r.Callback = ""
}
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
func (r *JsonpJSON) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w)
ret, err := json.Marshal(r.Data)
if err != nil {
@ -130,12 +201,22 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
}
// WriteContentType (JsonpJSON) writes Javascript ContentType.
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
func (r *JsonpJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonpContentType)
}
// Setup set data and opts
func (r *AsciiJSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *AsciiJSON) Reset() {
r.Data = nil
}
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
func (r *AsciiJSON) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w)
ret, err := json.Marshal(r.Data)
if err != nil {
@ -156,6 +237,31 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
}
// WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
func (r *AsciiJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonAsciiContentType)
}
// Instance a new JSON object.
func (JSONFactory) Instance() RenderRecycler {
return &JSON{}
}
// Instance a new IndentedJSON object.
func (IndentedJSONFactory) Instance() RenderRecycler {
return &IndentedJSON{}
}
// Instance a new SecureJSON object.
func (SecureJSONFactory) Instance() RenderRecycler {
return &SecureJSON{}
}
// Instance a new JsonpJSON object.
func (JsonpJSONFactory) Instance() RenderRecycler {
return &JsonpJSON{}
}
// Instance a new AsciiJSON object.
func (AsciiJSONFactory) Instance() RenderRecycler {
return &AsciiJSON{}
}

View File

@ -12,11 +12,28 @@ import (
"github.com/gin-gonic/gin/internal/json"
)
func init() {
Register(PureJSONRenderType, &PureJsonFactory{})
}
// PureJSON contains the given interface object.
type PureJSON struct {
Data interface{}
}
// Setup set data and opts
func (r *PureJSON) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *PureJSON) Reset() {
r.Data = nil
}
// JSONFactory instance the PureJson object.
type PureJsonFactory struct{}
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
func (r PureJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
@ -29,3 +46,8 @@ func (r PureJSON) Render(w http.ResponseWriter) error {
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
// Instance a new Render instance
func (PureJsonFactory) Instance() RenderRecycler {
return &PureJSON{}
}

View File

@ -10,26 +10,48 @@ import (
"github.com/ugorji/go/codec"
)
func init() {
Register(MsgPackRenderType, MsgPackFactory{})
}
// MsgPack contains the given interface object.
type MsgPack struct {
Data interface{}
}
// MsgPackFactory instance the MsgPack object.
type MsgPackFactory struct{}
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
// WriteContentType (MsgPack) writes MsgPack ContentType.
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
writeContentType(w, msgpackContentType)
// Setup set data and opts
func (r *MsgPack) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *MsgPack) Reset() {
r.Data = nil
}
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
func (r MsgPack) Render(w http.ResponseWriter) error {
func (r *MsgPack) Render(w http.ResponseWriter) error {
return WriteMsgPack(w, r.Data)
}
// WriteContentType (MsgPack) writes MsgPack ContentType.
func (r *MsgPack) WriteContentType(w http.ResponseWriter) {
writeContentType(w, msgpackContentType)
}
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, msgpackContentType)
var mh codec.MsgpackHandle
return codec.NewEncoder(w, &mh).Encode(obj)
}
// Instance a new MsgPack object.
func (MsgPackFactory) Instance() RenderRecycler {
return &MsgPack{}
}

View File

@ -10,15 +10,32 @@ import (
"github.com/golang/protobuf/proto"
)
func init() {
Register(ProtoBufRenderType, ProtoBufFactory{})
}
// ProtoBuf contains the given interface object.
type ProtoBuf struct {
Data interface{}
}
// ProtoBufFactory instance the ProtoBuf object.
type ProtoBufFactory struct{}
var protobufContentType = []string{"application/x-protobuf"}
// Setup set data and opts
func (r *ProtoBuf) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *ProtoBuf) Reset() {
r.Data = nil
}
// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.
func (r ProtoBuf) Render(w http.ResponseWriter) error {
func (r *ProtoBuf) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
bytes, err := proto.Marshal(r.Data.(proto.Message))
@ -31,6 +48,11 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error {
}
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
func (r *ProtoBuf) WriteContentType(w http.ResponseWriter) {
writeContentType(w, protobufContentType)
}
// Instance a new ProtoBuf object.
func (ProtoBufFactory) Instance() RenderRecycler {
return &ProtoBuf{}
}

View File

@ -4,7 +4,30 @@
package render
import "net/http"
import (
"net/http"
"sync"
)
// RenderType Names
const (
JSONRenderType = iota // JSON Render Type
IntendedJSONRenderType // IntendedJSON Render Type
PureJSONRenderType // PureJSON Render Type
AsciiJSONRenderType // AsciiJSON Render Type
JsonpJSONRenderType // JsonpJSON Render Type
SecureJSONRenderType // SecureJSON Type
XMLRenderType // XML Render Type
YAMLRenderType // YAML Render Type
MsgPackRenderType // MsgPack Render Type
ProtoBufRenderType // ProtoBuf Render Type
EmptyRenderType // Empty Render Type
unknownRenderType // Unknown Render Type,just used for test
)
var (
renderPool = &RenderPool{renderPools: make(map[int]sync.Pool)}
)
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
type Render interface {
@ -14,23 +37,103 @@ type Render interface {
WriteContentType(w http.ResponseWriter)
}
// RenderRecycler interface is to be implemented by JSON, XML, HTML, YAML and so on.
type RenderRecycler interface {
Render
// Setup set data and opts
Setup(data interface{}, opts ...interface{})
// Reset clean data and opts
Reset()
}
// RenderFactory interface is to be implemented by other Render.
type RenderFactory interface {
// Instance a new RenderRecycler instance
Instance() RenderRecycler
}
// RenderPool contains Render instance
type RenderPool struct {
mu sync.RWMutex
renderPools map[int]sync.Pool
}
func (p *RenderPool) get(name int) RenderRecycler {
p.mu.RLock()
defer p.mu.RUnlock()
var render RenderRecycler
if pool, ok := p.renderPools[name]; ok {
render, _ = pool.Get().(RenderRecycler)
} else {
pool, _ = p.renderPools[EmptyRenderType]
render, _ = pool.Get().(RenderRecycler)
}
return render
}
func (p *RenderPool) put(name int, render RenderRecycler) {
p.mu.RLock()
defer p.mu.RUnlock()
if pool, ok := p.renderPools[name]; ok {
render.Reset()
pool.Put(render)
}
}
func (p *RenderPool) register(name int, factory RenderFactory) {
p.mu.Lock()
defer p.mu.Unlock()
if factory == nil {
panic("gin: Register RenderFactory is nil")
}
if _, dup := p.renderPools[name]; dup {
panic("gin: Register called twice for RenderFactory")
}
p.renderPools[name] = sync.Pool{
New: func() interface{} {
return factory.Instance()
},
}
}
// Register makes a binding available by the provided name.
// If Register is called twice with the same name or if binding is nil,
// it panics.
func Register(name int, factory RenderFactory) {
renderPool.register(name, factory)
}
// Default returns the appropriate Render instance based on the render type.
func Default(name int) RenderRecycler {
return renderPool.get(name)
}
// Recycle put render to sync.Pool
func Recycle(name int, render RenderRecycler) {
renderPool.put(name, render)
}
var (
_ Render = JSON{}
_ Render = IndentedJSON{}
_ Render = SecureJSON{}
_ Render = JsonpJSON{}
_ Render = XML{}
_ RenderRecycler = &JSON{}
_ RenderRecycler = &IndentedJSON{}
_ RenderRecycler = &SecureJSON{}
_ RenderRecycler = &JsonpJSON{}
_ RenderRecycler = &XML{}
_ Render = String{}
_ Render = Redirect{}
_ Render = Data{}
_ Render = HTML{}
_ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ Render = MsgPack{}
_ RenderRecycler = &YAML{}
_ RenderRecycler = &MsgPack{}
_ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
_ RenderRecycler = &AsciiJSON{}
_ RenderRecycler = &ProtoBuf{}
)
func writeContentType(w http.ResponseWriter, value []string) {

View File

@ -19,7 +19,10 @@ func TestRenderPureJSON(t *testing.T) {
"foo": "bar",
"html": "<b>",
}
err := (PureJSON{data}).Render(w)
r := Default(PureJSONRenderType)
r.Setup(data)
err := r.Render(w)
Recycle(PureJSONRenderType, r)
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"))

View File

@ -25,17 +25,60 @@ import (
// TODO unit tests
// test errors
func TestRenderEmpty(t *testing.T) {
w := httptest.NewRecorder()
data := "empty data"
r := Default(EmptyRenderType)
r.Setup(data)
err := r.Render(w)
Recycle(EmptyRenderType, r)
assert.EqualError(t, err, "empty render,you need register one first")
}
// test not registered render type
func TestRenderUnknown(t *testing.T) {
r := Default(unknownRenderType)
_, ok := r.(*EmptyRender)
assert.True(t, ok)
}
func TestRenderRegisterNil(t *testing.T) {
defer func() {
if err := recover(); err != nil {
msg, _ := err.(string)
assert.Equal(t, msg, "gin: Register RenderFactory is nil")
}
}()
Register(unknownRenderType, nil)
}
func TestRenderRegisterDup(t *testing.T) {
defer func() {
if err := recover(); err != nil {
msg, _ := err.(string)
assert.Equal(t, msg, "gin: Register called twice for RenderFactory")
}
}()
Register(EmptyRenderType, EmptyRenderFactory{})
}
func TestRenderMsgPack(t *testing.T) {
w := httptest.NewRecorder()
data := map[string]interface{}{
"foo": "bar",
}
(MsgPack{data}).WriteContentType(w)
r := Default(MsgPackRenderType)
r.Setup(data)
r.WriteContentType(w)
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
err := (MsgPack{data}).Render(w)
r.Reset()
r.Setup(data)
err := r.Render(w)
Recycle(MsgPackRenderType, r)
assert.NoError(t, err)
h := new(codec.MsgpackHandle)
@ -56,10 +99,15 @@ func TestRenderJSON(t *testing.T) {
"html": "<b>",
}
(JSON{data}).WriteContentType(w)
r := Default(JSONRenderType)
r.Setup(data)
r.WriteContentType(w)
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
err := (JSON{data}).Render(w)
r.Reset()
r.Setup(data)
err := r.Render(w)
Recycle(JSONRenderType, r)
assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
@ -71,7 +119,12 @@ func TestRenderJSONPanics(t *testing.T) {
data := make(chan int)
// json: unsupported type: chan int
assert.Panics(t, func() { (JSON{data}).Render(w) })
assert.Panics(t, func() {
r := Default(JSONRenderType)
r.Setup(data)
r.Render(w)
Recycle(JSONRenderType, r)
})
}
func TestRenderIndentedJSON(t *testing.T) {
@ -81,7 +134,10 @@ func TestRenderIndentedJSON(t *testing.T) {
"bar": "foo",
}
err := (IndentedJSON{data}).Render(w)
r := Default(IntendedJSONRenderType)
r.Setup(data)
err := r.Render(w)
Recycle(IntendedJSONRenderType, r)
assert.NoError(t, err)
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String())
@ -93,7 +149,11 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
data := make(chan int)
// json: unsupported type: chan int
err := (IndentedJSON{data}).Render(w)
r := Default(IntendedJSONRenderType)
r.Setup(data)
err := r.Render(w)
Recycle(IntendedJSONRenderType, r)
assert.Error(t, err)
}
@ -103,10 +163,17 @@ func TestRenderSecureJSON(t *testing.T) {
"foo": "bar",
}
(SecureJSON{"while(1);", data}).WriteContentType(w1)
r := Default(SecureJSONRenderType)
r.Setup(data, "while(1);")
r.WriteContentType(w1)
Recycle(SecureJSONRenderType, r)
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
err1 := (SecureJSON{"while(1);", data}).Render(w1)
r1 := Default(SecureJSONRenderType)
r1.Setup(data, "while(1);")
err1 := r1.Render(w1)
Recycle(SecureJSONRenderType, r1)
assert.NoError(t, err1)
assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
@ -119,7 +186,11 @@ func TestRenderSecureJSON(t *testing.T) {
"bar": "foo",
}}
err2 := (SecureJSON{"while(1);", datas}).Render(w2)
r2 := Default(SecureJSONRenderType)
r2.Setup(datas, "while(1);")
err2 := r2.Render(w2)
Recycle(SecureJSONRenderType, r2)
assert.NoError(t, err2)
assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
@ -130,20 +201,31 @@ func TestRenderSecureJSONFail(t *testing.T) {
data := make(chan int)
// json: unsupported type: chan int
err := (SecureJSON{"while(1);", data}).Render(w)
r := Default(SecureJSONRenderType)
r.Setup(data, "while(1);")
err := r.Render(w)
Recycle(SecureJSONRenderType, r)
assert.Error(t, err)
}
func TestRenderJsonpJSON(t *testing.T) {
w1 := httptest.NewRecorder()
w := 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"))
r := Default(JsonpJSONRenderType)
r.Setup(data, "x")
r.WriteContentType(w)
Recycle(JsonpJSONRenderType, r)
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
err1 := (JsonpJSON{"x", data}).Render(w1)
w1 := httptest.NewRecorder()
r1 := Default(JsonpJSONRenderType)
r1.Setup(data, "x")
err1 := r1.Render(w1)
// Recycle(JsonpJSONRenderType, r1)
assert.NoError(t, err1)
assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String())
@ -156,7 +238,10 @@ func TestRenderJsonpJSON(t *testing.T) {
"bar": "foo",
}}
err2 := (JsonpJSON{"x", datas}).Render(w2)
r2 := Default(JsonpJSONRenderType)
r2.Setup(datas, "x")
err2 := r2.Render(w2)
Recycle(JsonpJSONRenderType, r2)
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"))
@ -167,10 +252,15 @@ func TestRenderJsonpJSONError2(t *testing.T) {
data := map[string]interface{}{
"foo": "bar",
}
(JsonpJSON{"", data}).WriteContentType(w)
r := Default(JsonpJSONRenderType)
r.Setup(data, "")
r.WriteContentType(w)
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
e := (JsonpJSON{"", data}).Render(w)
r.Reset()
r.Setup(data, "")
e := r.Render(w)
Recycle(JsonpJSONRenderType, r)
assert.NoError(t, e)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
@ -181,8 +271,11 @@ func TestRenderJsonpJSONFail(t *testing.T) {
w := httptest.NewRecorder()
data := make(chan int)
r := Default(JsonpJSONRenderType)
r.Setup(data, "x")
// json: unsupported type: chan int
err := (JsonpJSON{"x", data}).Render(w)
err := r.Render(w)
Recycle(JsonpJSONRenderType, r)
assert.Error(t, err)
}
@ -193,7 +286,9 @@ func TestRenderAsciiJSON(t *testing.T) {
"tag": "<br>",
}
err := (AsciiJSON{data1}).Render(w1)
r := Default(AsciiJSONRenderType)
r.Setup(data1)
err := r.Render(w1)
assert.NoError(t, err)
assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
@ -202,7 +297,10 @@ func TestRenderAsciiJSON(t *testing.T) {
w2 := httptest.NewRecorder()
data2 := float64(3.1415926)
err = (AsciiJSON{data2}).Render(w2)
r.Reset()
r.Setup(data2)
err = r.Render(w2)
Recycle(AsciiJSONRenderType, r)
assert.NoError(t, err)
assert.Equal(t, "3.1415926", w2.Body.String())
}
@ -211,8 +309,11 @@ func TestRenderAsciiJSONFail(t *testing.T) {
w := httptest.NewRecorder()
data := make(chan int)
r := Default(AsciiJSONRenderType)
r.Setup(data)
// json: unsupported type: chan int
assert.Error(t, (AsciiJSON{data}).Render(w))
assert.Error(t, r.Render(w))
Recycle(AsciiJSONRenderType, r)
}
type xmlmap map[string]interface{}
@ -247,10 +348,15 @@ b:
c: 2
d: [3, 4]
`
(YAML{data}).WriteContentType(w)
r := Default(YAMLRenderType)
r.Setup(data)
r.WriteContentType(w)
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
err := (YAML{data}).Render(w)
r.Reset()
r.Setup(data)
err := r.Render(w)
Recycle(YAMLRenderType, r)
assert.NoError(t, err)
assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String())
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
@ -265,7 +371,10 @@ func (ft *fail) MarshalYAML() (interface{}, error) {
func TestRenderYAMLFail(t *testing.T) {
w := httptest.NewRecorder()
err := (YAML{&fail{}}).Render(w)
r := Default(YAMLRenderType)
r.Setup(&fail{})
err := r.Render(w)
Recycle(YAMLRenderType, r)
assert.Error(t, err)
}
@ -279,12 +388,17 @@ func TestRenderProtoBuf(t *testing.T) {
Reps: reps,
}
(ProtoBuf{data}).WriteContentType(w)
r := Default(ProtoBufRenderType)
r.Setup(data)
r.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)
r.Reset()
r.Setup(data)
err = r.Render(w)
Recycle(ProtoBufRenderType, r)
assert.NoError(t, err)
assert.Equal(t, string(protoData), w.Body.String())
@ -294,7 +408,10 @@ func TestRenderProtoBuf(t *testing.T) {
func TestRenderProtoBufFail(t *testing.T) {
w := httptest.NewRecorder()
data := &testdata.Test{}
err := (ProtoBuf{data}).Render(w)
r := Default(ProtoBufRenderType)
r.Setup(data)
err := r.Render(w)
Recycle(ProtoBufRenderType, r)
assert.Error(t, err)
}
@ -304,10 +421,15 @@ func TestRenderXML(t *testing.T) {
"foo": "bar",
}
(XML{data}).WriteContentType(w)
r := Default(XMLRenderType)
r.Setup(data)
r.WriteContentType(w)
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
err := (XML{data}).Render(w)
r.Reset()
r.Setup(data)
err := r.Render(w)
Recycle(XMLRenderType, r)
assert.NoError(t, err)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())

View File

@ -9,20 +9,42 @@ import (
"net/http"
)
func init() {
Register(XMLRenderType, XMLFactory{})
}
// XML contains the given interface object.
type XML struct {
Data interface{}
}
// XMLFactory instance the XML object.
type XMLFactory struct{}
var xmlContentType = []string{"application/xml; charset=utf-8"}
// Setup set data and opts
func (r *XML) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *XML) Reset() {
r.Data = nil
}
// Render (XML) encodes the given interface object and writes data with custom ContentType.
func (r XML) Render(w http.ResponseWriter) error {
func (r *XML) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
return xml.NewEncoder(w).Encode(r.Data)
}
// WriteContentType (XML) writes XML ContentType for response.
func (r XML) WriteContentType(w http.ResponseWriter) {
func (r *XML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, xmlContentType)
}
// Instance a new XML object.
func (XMLFactory) Instance() RenderRecycler {
return &XML{}
}

View File

@ -10,15 +10,32 @@ import (
"gopkg.in/yaml.v2"
)
func init() {
Register(YAMLRenderType, YAMLFactory{})
}
// YAML contains the given interface object.
type YAML struct {
Data interface{}
}
// YAMLFactory instance the YAML object.
type YAMLFactory struct{}
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
// Setup set data and opts
func (r *YAML) Setup(data interface{}, opts ...interface{}) {
r.Data = data
}
// Reset clean data and opts
func (r *YAML) Reset() {
r.Data = nil
}
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
func (r YAML) Render(w http.ResponseWriter) error {
func (r *YAML) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
bytes, err := yaml.Marshal(r.Data)
@ -31,6 +48,11 @@ func (r YAML) Render(w http.ResponseWriter) error {
}
// WriteContentType (YAML) writes YAML ContentType for response.
func (r YAML) WriteContentType(w http.ResponseWriter) {
func (r *YAML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, yamlContentType)
}
// Instance a new Render instance
func (YAMLFactory) Instance() RenderRecycler {
return &YAML{}
}