Go - 在http.HandleFunc中记录对传入HTTP请求的响应

zai*_*ang 4 http go

这是一个后续问题,如何检查写入http.ResponseWriter的http响应?因为解决方案需要伪造一个请求,这对于单元测试很有用,但对于实时服务器则不行.

我想转储我的Web服务返回的HTTP响应,以响应它从用户收到的日志文件(或控制台)的请求.输出应该告诉我标题是什么和JSON有效负载.

怎么去那个?

如果有一个httputil.DumpResponse等效,它将http.ResponseWriter作为参数而不是http.Response,那将是完美的,但目前我只能从http.ResponseWriter访问Header

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)
Run Code Online (Sandbox Code Playgroud)

thw*_*hwd 7

中间件链接

这个问题的一个常见解决方案是所谓的中间件链.有几个库提供此功能,例如negroni.

这是一种延续传递方式,你可以编写这样的中间件函数(取自negroni的自述文件):

func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  // do some stuff before
  next(rw, r)
  // do some stuff after
}
Run Code Online (Sandbox Code Playgroud)

然后negroni为您提供了一个HTTP处理程序,以正确的顺序调用您的中间件.

我们可以稍微不同地实现这个解决方案,而不是一个不太神奇和功能更强的(如函数式编程)方法.定义处理程序组合器如下:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将您的链定义为组合:

h := NewFooHandler(NewBarHandler(NewBazHandler(Sink)))
Run Code Online (Sandbox Code Playgroud)

现在hhttp.HandlerFuncfoo,然后是bar,然后是baz.Sink只是一个空的最后一个处理程序,它什么都不做("完成"链.)

将此解决方案应用于您的问题

定义处理程序组合器:

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)

    }
}
Run Code Online (Sandbox Code Playgroud)

现在问题归结为处理程序管理.您可能希望将此处理程序应用于特定类别中的所有链.为此,您可以再次使用组合器(这有点等同于negroni的Classic()方法):

func NewDefaultHandler(next http.HandlerFunc) http.HandlerFunc {
    return NewResponseLoggingHandler(NewOtherStuffHandler(next))
}
Run Code Online (Sandbox Code Playgroud)

在此之后,无论何时你开始这样的链:

h := NewDefaultHandler(...)
Run Code Online (Sandbox Code Playgroud)

它将自动包含响应记录和您定义的所有默认内容NewDefaultHandler.


Not*_*fer 6

这可以通过使用ServerMux不进行路由的自定义来实现,但是替换响应编写器,然后将请求转发到普通的多路复用器.由于ResponseWriter只是一个界面,我们可以轻松伪造它.

首先,我们将ResponseWriter接口与我们自己的响应编写器一起包装,它将记录所有内容并将所有功能传递给真正的响应编写器:

type DumpResponseWriter struct {
    // the underlying writer
    w http.ResponseWriter
    // more stuff you want to use for logging context (ip, headers, etc) here
}


func (w *DumpResponseWriter)Header() http.Header {
    return w.w.Header()
}

func (w *DumpResponseWriter)Write(b []byte) (int, error) {
        // You can add more context about the connection when initializing the writer and log it here
        log.Println("Writing < more context details here> ", string(b) )
        return w.w.Write(b)
}


func (w *DumpResponseWriter)WriteHeader(h int) {
    log.Println("Writing Header< more context details here> ", h)
    w.w.WriteHeader(h)
}
Run Code Online (Sandbox Code Playgroud)

这使我们的处理程序功能与以前一样,并且与我们使用"假"编写器这一事实无关......

func MyHandler(w http.ResponseWriter, r *http.Request) {

    w.Write([]byte("Hello world"))
}
Run Code Online (Sandbox Code Playgroud)

然后我们简单地用我们自己的代理多路复用器替换默认的多路复用器,它取代了编写器并让普通的ServeMux做它的事情:

func main(){

    // we don't use the default mux, but a custom one
    mux := http.NewServeMux()
    mux.HandleFunc("/", MyHandler)

    // now we intercept each request and forward it to the mux to do    the routing to the handlers.
    err := http.ListenAndServe(":1337", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // we wrap the response writer with our own. Add more context here if you want to the writer's instance
        writer := &DumpResponseWriter{w}

        // and we let our ordinary mux take care of things from here
        mux.ServeHTTP(writer, r)

        // We can also dump the headers after the handler is done. It will not print the standard headers though
        log.Printf("Response headers: %#v", w.Header())

    }))
    if err != nil {
        panic(err)
    }
}
Run Code Online (Sandbox Code Playgroud)

http://play.golang.org/p/hT1PCNxI-V