Last time we made a little guestbook application, but there were a couple pain points. We had to have some boiler plate at the top of all of the handlers, and errors were handled by copying the same line of code everywhere. We also had fixed url paths hard coded in handlers and templates. Let’s see how we can fix that.
Adding context
A lot of the boiler plate in the handlers last time had to do with the database for each request, so let’s start by cleaning that up. How we do this is by creating a type that will have the context for the request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
A context is the general context the request will use to make decisions, bundled up with the handles to the resources it needs to perform actions. Right now we only have the database. Let’s change our handlers to use the new context.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
Now thats wonderful, but it looks like we just made it worse.
The magic of interfaces
To fix this, we’re going to create a new handler type, and give it a
ServeHTTP method. This new handler type will handle creating/closing
the context, and handling any errors that arise. Here’s the definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
The handler type is a function type, meaning any function with that signature
can be cast to that type. We define a method on the function (I know!) so that
the net/http package can use it as though it were any other handler. We’ve
already been doing something very similar to this already. When we called the
http.HandleFunc function in our main.go, we’ve been using our functions
as the type http.HandlerFunc which defines a ServeHTTP method, just like ours.
See, it’s not so bad. Here’s what the new handlers look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Much better! Let’s commit that.
Routing
The other pain points, hard coded urls, and checking the request method,
are going to be handled by more advanced routing. For this, we’re going
to use the execllent gorilla web toolkit, specifically the
gorilla/pat package. I really like the simple API it provides
with easy parameter capturing from the url. It’s very easy to use with the
net/http package:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
One important and easy to miss detail is we now pass the router in as the
second argument to the http.ListenAndServe call. Now we can remove the check
that the method is POST in the sign handler, as the router takes care of that
for us. Lets move on to fixing the hard coded entries.
Reversing URLs
If you’ll notice, we gave the handlers a .Name call. The gorilla/pat
package returns a *mux.Router for us to work with. Using that we can have the
router rebuild urls from the names. For example, if we wanted to grab the url for
the index page, we could use
r.GetRoute("index").URL()
but since r is inaccessable outside the main function, we have to move it
into a higher scope. Let’s do that.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
And now we can update the sign handler
1 2 3 4 5 6 7 8 9 10 11 | |
Reversing in Templates
To reverse inside the template, we could either remember to pass the router in as part of the template context on every invocation, or we could add a function to the template. Since keeping track of the router through nested templates and scope changes is a daunting task, adding a function to do the reversing is a better option. Heres that function:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
We choose to have the function panic on errors because any incorrect reversal
is a programmer error. We also accept a variadic number of interface values because
sometimes we need to have a parameter in the reversal that is an integer, like the
year on the blog post url, and the URL function takes strings.
So rather than force the template to do the conversion, or the function executing the template, we
just convert everything to a string by calling fmt.Sprint on it. Then we have
to add this function to the template.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Theres a tricky point here: the template package will error when trying to parse a template and it finds a function invocation to something undefined. That means we have to add our function map to the template before we add the files to parse. We write a little helper function to do this correctly. Now we can update the template to use it.
<form action="{{ reverse "sign" }}" method="POST">
Let’s update the sign handler to use the reverse function too.
http.Redirect(w, req, reverse("index"), http.StatusSeeOther)
Pain: consider yourself eliminated.
Next up, we’re going to do more with the context type we created, and make the guestbook a little more web 2.0. As always, the source to the gostbook is up on github.