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 :

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:

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.