Catching runtime errors
As soon as we begin adding dynamic behavior to our HTML templates there’s a risk of encountering runtime errors.
Let’s add a deliberate error to the view.tmpl
template and see what happens:
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} {{with .Snippet}} <div class='snippet'> <div class='metadata'> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> {{len nil}} <!-- Deliberate error --> <pre><code>{{.Content}}</code></pre> <div class='metadata'> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> {{end}} {{end}}
In this markup above we’ve added the line {{len nil}}
, which should generate an error at runtime because in Go the value nil
does not have a length.
Try running the application now. You’ll find that everything still compiles OK:
$ go run ./cmd/web time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000
But if you use curl to make a request to http://localhost:4000/snippet/view/1
you’ll get a response which looks a bit like this.
$ curl -i http://localhost:4000/snippet/view/1 HTTP/1.1 200 OK Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 734 Content-Type: text/html; charset=utf-8 <!doctype html> <html lang='en'> <head> <meta charset='utf-8'> <title>Snippet #1 - Snippetbox</title> <link rel='stylesheet' href='/static/css/main.css'> <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'> </head> <body> <header> <h1><a href='/'>Snippetbox</a></h1> </header> <nav> <a href='/'>Home</a> </nav> <main> <div class='snippet'> <div class='metadata'> <strong>An old silent pond</strong> <span>#1</span> </div> Internal Server Error
This is pretty bad. Our application has thrown an error, but the user has wrongly been sent a 200 OK
response. And even worse, they’ve received a half-complete HTML page.
To fix this we need to make the template render a two-stage process. First, we should make a ‘trial’ render by writing the template into a buffer. If this fails, we can respond to the user with an error message. But if it works, we can then write the contents of the buffer to our http.ResponseWriter
.
Let’s update the render()
helper to use this approach instead:
package main import ( "bytes" // New import "fmt" "net/http" ) ... func (app *application) render(w http.ResponseWriter, r *http.Request, status int, page string, data templateData) { ts, ok := app.templateCache[page] if !ok { err := fmt.Errorf("the template %s does not exist", page) app.serverError(w, r, err) return } // Initialize a new buffer. buf := new(bytes.Buffer) // Write the template to the buffer, instead of straight to the // http.ResponseWriter. If there's an error, call our serverError() helper // and then return. err := ts.ExecuteTemplate(buf, "base", data) if err != nil { app.serverError(w, r, err) return } // If the template is written to the buffer without any errors, we are safe // to go ahead and write the HTTP status code to http.ResponseWriter. w.WriteHeader(status) // Write the contents of the buffer to the http.ResponseWriter. Note: this // is another time where we pass our http.ResponseWriter to a function that // takes an io.Writer. buf.WriteTo(w) }
Restart the application and try making the same request again. You should now get a proper error message and 500 Internal Server Error
response.
$ curl -i http://localhost:4000/snippet/view/1 HTTP/1.1 500 Internal Server Error Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Wed, 18 Mar 2024 11:29:23 GMT Content-Length: 22 Internal Server Error
Great stuff. That’s looking much better.
Before we move on to the next chapter, head back to the view.tmpl
file and remove the deliberate error:
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} {{with .Snippet}} <div class='snippet'> <div class='metadata'> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> <pre><code>{{.Content}}</code></pre> <div class='metadata'> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> {{end}} {{end}}