This is a follow-up question to In go, how to inspect the http response that is written to http.ResponseWriter? since the solution there requires faking a request, which works great for a unit test but not on a live server.
I would like to dump out HTTP response that my web service is returning in response to requests it receives from users into a log file (or to console). The output should tell me what the headers are and the JSON payload.
How does one go about that?
If there were a httputil.DumpResponse equivalent that takes a http.ResponseWriter as argument rather than http.Response it would be perfect, but currently I can only access the Header from http.ResponseWriter
r = mux.NewRouter()
r.HandleFunc("/path", func (w http.ResponseWriter, r *http.Request) {
fmt.Printf("r.HandleFunc /path\n")
resp := server.NewResponse()
defer resp.Close()
r.ParseForm()
// Server does some work here
// ...
// Insert debug code here, something like
//
// dump = http.DumpResponseFromWriter(w)
// fmt.Printf("%s\n", dump)
});
http.Handle("/path", r)
This can be achieved by using a custom
ServerMux
that does no routing, but replaces the response writer, and then forwards the request to a normal mux. Since ResponseWriter is just an interface we can fake it easily.First, we wrap the ResponseWriter interface with our own response writer, that will log everything and pass all functionality to a real response writer:
This leaves our handler func the same as before, and agnostic to the fact that we're using a "Fake" writer...
And then we simply replace the default mux with our own proxy mux, that replaces the writer and lets a regular ServeMux do its thing:
http://play.golang.org/p/hT1PCNxI-V
Middleware Chaining
A common solution to this problem is the so called middleware chain. There are several libraries that provide this functionality e.g. negroni.
It's a form of continuation-passing style where you write your middleware functions like this (taken from negroni's readme):
And then negroni gives you an HTTP handler that calls your middlewares in the right order.
We could implement this solution slightly differently to a less magical and more functional (as in functional programming) approach. Define handler combinators as follows:
Then define your chain as a combination:
Now
h
is anhttp.HandlerFunc
that does foo, then bar, then baz.Sink
is just an empty last handler, that does nothing (to "finish" the chain.)Applying this solution to your problem
Define a handler combinator:
Now the problem boils down to handler management. You'll probably want this handler applied to all chains in a certain category. For this, you can use combinators again (this is somewhat equivalent to negroni's
Classic()
method):After this, whenever you start a chain like this:
It will automatically include response logging and all the default stuff that you defined in
NewDefaultHandler
.Implementing Mat Ryer's approach with logging request id based on
httptest.ResponseRecorder
Disadvantages of using
httptest.ResponseRecorder
:Content-Length
andDate
are not available in recorderCode: