Description
Proposal Details
Expose status code of http response
In this proposal I want to discuss the possibility of exposing the http status code of an HTTP response of an HTTP server.
This is mostly useful for logging and telemetry purposes.
Current situation
Currently, to capture the status code of a request you have to something like this:
type statusRecorder struct {
writer http.ResponseWriter
writeHeaderCalled bool
StatusCode int
}
var _ http.ResponseWriter = &statusRecorder{}
func (s *statusRecorder) Header() http.Header {
return s.writer.Header()
}
func (s *statusRecorder) Write(data []byte) (int, error) {
if !s.writeHeaderCalled {
// Replicate behaviour from http.ResponseWriter.
// When Write() is called before WriteHeader(), a 200 OK is returned.
s.WriteHeader(http.StatusOK)
}
return s.writer.Write(data)
}
func (s *statusRecorder) WriteHeader(statusCode int) {
s.writeHeaderCalled = true
s.StatusCode = statusCode
s.writer.WriteHeader(statusCode)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
_ = http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
st := &statusRecorder{writer: w}
mux.ServeHTTP(st, r)
fmt.Printf("Request completed with code %d\n", st.StatusCode)
}),
}
}
This works but has a major issue in which it does not expose any optional interfaces implemented by the internal http.response
struct.
Things like http.Flusher
, http.Hijacker
, etc... are lost.
We could implement these extra optional interfaces in the statusRecorder
described above but what if the original http.ResponseWriter
did not implement this interface?
Code using the wrapped request might incorrectly think that extra functionality is available even though it isn't.
My proposal
Since we can not wrap the http.ResponseWriter
without losing these optional interfaces the only real option that we have left is to add another optional interface.
// Ignore function / interface names for now, feedback on this is much appreciated.
type StatusExposer interface {
StatusCode() int
}
The http.request
struct would then implement this extra interface. This would allow you to do the following:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
_ = http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// We are not wrapping w, so we do not lose optional interfaces implemented by w.
mux.ServeHTTP(w, r)
var code int
if st, ok := w.(http.StatusExposer); ok {
code = st.StatusCode()
}
fmt.Printf("Request completed with code %d\n", code)
}),
}
}
Most structs inside net/http
that implement http.ResponseWriter
already have easy access to the returned HTTP status code or require minimal changes to save this.
An example implementation of the StatusCode()
function could be as simple as adding:
// in src/net/http/server.go
func (w *response) StatusCode() int {
return w.status
}
The thing that might be a bit tricky with implementation is how to deal with websockets, etc... but I think this is easily handled by tests.