Writing delightful HTTP middleware in Go
While writing complex services in go, one typical topic that you will encounter is middleware. This topic has been discussed again, and again, and again, on internet. Essentially a middleware should allow us to:
- Intercept a
ServeHTTPcall, and execute any arbitrary code.
- Make changes to request/response flow along continuation chain.
- Break the middleware chain, or continue onto next middleware interceptor eventually leading to real request handler.
All of this would sound very similar to what express.js middleware do. We explored various libraries and found existing solutions that closely matched what we wanted, but they either had unnecessary extras, or were not delightful for our taste buds. It was pretty obvious that we can write express.js inspired middleware, with cleaner installation API under 20 lines of code ourselves.
While designing the abstraction, we first imagined how we want to write our middleware functions (referred as interceptors from this point onwards), and the answer was pretty obvious:
They look just like
http.HandlerFunc, but having an extra parameter
next that continues the handler chain. This would allow anyone to write interceptor as simple function similar to
http.HandlerFunc that can intercept the call, do what they want, and pass on the control if they want to.
Next we imagined how to hook these interceptors to our
http.HandlerFunc. In order to do so, first thing do is define
MiddlewareHandlerFunc which is simply a type of
type MiddlewareHandlerFunc http.HandlerFunc). This will allow us to build a nicer API on top of stock
http.HandlerFunc. Now given a
http.HandlerFunc we want our chain-able API to look somewhat like this:
MiddlewareHandlerFunc, and then calling
Intercept method to install our interceptor. The return type of
Intercept is again a
MiddlewareHandlerFunc, which allows us to call
One important thing to note with
Intercept scheme is the order of execution. Since calling
chain(responseWriter, request) is indirectly invoking last interceptor, the execution of interceptors is reversed i.e. it goes from interceptor at tail all the way back to handler at head. Which makes perfect sense because you are intercepting the call; so you should get a chance to execute before your parent.
While this reverse chaining system makes the abstraction more fluent, turns out most the time we have a precompiled array of interceptors that will be reused with different handlers. Also when we are defining a chain of middleware as an array, we would naturally prefer to declare them in the order of their execution (not reversed order). Let’s call this array interceptors
MiddlewareChain. We want our middleware chains to look somewhat like:
Note these middleware will be invoked in same order as the appear in the chain i.e.
ElapsedTimeInterceptor. This adds both reusability, and readability to our code.
Once we designed the abstraction the implementation was pretty straight forward:
So under 20 lines of code (excluding comments), we were able to build a nice middleware library. It’s almost barebones, but the coherent abstraction of these few lines is just amazing. It enables us to write some slick middleware chains, without any fuss. Hopefully these few lines will inspire your middleware experience to be delightful as well.
Like what you see? Come join us.