menu

Golang Tip: Wrapping http.ResponseWriter for Middleware

Utilizing Interfaces and Composition

Nick Stogner | Jun 2017

Using middleware provides a clean way to reduce code duplication when handling HTTP requests in Go. By utilizing the standard handler signature, func(w http.ResponseWriter, r *http.Request) we can write functions that are easily dropped into any Go web service. One of the most common uses of middleware is to provide request/response logging. In order to log responses we will need to capture the status code that was written by a nested handler.

Since we don’t have direct access to this status code, we will wrap the ResponseWriter that is passed down:

Because the http.ResponseWriter type is an interface we can pass a custom type to subsequent handlers as long as it implements all of the defined methods:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

By using composition we only need to explicitly implement the WriteHeader method that we want to modify. This is because we have embedded the ResponseWriter type in our struct so all of its methods get promoted and can be called on statusRecorder.

Note: We pass a pointer into ServeHTTP so that nested handlers can change the status field that we later reference.

Using the middleware is simple:

The above handle function calls two methods:

  1. WriteHeader: This calls our statusRecorder.WriteHeader method which records the 201 status for logging and then calls the original ResponseWriter.WriteHeader method.
  2. Write: This calls the original ResponseWriter.Write method directly.

Why do we choose to store the status in our struct rather than having the WriteHeader method do the logging? Calling WriteHeader inside of a handler is optional. An implicit 200-OK status is sent to the client if the status code is not set. In this situation the logging call would be missed.

Happy coding, and be on the lookout for other places to use composition and interfaces to simplify your Go code.