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
  }
}
 
8
Kudos
 
8
Kudos

Now read this

Fuck the Easy Way, Find the Challenge

TL;DR Do hard stuff that you will fail miserably at. It’s a great way to learn. I just got back from playing in a soccer game in which my team was destroyed. The score was 7-2. Yea, destroyed. The league I play in is a recreational one,... Continue →