Why do I not like any Golang URL Routers?
For those who don’t know, URL routing is the practice of taking a URL partial, and mapping it to some form of request handler. This enables one to create a structured URL partial path and “route” that traffic to a function that takes in a request and a response stream. There are a few options out there for Gophers to use that are not strictly tied to a particular web framework that I have looked at and have opinions on.
net/http.ServeMux docs
Lets start with the basics! Built into the standard library is a basic implementation of a URL router. To the Code! net/http.ServeMux (parts chopped to save space)
This is a fairly understandable url router, in that the type contains a route map of strings to muxEntry, which contains a handler and a url pattern. This allows us to create a pattern for a url, such as /amazing/path/. Sounds great, out of the box and we have a mechanism to route urls to handlers.
This router implementation works, but it works for very narrowly focused applications with fairly static routes.
If you want, say, URL parameters in your url such as */amazing/
Reviewing the matching logic you can see that ServeMux.match takes a path, and then iterates over the entire range of routes to muxEntries map on every request and then performs a check to see if the pattern exactly matches any routes. This is very strict, no wildcards, no url parameters, and not a very efficient datastructure choice for applications with lots of url routes.
Gorilla Mux docs
Gorilla Mux is another router implementation that takes care of the need for dynamic url routes pretty well. With a line like the one listed below, we can also specify which http methods are allowed on what url routes! This is pretty sweet, because we can really lock down our API and make sure we know what we are supporting method wise:
We can pass in a standard net/http Handler to this router, and amazingly we can get the variables within “{}” within the handler function by performing a call to mux.Vars(). This is a pretty sweet router. Lets take a look at how this works:
The router looks like a slice of Route Pointers. Also it appears that the route matching is performed by iterating over that slice, and calling “Match” on each route that is registered with the router. Below is an excerpt of how Matching is done in the Route structure:
Looking at the Route.Match method we can see that a route is using a list of “matchers” to validate that the url route is matched to the http.Request that is incomming. You can see in here that on a match a match.Vars map is created to hold the matched variables from the url for our dynamic routes. This seems like an okay-ish approach to me, but I am wondering where these matchers come from, and how they are made. Specifically how do the method matches work?
Ruh, Roh… Mux is using the method as a matcher… How I read this is, since a matcher is an implementation of an interface with a boolean response only, they can’t possibly pass any more information back when the matcher fails. Furthermore since they are re-using this matching concept, we are going to end up getting 404’s as responses when we don’t match an exact HTTP method.. This sucks, as it is completely in violation of the RFC-2616 if you specify the methods in your matching.
I really appreciate the use of interfaces, and implementation of those interfaces with the various matchers, but sometimes the matchers shouldn’t treated the same way, and I really have a feeling that method matching was a bolt on addition to this url router.
Gorilla Patdocs
Maybe Gorilla’s “other” url router will be better? Lets take a look at the source code real quick to see what we are getting into:
Nice, we can use this syntax: r.Get(“/path/{var}/”, handler), but, whaaaa? wait a second… Are you KIDDING ME?
Well this router is just a wrapper of Gorilla Mux with some nicities. Unfortunately, it will suffer from the exact same problems I had with Mux. I do however like how in the below they are re-writing the URL variables inside of the url query string! This means that the handler will have access to the parameters the same way it accesses GET and POST data!
Neat Idea! But still have issues with this package due to the underlying package!
HTTPRouter docs
oooooooohhh! The benchmarks of this implementation are very impressive. And the high level overview of how the route matching works seems very nice, using Radix Tree data structure for building url paths. Makes sense, since in the end we are string matching routes! It does allow for “named parameters” which is usually a must have for api’s these days. I did notice something a little annoying about how this package deals with named parameters though on the github page:
Well shoot, throw a url scheme like this out the window: /path/:customer_id/name and /path/:customer_id/email
Also, the real elephant in the room is how these named parameters are passed into our handlers; your handler will need to accept a third Parameter for the named parameters just for this url router. This is really painful because it almost feels like you are locked into this url package for the long run. Why on earth didn’t this package follow a more “conventional” model of using a per request context, or url rewrites to accomplish this?
In conclusion, I would like to say that everyone’s design ideas are different about URL routing. They all bring benefit to the ecosystem, but what I really want is something that will use Pat’s URL Query String rewriting, and HTTPRouter’s Radix Tree matching. Maybe that will be something I can make ;)
UPDATE - I have followed through on the above conclusion and created Vestigo!