zai chang zai chang - 5 months ago 57
HTTP Question

Go - Logging responses to incoming HTTP requests inside http.HandleFunc

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()


// Server does some work here
// ...

// Insert debug code here, something like
// dump = http.DumpResponseFromWriter(w)
// fmt.Printf("%s\n", dump)
http.Handle("/path", r)


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):

func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  // do some stuff before
  next(rw, r)
  // do some stuff after

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:

func NewFooHandler(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // do some stuff before
        // do some stuff after

Then define your chain as a combination:

h := NewFooHandler(NewBarHandler(NewBazHandler(Sink)))

Now h is an http.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:

func NewResponseLoggingHandler(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {

        // switch out response writer for a recorder
        // for all subsequent handlers
        c := httptest.NewRecorder()
        next(c, r)

        // copy everything from response recorder
        // to actual response writer
        for k, v := range c.HeaderMap {
            w.Header()[k] = v


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):

func NewDefaultHandler(next http.HandlerFunc) http.HandlerFunc {
    return NewResponseLoggingHandler(NewOtherStuffHandler(next))

After this, whenever you start a chain like this:

h := NewDefaultHandler(...)

It will automatically include response logging and all the default stuff that you defined in NewDefaultHandler.