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

My Ideal Programming Language, Part 1

I’m shifting in this post from writing about my life, health, etc… to writing about software again. Among the many things Bjarne Stroustrup has said that someone has published on the internet, my favorite is this: “There are only two... Continue →