Writing middleware in #golang and how Go makes it so much fun.

I recently did a talk at the Go UK Conference in London (you can see the hosted slides) on Building APIs with Go where I touched upon a very interesting pattern which I will, at the request of some of my online Gopher friends, explain in more detail here.

The pattern I describe in this article is an abstraction above those I discuss in my article on The http.HandlerFunc wrapper technique in #golang, which is simpler and worth checking out before continuing if you haven’t already.

Here’s the video:

When we talk about Middleware in Go, at its simplest, we are really talking about running code before and/or after our handler code in a HTTP request lifecycle. For example, logging middleware might write the incoming request details to a log, then call the handler code, before writing details about the response to the log. One of the cool things about middleware, if implemented correctly, is that these units are extremely flexible, reusable, and sharable.

Good middleware shouldn’t deviate at all from the standard library —the http.Handler type is sacred.

Keeping HTTP handling code to a simple signature (w http.ResponseWriter, r *http.Request) means that any Go programmer can come to your code, and jump right in without having to learn too much about the code.

Rather than writing functions or types that take in an http.HandlerFunc (or http.Handler) and return a wrapper alternative, we are going to represent this idea in a type of its own.

Adapter type

type Adapter func(http.Handler) http.Handler

The Adapter type (it gets its name from the adapter pattern — also known as the decorator pattern) above is a function that both takes in and returns an http.Handler. This is the essence of the wrapper; we will pass in an existing http.Handler, the Adapter will adapt it, and return a new (probably wrapped) http.Handler for us to use in its place. So far this is not much different from just wrapping http.HandlerFunc types, however, now, we can instead write functions that themselves return an Adapter.

It’s getting a little meta, so let’s look at some code.

func Notify() Adapter {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("before")
defer log.Println("after")
h.ServeHTTP(w, r)
}
}
}

It’s clear that the Notify example above will adapt an http.Handler to write out the “before” and “after” strings, allowing the original http.Handler `h` to do whatever it was already going to do in between. But it might not be clear how or why we are doing it in this way.

The Notify function returns an Adapter, which is just a function that takes and returns an http.Handler. When Notify is called, the returning function can be then called to adapt handlers.

We are using the http.HandlerFunc type to allow us to make an http.Handler out of a plain old function — if you’re not sure what’s going on there, be sure to check out the http.HandlerFunc source code in the standard library, and pay particular attention to the fact that the http.HandlerFunc implements http.Handler by having its own ServeHTTP method.

You could just skip the extra step of returning the Adapter from a function altogether, except when you consider that we may want our adapters to have state of their own.

The following example is going to allow us to specify the log.Logger (from the standard package) that we want our “before” and “after” strings written to.

func Notify(logger *log.Logger) Adapter {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Println("before")
defer logger.Println("after")
h.ServeHTTP(w, r)
}
}
}

Now, thanks to closures, we can capture the logger object inside our functions without spoiling the Adapter interface (it still takes and returns a single http.Handler).

Using the Adapters

The simplest way (albeit somewhat confusing to read) to make use of an adapter is to get the Adapter function and immediately call it:

logger := log.New(os.Stdout, "server: ", log.Lshortfile)
http.Handle("/", Notify(logger)(indexHandler))

A cleaner approach is to provide yourself an Adapt function, that can do all your adapting for you.

func Adapt(h http.Handler, adapters ...Adapter) http.Handler

Our Adapt function takes the handler you want to adapt, and a list of our Adapter types. The result of our Notify function is an acceptable Adapter. Our Adapt function will simply iterate over all adapters, calling them one by one (in reverse order) in a chained manner, returning the result of the first adapter.

func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}
To make the adapters run in the order in which they are specified, you would reverse through them in the Adapt function, rather than just ranging over them. Thanks to yimmy149 for pointing this out.

Assuming we have several suitable functions that return Adapters, we can then implement middleware on our handlers by calling the Adapt function:

http.Handle("/", Adapt(indexHandler, AddHeader("Server", "Mine"),
CheckAuth(providers),
CopyMgoSession(db),
Notify(logger),
)

The execution for indexHandler would then run like this:

  • Notify (log “before” string — maybe with some other bits)
  • CopyMgoSession (copy db session and make it available to handler)
  • CheckAuth (check auth cresds and bail if failed)
  • AddHeader (add response header)
  • indexHandler
  • Any AddHeader deferred functions
  • Any CheckAuth deferred functions
  • CopyMgoSession deferred function (probably closing the database copy)
  • Notify deferred function (writing “after” string)

There are lots of different types of middleware that could be implemented with this pattern, and the nice thing is, it doesn’t break the http.Handler interface allowing you to apply middleware wherever the http.Handler interface is used, such as globally in http.ServeAndListen, or individually in http.Handle or http.HandleFunc. Since packages like Gorilla’s mux use http.Handler — your middleware will automatically be supported.

Questions?

Do feel free to tweet me @matryer if you have any questions about this.

  • EDIT: Code tweaks for readability.
  • EDIT 31 Aug: The Adapt execution was reversed in my example.