Go Templates
I recently discovered that I was rendering Go templates on every HTTP request. Luckily, my templates are small and Go renders them quickly. Unluckily I didn’t notice the rendering overhead (ironically, because it was so small).
Before the optimization, my HTTP handler looked like this:
func myHandler(res http.ResponseWriter, req *http.Request) {
tmpl, err := template.New("MyTmpl").ParseFiles("some_template.tmpl")
if err != nil {
http.Error(res, "couldn't parse template", http.StatusInternalServerError)
return
}
tmpl.Execute(res, map[string]string{})
}
Notice that every request executes myHandler
, so ParseFiles
gets called on every request. I ran a simple benchmark that calls Parse
instead of ParseFiles
and still takes 6 milliseconds (reading files would add even more latency.)
Let’s eliminate that latency by removing the ParseFiles
call from the handler and pre-render the templates instead.
Pre-Rendering Templates #
To do the pre-rendering, we need to put the ParseFiles
calls somewhere else. We can pre-render the template by putting it in a global var
:
var tmpl = template.Must(template.New("MyTmpl").ParseFiles("some_template.tmpl"))
We also added the template.Must
call so the server will panic and crash on startup if the template couldn’t be parsed - an added feature.
A Cleaner Way #
Global variables are ok for small programs, but as your program gets bigger, they can lead to problems.
So instead of storing our pre-rendered templates in global scope, let’s create a closure and put the parsed template in it.
func myHandlerWrapper() func(http.ResponseWriter, *http.Request) {
tmpl := template.Must(template.New("MyTmpl").ParseFiles("some_template.tmpl"))
return func(res http.ResponseWriter, req *http.Request {
//use tmpl in here
}
}
Now we’re parsing the template and returning a func
that has the template in its closure. We call myHandlerWrapper
once to get the func
to be passed to http.HandleFunc
:
http.HandleFunc("/my_endpoint", myHandlerWrapper())
Since myHandlerWrapper
gets called once, the template is still parsed once, and it will still panic immediately if the template is un-parseable.
Other Options #
This improvement is really all about where to store the pre-rendered template. I showed storing it in a closure, which is handy if you’re refactoring a bunch of http.HandleFunc
calls. You can also store it in a http.Handler
or some other custom structure you choose.
And finally, you can use these strategies to pass other data to your HTTP handlers. For example, in one of my projects I pass a go-bindata-html-template AssetFunc
to my handlers using the closure strategy above, and then pre-compute the template using the AssetFunc
:
func myHandlerWrapper(assetFunc template.AssetFunc) func(http.ResponseWriter, *http.Request) {
tmpl := template.Must(template.New("MyTmpl").Parse(assetFunc("some_template.tmpl"))
return func(res http.ResponseWriter, req *http.Request) {
//use tmpl here
}
}