It is a common requirement to encounter the necessity of having various operations needed around incoming HTTP requests to your server. The most prevalent examples would be logging and tracing. Although there are various frameworks out there that provide this out of the box, I’d like to illustrate how simple it is to create this logic using nothing but the standard library. Building your own implementation gives you full flexibility and control over the behaviour, reducing the need for external dependencies.
Ideally, I would prefer the API to look something like this, within the context of my naive example:
In the above example, I used chainMiddleware
to create a wrapper for all my handlers, which augments both home
and about
with the logging and tracing middleware that I’ve defined and passed as parameters to this function.
In order for this to work, we need to define a middleware type. It can be defined as:
Thereby, a middleware
will be defined as a function that returns a new handler which will perform some operations before (or after) calling the next handler that is provided as an argument. For the sake of brevity when illustrating these examples, we’ll be using [http.HandlerFunc](https://golang.org/pkg/net/http#HandlerFunc)
instead of the classical [http.Handler](https://golang.org/pkg/net/http#Handler)
. The behaviour is the same.
Using this pattern we define the dummy withLogging
and withTracing
middleware as such:
Our middleware is purely illustrative and does nothing but log that an action has been taken.
We could theoretically already use this implementation, without the need for chainMiddleware
, simply by defining each route as such:
This is good and it works but can quickly become tedious and verbose if we introduce more middleware. Thus, our chain function provides nice syntactic sugar for chaining multiple middleware into one.
Let’s look at the implementation:
By making use of the functional programming features of Go, we can use the above implementation to link a chain of middleware which call into one another in the provided order, returning a new wrapper for our handlers. We take each middleware function in the list in reverse order and pass its return value as an argument to the one before it, starting with the final
handler. In this way, the chain happens in the order that it is passed in.
Now, if we run the program and visit http://localhost:8080
in the browser, we should expect to see something similar to the following output in the console, before the final handler is called:
main.go:34: Logged connection from [::1]:54962
main.go:41: Tracing request for /
Simple as that! You can find the full running example here.