The http.Handler wrapper technique in #golang UPDATED

Mat Ryer
Mat Ryer
Feb 24, 2017 · 4 min read
Image for post
Image for post

tl;dr: functions that take an http.Handler and return a new one can do things before and/or after the handler is called, and even decide whether to call the original handler at all.

If you’re building web services using Go (if you’re not, why not?) and you’re not using any middleware packages (and even if you are), then you need to understand the power of wrapping http.Handler types.

An http.Handler wrapper is a function that has one input argument and one output argument, both of type http.Handler.

Wrappers have the following signature:

func(http.Handler) http.Handler

The idea is that you take in an http.Handler and return a new one that does something else before and/or after calling the ServeHTTP method on the original. For example, a simple logging wrapper might look like this:

func log(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r
*http.Request) {
h.ServeHTTP(w, r) // call original

Here, our log function returns a new handler (remember that http.HandlerFunc is a valid http.Handler too) that will print the “Before” string, and call the original handler before printing out the “After” string.

Now, wherever I pass my original http.Handler I can wrap it such that:

http.Handle("/path", handleThing)


http.Handle("/path", log(handleThing))

When would you use wrappers?

This approach can be used to address lots of different situations, including but not limited to:

  • Logging and tracing
  • Validating the request; such as checking authentication credentials
  • Writing common response headers

To call or not to call

Wrappers get to decide whether to call the original handler or not. If they want to, they can even intercept the request and response on their own. Say a key URL parameter is mandatory in our API:

func checkAPIKey(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if len(r.URL.Query().Get("key") == 0) {
http.Error(w, "missing key", http.StatusUnauthorized)
return // don't call original handler
h.ServeHTTP(w, r)

The checkAPIKey wrapper will make sure there is a key, and if there isn’t, it will return with an Unauthorized error. It could be extended to validate the key in a datastore, or ensure the caller is within acceptable rate limits etc.


Using Go’s defer statement we can add code that we can be sure will run whatever happens inside our original handler:

func log(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer log.Println("After")
h.ServeHTTP(w, r)

Now, even if the code inside our handler panics, we’ll still see the “After” line printed.

Passing arguments to the wrappers

We can pass additional arguments into our wrapper functions if we want our wrappers to be reused with slight variants.

Our checkAPIKey wrapper could be changed to support checking for the presence of any URL parameters:

func MustParams(h http.Handler, params ...string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){

q := r.URL.Query()
for _, param := range params {
if len(q.Get(param)) == 0 {
http.Error(w, "missing "+param, http.StatusBadRequest)
return // exit early
h.ServeHTTP(w, r) // all params present, proceed

We can use MustParams to insist on different parameters by calling it with different arguments. The params argument inside MustParams will be captured in the closure of the function we return, so there’s no need to add a struct to store state here:

http.Handler("/user", MustParams(handleUser, "key", "auth"))
http.Handler("/group", MustParams(handleGroup, "key"))
http.Handler("/items", MustParams(handleSearch, "key", "q"))

Wrappers within wrappers

Given the self-similar nature of this pattern, and the fact that we haven’t changed the http.Handler signature at all, we are able to easily chain wrappers as well as nest them in interesting ways.

If we have a MustAuth wrapper that will validate an auth token for a request, it might well insist on the auth parameter being present. So we can use the MustParams wrapper inside our MustAuth one:

func MustAuth(h http.Handler) http.Handler {
checkauth := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
err := validateAuth(r.URL.Query().Get("auth"))
if err != nil {
http.Error(w, "bad auth param", http.StatusUnauthorized)
h.ServeHTTP(w, r) })
return MustParams(checkauth, "auth")

Intercepting the ResponseWriter

The http.ResponseWriter type is an interface, which means we can intercept a request and swap it for a different object — provided our object satisfies the same interface.

You might decide to do this if you want to capture the response body, perhaps to log it:

func log(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w := NewResponseLogger(w)
h.ServeHTTP(w, r)

The NewResponseLogger type would provide its own Write function and log the data:

func (r *ResponseLogger) Write(b []byte) (int, error) {
log.Print(string(b)) // log it out
return r.w.Write(b) // pass it to the original ResponseWriter

It logs the string out, and also writes the bytes to the original http.ResponseWriter, so that the caller of the API still gets the response.

Handlers, all the way down

Aside from just wrapping individual handlers as we have done so far, since everything is an http.Handler, we can wrap entire servers in one go:

http.ListenAndServe(addr, log(server))

More on writing middleware

If you’re interested in learning more about writing middleware in Go, check out


If you have any questions about specific use cases, tweet me and I’ll be happy to help.

Buy my book, obviously

Image for post
Image for post

Learn more about the practicalities of Go with .

If you liked the first edition — you’ll love this one.

If you didn’t love the first edition — then you might love this one.

If you hated the first edition — buy the Second Edition for your enemies.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store