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:

  • Regex table: loop through pre-compiled regexes and pass matches using the request context
  • Regex switch: a switch statement with cases that call a regex-based match() helper which scans path parameters into variables
  • Pattern matcher: similar to the above, but using a simple pattern matching function instead of regexes
  • Split switch: split the path on / and then switch on the contents of the path segments
  • ShiftPath: Axel Wagner’s hierarchical ShiftPath technique

And three versions using third-party router packages:

  • Chi: uses github.com/go-chi/chi
  • Gorilla: uses github.com/gorilla/mux
  • Pat: uses 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

Different Approaches to HTTP Routing in Go
1.90 GEEK