-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
Problem
A very common pattern in Go HTTP servers is to implement an http.ResponseWriter
that wraps another http.ResponseWriter
and captures the status code. This is often used for logging and metrics collection.
For example,
type statusCaptureWriter struct {
http.ResponseWriter
status int
}
func (scw *statusCaptureWriter) WriteHeader(status int) {
scw.status = status
scw.ResponseWriter.WriteHeader(status)
}
type loggedHandler struct {
handler http.Handler
logger SomeLogger
}
func (h *loggedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
scw := &statusCaptureWriter{ResponseWriter: w}
h.handler.ServeHTTP(scw, r)
h.logger.Logf("status=%d ...", scw.status)
}
I've written something like this a bunch of times. You can find examples in nearly every Go web framework out there. One example is https://github.com/urfave/negroni/blob/master/response_writer.go#L13-L26.
There are some issues with this approach. For instance, my statusCaptureWriter
doesn't implement other interfaces like http.Flusher
, http.CloseNotifier
or http.Pusher
. I can't determine at compile time whether the underlying http.ResponseWriter
implementation implements any of these interfaces, so if I choose to implement them I might lie to callers at higher levels of the stack and inadvertently break things. (This particularly a problem with CloseNotifier.)
Proposal (rejected, see below)
I'd like to propose an additional interface, http.Statuser
(better name welcome) that exposes the status code within a http.ResponseWriter
implementation. The internal http.(*response)
implementation already tracks the status code written, so this can just be exposed and it will automatically implement this interface.
Software could avoid wrapping the http.ResponseWriter
by instead type asserting it to http.Statuser
and getting the status as needed there. (And it could optionally continue to wrap the ResponseWriter as needed until this is widely deployed.)
type loggedHandler struct {
handler http.Handler
logger SomeLogger
}
func (h *loggedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Could check for http.Statuser implementation here and wrap http.ResponseWriter
// if necessary, but omitting for brevity
h.handler.ServeHTTP(w, r)
status := 0
if s, ok := w.(http.Statuser); ok {
status = s.Status()
}
h.logger.Logf("status=%d ...", status)
}
Alternative proposal
Implement an httptrace.ServerTrace
struct that is analogous to the ClientTrace
already there. See #18997 (comment) for more info.