Logging responses to incoming HTTP requests inside http.HandleFunc

前端 未结 3 2045
慢半拍i
慢半拍i 2020-12-10 17:38

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 wor

3条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-10 18:16

    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
            next(r,w)
            // 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
            }
            w.WriteHeader(c.Code)
            c.Body.WriteTo(w)
    
        }
    }
    

    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.

提交回复
热议问题