This post is part of a series. For a listing of all the posts, as well as instructions on running the code, see here.
So far, we’ve pretty much always been using Go to spit out static image files. However, it’d be nice if we could do something a little bit more interactive, like generate images based on user input and the like. So here’s the plan:
Today we’ll just take a look at the HTTP server, in the process explaining how to build API handlers, serve static files and “decorate” functions. So sorry, no piccies today.
Getting a simple HTTP working in Go is a piece of cake - it is even included as part of the Tour of Go . To make things more interesting and to give ourselves more flexibility in the future we’ll also reach for a third-party component to make routing requests cleaner and easier to write. Mux is that badger, letting us define route handlers using regular expressions and more. If you’ve used Sinatra or Flask, this should be familiar.
To fetch the Mux
code, use go get github.com/gorilla/mux
. Once that is done, here’s is how you’d use it:
import (
"io"
"net/http"
"github.com/gorilla/mux"
)
func HelloHandler(writer http.ResponseWriter, request *http.Request) {
io.WriteString(writer, "hello!")
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/hello", HelloHandler)
http.ListenAndServe("localhost:1234", router)
}
There we are, one http server. Try running it and navigating to localhost:1234/hello
. A few things to notice:
mux
package, we use the full package name.HelloHandler
function on it, with an associated route: "/hello"
HelloHandler
takes 2 arguments, a writer
into which we send the response and the request
itself, which we can examine, e.g. to see what headers were set.The power of Mux
is that it makes it easy to parse out pieces of the URL. So if we’d like to say hello, based on the user’s name we could do:
router.HandleFunc("/hello/{name}", HelloHandler)
And then in the handler access the variables using mux.Vars()
:
vars := mux.Vars(request)
io.WriteString(writer, "hello " + vars["name"] + "!")
There’s much more that Mux
can do, check out their docs if curious.
For writing an API the above is great, but what if we want to serve static files? Luckily for us, the Go library come with a FileHandler
ready to go. Adding it in is just one line:
staticHandler := http.FileServer(http.Dir("."))
router.PathPrefix("/").Handler(staticHandler)
(Well, two lines. But it could be one. Rest assured, all will be explained)
The staticHandler
will take the part of the route requested after the "/"
, and try to serve that file if it exists in the current directory. For example http://localhost:1234/sweet_goat.jpeg
will return the file sweet_goat.jpeg
found in the directory the code is running from. This router is then registered with our Mux
router object, which will send any requests that match to it - in this case everything will match as we’ve entered "/"
.
There is one subtlety here though: what if we wanted to serve our static content at http://localhost:1234/static
? Easy, we’d change the PathPrefix
argument above from "/"
to "/static/"
, right? Well, not quite.
The trouble is that if we do this then while our router will correctly recognise that we have navigated to /static/file
, our FileServer
does not know that it should throw away the static
part of the URL when handling the request and tries to find the file in the static
directory on its filesystem, which does not exist. To remedy this, we need some sort of way to strip out the prefix before passing the request onto the FileServer
.
Again, luckily for us, the standard library comes to the rescue:
staticHandler = http.StripPrefix("/static/", staticHandler)
See here for a working example
It’s worth taking a look at the source of the StripPrefix
function. What it is doing, is wrapping the staticHandler
function in another function and then returning that. For those of you know Python, this is more or less the same as decorating a function, except without the syntactic sugar.
To really understand the concept, lets create a “decorator” of our own. Specifically, let’s modify a Handler
so that we set the headers in such a way to instruct a web browser to not cache our data. I find this pretty useful during development, when I want to be sure I’m running the latest code.
So, here’s our decorator:
func NoCacheDecorator(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
h.ServeHTTP(w, r)
})
}
Which we’ll apply to an existing Handler
like so:
staticHandler = NoCacheDecorator(staticHandler)
First off, notice what it is that we’re actually returning. We are defining a function, and then calling http.HandlerFunc
on it. This is just a convenient way of converting our function into a Handler
object. The function itself, takes the request that will be passed onto the Handler
we are decorating (in this example staticHandler
) and modifies some of the headers. It passes the request onto the decorated Handler
, by invoking its ServeHttp
method.
You can verify this is working by using something like the Chrome develop tools to examine the headers on the response.
One nice feature about the decorator pattern is that we can stack multiple decorators on top of each other and things will work (as long as the decorators do not interfere functionally). In fact, that is exactly what we’ve done. We’ve created a bog standard FileHandler
, decorated it once using StripPrefix
and then again using NoCacheDecorator
. The result is a file handler which knows to strip off the correct prefix, and set the correct no caching headers.
Check out the code on github for a working example. Run using go run simple_server.go