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:
WriteHeader
: This calls our statusRecorder.WriteHeader
method which records the 201
status for logging and then calls the original ResponseWriter.WriteHeader
method.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.