Skip to content

Commit

Permalink
Performance improvements when rendering
Browse files Browse the repository at this point in the history
- Fast path for JSON, XML and plain text rendering
  • Loading branch information
manucorporat committed May 7, 2015
1 parent eb3e929 commit 2d8f0a4
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 158 deletions.
52 changes: 26 additions & 26 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package gin

import (
"errors"
"fmt"
"math"
"net/http"
"strings"
Expand Down Expand Up @@ -314,61 +313,62 @@ func (c *Context) BindWith(obj interface{}, b binding.Binding) bool {
/******** RESPONSE RENDERING ********/
/************************************/

func (c *Context) renderingError(err error, meta ...interface{}) {
c.ErrorTyped(err, ErrorTypeInternal, meta)
c.AbortWithStatus(500)
}

func (c *Context) Render(code int, render render.Render, obj ...interface{}) {
if err := render.Render(c.Writer, code, obj...); err != nil {
c.ErrorTyped(err, ErrorTypeInternal, obj)
c.AbortWithStatus(500)
c.renderingError(err, obj)
}
}

// Serializes the given struct as JSON into the response body in a fast and efficient way.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
c.Render(code, render.JSON, obj)
// 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/
func (c *Context) HTML(code int, name string, obj interface{}) {
c.Render(code, c.Engine.HTMLRender, name, obj)
}

func (c *Context) IndentedJSON(code int, obj interface{}) {
c.Render(code, render.IndentedJSON, obj)
}

// Serializes the given struct as JSON into the response body in a fast and efficient way.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
if err := render.WriteJSON(c.Writer, code, obj); err != nil {
c.renderingError(err, obj)
}
}

// Serializes the given struct as XML into the response body in a fast and efficient way.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML, obj)
}

// 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/
func (c *Context) HTML(code int, name string, obj interface{}) {
c.Render(code, c.Engine.HTMLRender, name, obj)
if err := render.WriteXML(c.Writer, code, obj); err != nil {
c.renderingError(err, obj)
}
}

// Writes the given string into the response body and sets the Content-Type to "text/plain".
func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.Plain, format, values)
render.WritePlainText(c.Writer, code, format, values)
}

// Writes the given string into the response body and sets the Content-Type to "text/html" without template.
func (c *Context) HTMLString(code int, format string, values ...interface{}) {
c.Render(code, render.HTMLPlain, format, values)
render.WriteHTMLString(c.Writer, code, format, values)
}

// Returns a HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) {
if code < 300 || code > 308 {
panic(fmt.Sprintf("Cannot redirect with status code %d", code))
}
c.Render(code, render.Redirect, c.Request, location)
render.WriteRedirect(c.Writer, code, c.Request, location)
}

// Writes some data into the body stream and updates the HTTP code.
func (c *Context) Data(code int, contentType string, data []byte) {
if len(contentType) > 0 {
c.Writer.Header().Set("Content-Type", contentType)
}
c.Writer.WriteHeader(code)
c.Writer.Write(data)
render.WriteData(c.Writer, code, contentType, data)
}

// Writes the specified file into the body stream
Expand Down
20 changes: 20 additions & 0 deletions render/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package render

import "net/http"

type dataRender struct{}

func (_ dataRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
contentType := data[0].(string)
bytes := data[1].([]byte)
WriteData(w, code, contentType, bytes)
return nil
}

func WriteData(w http.ResponseWriter, code int, contentType string, data []byte) {
if len(contentType) > 0 {
w.Header().Set("Content-Type", contentType)
}
w.WriteHeader(code)
w.Write(data)
}
66 changes: 66 additions & 0 deletions render/html.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package render

import (
"errors"
"fmt"
"html/template"
"net/http"
)

type (
HTMLRender struct {
Template *template.Template
}

htmlPlainRender struct{}

HTMLDebugRender struct {
Files []string
Glob string
}
)

func (html HTMLRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "text/html")
file := data[0].(string)
args := data[1]
return html.Template.ExecuteTemplate(w, file, args)
}

func (r *HTMLDebugRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "text/html")
file := data[0].(string)
obj := data[1]

if t, err := r.loadTemplate(); err == nil {
return t.ExecuteTemplate(w, file, obj)
} else {
return err
}
}

func (r *HTMLDebugRender) loadTemplate() (*template.Template, error) {
if len(r.Files) > 0 {
return template.ParseFiles(r.Files...)
}
if len(r.Glob) > 0 {
return template.ParseGlob(r.Glob)
}
return nil, errors.New("the HTML debug render was created without files or glob pattern")
}

