Resource-ful Handlers

It seems that most routers for web apps provide an API that gives equal importance to the path and the method, which in my opinion is totally wrong. A path names a resource whereas the method is an action you can (possibly) perform on that resource.

Routing should happen in layers:

  1. At the top level you are finding the correct resource for the request.
  2. Then work out the action to perform; GET = retrieve, POST = create, etc.
  3. Finally content negotiation using the Content-Type and Accept-* headers.

The third layer is not always needed, but the first two generally are. But most of the time they are defined as if they are equals.

Previously I was writing my applications with routing like,

booksHandler := handlers.Books(db, es)
r.Path("/books/{id}").Methods("GET").Handler(persona.Protect(booksHandler.Get))
r.Path("/books/{id}").Methods("PATCH").Handler(persona.Protect(booksHandler.Update))
r.Path("/books/{id}").Methods("DELETE").Handler(persona.Protect(booksHandler.Delete))

A struct is created with different fields for the handlers which should be used for the various http methods. But there is no reason for the top-level routing layer to know about whether this supports DELETE, it should simply say /books/{id} is a book.

Contrast with the current version:

route.Handle("/books/:id", persona.Protect(handlers.Books(db, es)))

Now the different actions that can be made on a book are defined in a separate handler:

func Books(db data.Db, es *events.Source) http.Handler {
  h := booksHandler{db, es}
  return mux.Method{
    "GET": h.Get(),
    "PATCH": h.Update(),
    "DELETE": h.Delete(),
  }
}

This uses my mux library which defines some simple map[string]http.Handler types that implement http.Handler. It now acts like a resource, so the router is left the simple job of mapping request paths to resources.

Yet Another Http Request Router

Stop me. I’ve just written another http request router for Go, adding to the ever growing list in the world. But there was a reason…

Previously I’ve used either net/http or, when parameterised routes were required, gorilla/mux. This worked fine and still does. But I disliked the way gorilla/mux defined routes, it seemed cumbersome, probably since it allows you to do some pretty complicated things. I wanted the simplicity of http.Handle("/some", someHandler) but with parameters.

So I started looking at others. One stood out above the others: julienschmidt/httprouter. If you look at the benchmarks it is incredible. But this did a little too much, all handlers were registered with the route and method. I’ve been writing apps in a style where a handler defines all of the methods within itself, so I only want to be registering with a route.

I began by cloning the repo and ripping out the method related stuff. This took it down to registering everything to a single tree, based on route, just like ServeMux. All was good.

But then I ran into a small problem: I wanted to register the following url structure,

/
/:name
/:name/edit
/:name/delete
/-/create
/-/sign-in
/-/sign-out

The problem is that httprouter doesn’t allow registering overlapping routes like this, I would have to change /:name to /something/:name so the /:name/... and /-/... prefixes didn’t overlap (sim. for the others). This wasn’t ideal, why should I have to change the urls?

So I thought I’d just make a quick change to the route registration to fix these cases, I started reading the implementation. It uses a radix tree which is nice and space efficient, but I had no idea where/if I could make the changes.

Instead I gave up and wrote a new, simpler, tree. The new version makes no attempt to reuse common path segments but instead splits a path on each /, building a new node for each segment. This is slightly less space efficient but made it easy to allow overlapping url structures: we simply try to find an exact match first, failing that we match the parameter.

So that is why hawx/route exists. Yet another http request router.