Idiomatic way to handle template errors in golang
Asked Answered
S

1

14

Say I have a html/template like the following:

<html>
<body>
    <p>{{SomeFunc .SomeData}}</p>
</body>

and sometimes SomeFunc returns an error. Is there an idiomatic way to deal with this?

If I write directly to the ResponseWriter, then a status code 200 has already been written before I encounter the error.

var tmpl *template.Template

func Handler(w http.ResponseWriter, r *http.Request) {
    err := tmpl.Execute(w, data)
    // "<html><body><p>" has already been written...
    // what to do with err?
}

Preferably I would return a status code 400 or some such, but I can't see a way to do this if I use template.Execute directly on the ResponseWriter. Is there something I'm missing?

Shackleford answered 13/6, 2015 at 17:49 Comment(0)
N
17

Since the template engine generates the output on-the-fly, parts of the template preceding the SomeFunc call are already sent to the output. And if the output is not buffered, they (along with the HTTP 200 status) may already be sent.

You can't do anything about that.

What you can do is perform the check before you call template.Execute(). In trivial case it should be enough to call SomeFunc() and check its return value. If you choose this path and the return value of SomeFunc() is complex, you do not have to call it again from the template, you can simply pass its return value to the params you pass to the template and refer to this value in the template (so SomeFunc() won't have to be executed twice).

If this is not enough or you can't control it, you can create a bytes.Buffer or strings.Builder, execute your template directed into this buffer, and after the Execute() returns, check if there were errors. If there were errors, send back a proper error message / page. If everything went ok, you can just send the content of the buffer to the ResponseWriter.

This could look something like this:

buf := &bytes.Buffer{}
err := tmpl.Execute(buf, data)
if err != nil {
    // Send back error message, for example:
    http.Error(w, "Hey, Request was bad!", http.StatusBadRequest) // HTTP 400 status
} else {
    // No error, send the content, HTTP 200 response status implied
    buf.WriteTo(w)
}
Nonchalance answered 13/6, 2015 at 19:4 Comment(1)
I wrote about this a while back—a buffer pool either via sync.Pool or another construct—is a slightly more performant way to solve this. Allocate a pool, Get a buffer, write to it and then to the response (on nil error) and then Put the buffer back into the pool. Otherwise you're on point!Dunston

© 2022 - 2024 — McMap. All rights reserved.