func (_ htmlPlainRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
format := data[0].(string)
values := data[1].([]interface{})
WriteHTMLString(w, code, format, values)
return nil
}

func WriteHTMLString(w http.ResponseWriter, code int, format string, values []interface{}) {
WriteHeader(w, code, "text/html")
if len(values) > 0 {
fmt.Fprintf(w, format, values...)
} else {
w.Write([]byte(format))
}
}
38 changes: 0 additions & 38 deletions render/html_debug.go

This file was deleted.

31 changes: 31 additions & 0 deletions render/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package render

import (
"encoding/json"
"net/http"
)

type (
jsonRender struct{}

indentedJSON struct{}
)

func (_ jsonRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
return WriteJSON(w, code, data[0])
}

func (_ indentedJSON) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "application/json")
jsonData, err := json.MarshalIndent(data[0], "", " ")
if err != nil {
return err
}
_, err = w.Write(jsonData)
return err
}

func WriteJSON(w http.ResponseWriter, code int, data interface{}) error {
WriteHeader(w, code, "application/json")
return json.NewEncoder(w).Encode(data)
}
22 changes: 22 additions & 0 deletions render/redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package render

import (
"fmt"
"net/http"
)

type redirectRender struct{}

func (_ redirectRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
req := data[0].(*http.Request)
location := data[1].(string)
WriteRedirect(w, code, req, location)
return nil
}

func WriteRedirect(w http.ResponseWriter, code int, req *http.Request, location string) {
if code < 300 || code > 308 {
panic(fmt.Sprintf("Cannot redirect with status code %d", code))
}
http.Redirect(w, req, location, code)
}
105 changes: 13 additions & 92 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,103 +4,24 @@

package render

import (
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"net/http"
)

type (
Render interface {
Render(http.ResponseWriter, int, ...interface{}) error
}

jsonRender struct{}

indentedJSON struct{}

xmlRender struct{}

plainTextRender struct{}

htmlPlainRender struct{}
import "net/http"

redirectRender struct{}

HTMLRender struct {
Template *template.Template
}
)
type Render interface {
Render(http.ResponseWriter, int, ...interface{}) error
}

var (
JSON = jsonRender{}
IndentedJSON = indentedJSON{}
XML = xmlRender{}
HTMLPlain = htmlPlainRender{}
Plain = plainTextRender{}
Redirect = redirectRender{}
JSON Render = jsonRender{}
IndentedJSON Render = indentedJSON{}
XML Render = xmlRender{}
HTMLPlain Render = htmlPlainRender{}
Plain Render = plainTextRender{}
Redirect Render = redirectRender{}
Data Render = dataRender{}
_ Render = HTMLRender{}
_ Render = &HTMLDebugRender{}
)

func (_ redirectRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
req := data[0].(*http.Request)
location := data[1].(string)
http.Redirect(w, req, location, code)
return nil
}

func (_ jsonRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "application/json")
return json.NewEncoder(w).Encode(data[0])
}

func (_ indentedJSON) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "application/json")
jsonData, err := json.MarshalIndent(data[0], "", " ")
if err != nil {
return err
}
_, err = w.Write(jsonData)
return err
}

func (_ xmlRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "application/xml")
return xml.NewEncoder(w).Encode(data[0])
}

func (_ plainTextRender) Render(w http.ResponseWriter, code int, data ...interface{}) (err error) {
WriteHeader(w, code, "text/plain")
format := data[0].(string)
args := data[1].([]interface{})
if len(args) > 0 {
_, err = fmt.Fprintf(w, format, args...)
} else {
_, err = w.Write([]byte(format))
}
return
}

func (_ htmlPlainRender) Render(w http.ResponseWriter, code int, data ...interface{}) (err error) {
WriteHeader(w, code, "text/html")
format := data[0].(string)
args := data[1].([]interface{})
if len(args) > 0 {
_, err = fmt.Fprintf(w, format, args...)
} else {
_, err = w.Write([]byte(format))
}
return
}

func (html HTMLRender) Render(w http.ResponseWriter, code int, data ...interface{}) error {
WriteHeader(w, code, "text/html")
file := data[0].(string)
args := data[1]
return html.Template.ExecuteTemplate(w, file, args)
}

func WriteHeader(w http.ResponseWriter, code int, contentType string) {
contentType = joinStrings(contentType, "; charset=utf-8")
w.Header().Set("Content-Type", contentType)
Expand Down
Loading

0 comments on commit 2d8f0a4

Please sign in to comment.