Creating Middleware with httprouter
When working with HTTP-based services in Go, many developers (including myself) like to use HTTP Multiplexers (mux). Multiplexers, also sometimes referred to as Request Routers, offer a suite of features and capabilities. The simplest form allows users to register a specific handler function with specific URL patterns and methods.
One of the more advanced features is using HTTP Multiplexers to create “HTTP middleware.” This capability allows users to create a standard function or set of executed functions before requests route to the User-defined handlers. Developers can use these common functions to perform many tasks, from creating a standard logger to handling authentication.
Today’s article will explore using the httprouter HTTP Multiplexer to create a simple HTTP middleware.
There are many Multiplexers available, with popular ones being Gorilla Mux, httprouter, and Bone. For this article, I will explore httprouter, one I often use because I like how simple and efficient it is. But also because there is a version available for both
fasthttp. I use both of these HTTP packages throughout my various projects. And having one HTTP Multiplexer to use regardless of which I'm using helps keep things simple.
Getting Started with httprouter
Before jumping into creating an HTTP middleware function, we should first start with the basics of using httprouter. We can look at the most basic HTTP service that prints “Hello World” to kick us off.
The above code only uses the standard
net/http and the default mux with the standard library. One of the nice things about Go is that the standard library is all you need if you build elementary HTTP-based services.
But for this article, we want to do something more advanced; we want to have a standard HTTP middleware executed before our HTTP handler.
To get started, let’s add httprouter into the mix.
As we can see from the code example above, we made a few changes to our code. The first to call out is rather than using
http.HandleFunc() to register our
handler function, we created an
httprouter.Router and used this to register our
handler function for the
A keen eye may also notice that with httprouter, we can register our functions under different HTTP Methods. This feature is one of the excellent features of httprouter over the default standard library. With httprouter, users can register different functions for different HTTP Methods. In our example, it doesn’t make sense to serve the
/hello route under any method other than a GET.
In addition to the changes in how we register our
handler function, it is worth calling out that the signature of our
handler function has also changed. Now, in addition to the standard
http.ResponseWriter and the
http.Request types, our function also receives an
With httprouter, one of the more advanced features is the ability to parameterize the URI. For example, instead of registering
/hello, we could register
/hello/:name. We could ask users to put their names within the URI, such as
/hello/ben. We could then access that parameter using the
httprouter.Params provided to our handler.
We now have a fully working HTTP service using httprouter; we can start creating our middleware function from here.
Creating our HTTP Middleware
For this article, we will create a basic logger middleware. This middleware aims to create a consistent log of HTTP requests regardless of what HTTP handler executes. That means before our
handler() function executes, we want our middleware to run first.
We will achieve this by creating a wrapper function and then including this wrapper function with our
handler() registration call.
In the above, we can see our wrapper function
middleware() is now being registered along with the
handler() function. The name of this technique is chaining; by wrapping the
handler() function with the
middleware() function, we are chaining the two functions together. The
middleware() function executes first, and at the end, the
middleware() function itself calls our
At this point, we have a working HTTP middleware powered by httprouter. For every custom handler we have, we can use this HTTP middleware to keep a consistent log. But what about non-custom handlers? What about things like PProf that has their handlers built with the standard HTTP signature?
Registering non-Custom Handlers with the HTTP middleware
A common practice for libraries such as PProf is to provide HTTP handlers as part of the package interface. The
net/http/pprof package has several functions that adhere to the standard
net/http method signature. Functions such as Index, which handles the
/debug/pprof/ end-point, and Profile, which serves the
/debug/pprof/profile CPU profiling end-point.
The problem is that these functions only take two inputs,
http.Request. They don't match our HTTP middleware's signature, but we still want to use our middleware for these functions.
Luckily, we can. Using the
http.HanderFunc() adapter we can convert these functions into
http.Handler's. And from there, we can use another small wrapper to wrap this
http.Handler with our middleware.
As we can see from the code above, we added a
wrapper() function with an input of
http.Handler, and that wrapper function is wrapping an anonymous function running
http.Handler.ServeHTTP() with our middleware.
With the addition of this
wrapper() function, we can now introduce our HTTP middleware on any standard HTTP handler, whether it's from PProf or other libraries.
Now that we have our
wrapper() function defined, let's go ahead and add the rest of the PProf parts.
In the above example, a keen eye may notice that about halfway through registering
/debug/pprof paths, we stop using the
http.HandlerFunc() adapter. Instead, we are registering a
pprof.Handler() function with various arguments. Some paths are satisfied with the PProf package via the
pprof.Handler() function, which returns an
With the above, we have a simple hello world application that shows how easy it is to create HTTP Middleware functions with httprouter. We can also see how it is possible to use this middleware even with packages like PProf, where the HTTP handlers use a different signature.
If you build a complex service with many routes and different handlers, routers such as httprouter can be a great tool. But the best thing about Go is, if all you need to do is simple HTTP services, you don’t need much beyond the standard library.
Originally published at https://bencane.com.