From f7f798f40810828f4e6276b5167f25880e9c76e5 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Sat, 29 Dec 2018 23:51:47 +0800 Subject: [PATCH] render: add sync.Pool to manager JSON,XML,YAML,ProtoBuf,MsgPack Render object --- .gitignore | 2 + context.go | 35 +++++++--- context_17.go | 2 +- render/empty.go | 39 +++++++++++ render/json.go | 130 ++++++++++++++++++++++++++++++++--- render/json_17.go | 22 ++++++ render/msgpack.go | 30 ++++++-- render/protobuf.go | 26 ++++++- render/render.go | 139 ++++++++++++++++++++++++++++++++----- render/render_17_test.go | 5 +- render/render_test.go | 145 ++++++++++++++++++++++++++++++--------- render/xml.go | 26 ++++++- render/yaml.go | 26 ++++++- 13 files changed, 546 insertions(+), 81 deletions(-) create mode 100644 render/empty.go diff --git a/.gitignore b/.gitignore index bdd50c95..774eeb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ count.out test profile.out tmp.out +.idea/ +*.iml diff --git a/context.go b/context.go index c38b2b87..30dcca61 100644 --- a/context.go +++ b/context.go @@ -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. diff --git a/context_17.go b/context_17.go index 024dcb70..7fd4fc20 100644 --- a/context_17.go +++ b/context_17.go @@ -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) } /************************************/ diff --git a/render/empty.go b/render/empty.go new file mode 100644 index 00000000..4b1b0751 --- /dev/null +++ b/render/empty.go @@ -0,0 +1,39 @@ +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 (*EmptyRender) Render(http.ResponseWriter) error { + return fmt.Errorf("empty render,you need register one first") +} + +// WriteContentType writes custom ContentType. +func (*EmptyRender) WriteContentType(w http.ResponseWriter) { + // Empty +} + +// Setup set data and opts +func (*EmptyRender) Setup(data interface{}, opts ...interface{}) { + // Empty +} + +// Reset clean data and opts +func (*EmptyRender) Reset() { + // Empty +} diff --git a/render/json.go b/render/json.go index 32d0fc42..bd572e97 100644 --- a/render/json.go +++ b/render/json.go @@ -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,30 @@ 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 { + if callback, ok := opts[0].(string); ok { + r.Callback = callback + } else { + r.Callback = "" + } + } +} + +// 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 +205,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 +241,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{} +} diff --git a/render/json_17.go b/render/json_17.go index 208193c7..84924e2c 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -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{} +} diff --git a/render/msgpack.go b/render/msgpack.go index dc681fcf..529d4c4f 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -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{} +} diff --git a/render/protobuf.go b/render/protobuf.go index 47895072..12eb2ac0 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -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{} +} diff --git a/render/render.go b/render/render.go index abfc79fc..39a532e7 100644 --- a/render/render.go +++ b/render/render.go @@ -4,7 +4,29 @@ 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 +) + +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 +36,106 @@ 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) + } + if render == nil { + render = &EmptyRender{} + } + 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{} - _ Render = String{} - _ Render = Redirect{} - _ Render = Data{} - _ Render = HTML{} - _ HTMLRender = HTMLDebug{} - _ HTMLRender = HTMLProduction{} - _ Render = YAML{} - _ Render = MsgPack{} - _ Render = Reader{} - _ Render = AsciiJSON{} - _ Render = ProtoBuf{} + _ RenderRecycler = &JSON{} + _ RenderRecycler = &IndentedJSON{} + _ RenderRecycler = &SecureJSON{} + _ RenderRecycler = &JsonpJSON{} + _ RenderRecycler = &XML{} + _ Render = String{} + _ Render = Redirect{} + _ Render = Data{} + _ Render = HTML{} + _ HTMLRender = HTMLDebug{} + _ HTMLRender = HTMLProduction{} + _ RenderRecycler = &YAML{} + _ RenderRecycler = &MsgPack{} + _ Render = Reader{} + _ RenderRecycler = &AsciiJSON{} + _ RenderRecycler = &ProtoBuf{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_17_test.go b/render/render_17_test.go index 68330090..5c4ade6b 100644 --- a/render/render_17_test.go +++ b/render/render_17_test.go @@ -19,7 +19,10 @@ func TestRenderPureJSON(t *testing.T) { "foo": "bar", "html": "", } - 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\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) diff --git a/render/render_test.go b/render/render_test.go index 4c9b180d..68c57bd7 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -31,11 +31,15 @@ func TestRenderMsgPack(t *testing.T) { "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 +60,15 @@ func TestRenderJSON(t *testing.T) { "html": "", } - (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 +80,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 +95,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 +110,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 +124,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 +147,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 +162,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 +199,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 +213,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 +232,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 +247,9 @@ func TestRenderAsciiJSON(t *testing.T) { "tag": "
", } - 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 +258,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 +270,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 +309,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 +332,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 +349,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 +369,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 +382,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, "bar", w.Body.String()) diff --git a/render/xml.go b/render/xml.go index cc5390a2..d1648bc5 100644 --- a/render/xml.go +++ b/render/xml.go @@ -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{} +} diff --git a/render/yaml.go b/render/yaml.go index 33bc3254..8d39b34a 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -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{} +}