Golang Http Handlers as Middleware

Most modern web stacks allow the “filtering” of requests via stackable/composable middleware, allowing you to cleanly separate cross-cutting concerns from your web application. This weekend I needed to hook into go’s http.FileServer and was pleasantly surprised how easy it was to do.

Let’s start with a basic file server for /tmp:

main.go
1
2
3
func main() {
    http.ListenAndServe(":8080", http.FileServer(http.Dir("/tmp")))
}

This starts up a local file server at :8080. How can we hook into this so we can run some code before file requests are served? Let’s look at the method signature for http.ListenAndServe:

1
func ListenAndServe(addr string, handler Handler) error

So it looks like http.FileServer returns a Handler that knows how to serve files given a root directory. Now let’s look at the Handler interface:

1
2
3
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Because of go’s granular interfaces, any object can be a Handler so long as it implements ServeHTTP. It seems all we need to do is construct our own Handler that wraps http.FileServer’s handler. There’s a built in helper for turning ordinary functions into handlers called http.HandlerFunc:

1
type HandlerFunc func(ResponseWriter, *Request)

Then we just wrap http.FileServer like so:

main.go
1
2
3
4
5
6
7
8
9
10
11
12
func OurLoggingHandler(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Println(*r.URL)
    h.ServeHTTP(w, r)
  })
}

func main() {
    fileHandler := http.FileServer(http.Dir("/tmp"))
    wrappedHandler := OurLoggingHandler(fileHandler)
    http.ListenAndServe(":8080", wrappedHandler)
}

Go has a bunch of other builtin handlers like TimeoutHandler and RedirectHandler that can be mixed and matched the same way.

go, http