There are many ways to do HTTP path routing in Go – for better or worse. There’s the standard library’s http.ServeMux, but it only supports basic prefix matching. There are many ways to do more advanced routing yourself, including Axel Wagner’s interesting ShiftPath
technique. And then of course there are lots of third-party router libraries. In this article I’m going to do a comparison of several custom techniques and some off-the-shelf packages.
I’ll be upfront about my biases: I like simple and clear code, and I’m a bit allergic to large dependencies (and sometimes those are in tension). Most libraries with “framework” in the title don’t do it for me, though I’m not opposed to using well-maintained libraries that do one or two things well.
My goal here is to route the same 11 URLs with eight different approaches. These URLs are based on a subset of URLs in a web application I maintain. They use GET
and POST
, but they’re not particularly RESTful or well designed – the kind of messiness you find in real-world systems. Here are the methods and URLs:
GET / # home
GET /contact # contact
GET /api/widgets # apiGetWidgets
POST /api/widgets # apiCreateWidget
POST /api/widgets/:slug # apiUpdateWidget
POST /api/widgets/:slug/parts # apiCreateWidgetPart
POST /api/widgets/:slug/parts/:id/update # apiUpdateWidgetPart
POST /api/widgets/:slug/parts/:id/delete # apiDeleteWidgetPart
GET /:slug # widget
GET /:slug/admin # widgetAdmin
POST /:slug/image # widgetImage
The :slug
is a URL-friendly widget identifier like foo-bar
, and the :id
is a positive integer like 1234
. Each routing approach should match on the exact URL – trailing slashes will return 404 Not Found (redirecting them is also a fine decision, but I’m not doing that here). Each router should handle the specified method (GET
or POST
) and reject the others with a 405 Method Not Allowed response. I wrote some table-driven tests to ensure that all the routers do the right thing.
In the rest of this article I’ll present code for the various approaches and discuss some pros and cons of each (all the code is in the benhoyt/go-routing repo). There’s a lot of code, but all of it is fairly straight-forward and should be easy to skim. You can use the following links to skip down to a particular technique. First, the five custom techniques:
match()
helper which scans path parameters into variables/
and then switch on the contents of the path segmentsShiftPath
techniqueAnd three versions using third-party router packages:
github.com/go-chi/chi
github.com/gorilla/mux
github.com/bmizerany/pat
I also tried httprouter, which is supposed to be really fast, but it can’t handle URLs with overlapping prefixes like /contact
and /:slug
. Arguably this is bad URL design anyway, but a lot of real-world web apps do it, so I think this is quite limiting.
There are many other third-party router packages or “web frameworks”, but these three bubbled to the top in my searches (and I believe they’re fairly representative).
In this comparison I’m not concerned about speed. Most of the approaches loop or switch
through a list of routes (in contrast to fancy trie-lookup structures). All of these approaches only add a few microseconds to the request time (see benchmarks), and that isn’t an issue in any of the web applications I’ve worked on.
#go #developer