Lately I’ve been noticing that new Golang web-frameworks have their own way of defining HTTP handlers. They differ in syntax, but the common theme is that the handler usually accepts some sort of context where request and other data are linked:

// Echo
func(c echo.Context) error {}

// Buffao
func (c buffalo.Context) error {}

// Gin
func(c *gin.Context) {}

// Macaron
func(ctx *macaron.Context) {}

These are just a few of them and the problem here is that this locks you in on that specific framework and diverges from idiomatic way of doing handlers in Go. Now I understand how this came to be, prior to Go 1.7 Context wasn’t baked in with the language and so people found creative ways to pass additional data that are per-request context. But now that we have Context and per-request context via Request.WithContext and Request.Context, its about time we fix this.

Go has two ways to define a handler, if you have a struct, you just need to implement the Handler interface and you can pass your struct instance as a handler:

type MyHandler struct {}

func (h *MyHandler) ServeHTTP(ResponseWriter, *Request) {}

// usage
http.Handle("/", new(MyHandler))

Or if you have a function, you just need to have the signature HandlerFunc and you can pass your function as a handler as well:

myHandler := func(w http.ResponseWriter, r *http.Request) {}

// usage
http.HandleFunc("/", myHandler)

By following these interface, you make your code compatible against packages that follow idiomatic Go and makes code from Go ecosystem highly reusable.

Now its not all bad, I still found others that follow these interface like Goji and httptreemux and there are possibly more but I want to highlight some of httptreemux features that makes it awesome.

First is the handler:

func(w http.ResponseWriter, r *http.Request) {}

Oh yeah, exactly like what http.HandleFunc expects. So this sets you up for reusability right off the bat.

What about url namespacing?

router := httptreemux.NewContextMux()
router.GET("/:page", func(w http.ResponseWriter, r *http.Request) {
  params := httptreemux.ContextParams(r.Context())
  fmt.Fprintf(w, "GET /%s", params["page"])
})

group := router.NewGroup("/api")
group.GET("/v1/:id", func(w http.ResponseWriter, r *http.Request) {
  // do some magic
})

It can’t get any better than this! See how params are nicely tucked into the request’s context, its just cleaner this way.

Sensible yet customizable defaults for routing, and trailing slash handling just makes me happy.

But does it perform well? I opened this PR to benchmark against other MUX and here’s the result:

GocraftWeb_Simple-4            3000000       491 ns/op	    6 allocs/op
GocraftWeb_Route15-4           1000000      1931 ns/op	    7 allocs/op
GocraftWeb_Route75-4           1000000      2117 ns/op	    7 allocs/op
GocraftWeb_Route150-4          1000000      2222 ns/op	    7 allocs/op
GocraftWeb_Route300-4          1000000      2048 ns/op	    7 allocs/op
GocraftWeb_Route3000-4          500000      2720 ns/op	    7 allocs/op
GocraftWeb_Middleware-4        2000000       897 ns/op	    8 allocs/op
GocraftWeb_Composite-4          500000      3488 ns/op	    8 allocs/op

GorillaMux_Simple-4            2000000       867 ns/op	    9 allocs/op
GorillaMux_Route15-4            500000      2500 ns/op	   10 allocs/op
GorillaMux_Route75-4            300000      4662 ns/op	   10 allocs/op
GorillaMux_Route150-4           200000      7343 ns/op	   10 allocs/op
GorillaMux_Route300-4           100000     14347 ns/op	   10 allocs/op
GorillaMux_Route3000-4           10000    121817 ns/op	   12 allocs/op

Martini_Simple-4                300000      3869 ns/op	   10 allocs/op
Martini_Route15-4               300000      4867 ns/op	   10 allocs/op
Martini_Route75-4               200000      6153 ns/op	   10 allocs/op
Martini_Route150-4              200000      7788 ns/op	   10 allocs/op
Martini_Route300-4              100000     11112 ns/op	   10 allocs/op
Martini_Route3000-4              20000    117156 ns/op	   12 allocs/op
Martini_Middleware-4            100000     15124 ns/op	   16 allocs/op
Martini_Composite-4             100000     19231 ns/op	   17 allocs/op

PiluTraffic_Simple-4           1000000      1197 ns/op	   13 allocs/op
PiluTraffic_Route15-4          1000000      1919 ns/op	   18 allocs/op
PiluTraffic_Route75-4           500000      3202 ns/op	   26 allocs/op
PiluTraffic_Route150-4          300000      4881 ns/op	   37 allocs/op
PiluTraffic_Route300-4          200000      8152 ns/op	   58 allocs/op
PiluTraffic_Route3000-4          20000     84404 ns/op	  423 allocs/op
PiluTraffic_Middleware-4       1000000      1091 ns/op	   13 allocs/op
PiluTraffic_Composite-4         300000      4948 ns/op	   39 allocs/op

HttpTreeMux_Simple-4          20000000       106 ns/op	    0 allocs/op
HttpTreeMux_Route15-4          3000000       446 ns/op	    3 allocs/op
HttpTreeMux_Route75-4          3000000       453 ns/op	    3 allocs/op
HttpTreeMux_Route150-4         3000000       455 ns/op	    3 allocs/op
HttpTreeMux_Route300-4         3000000       468 ns/op	    3 allocs/op
HttpTreeMux_Route3000-4        3000000       527 ns/op	    3 allocs/op

Aside from consistent routing speed, notice how it barely allocates memory as well compared to others. With a modern mux like this, I’m excited for the future of web-frameworks in Golang and maybe perhaps I’d write one myself just for func!