/httpinfo

You don't need to write http.ResponseWriter wrappers anymore

Primary LanguageGoMIT LicenseMIT

httpinfo

godoc Licence Latest version

Build Status Code quality Code coverage

You don't need to write http.ResponseWriter wrappers anymore.

Motivation

It has become a common thing to write a wrapper of http.ResponseWriter because at some point it was a need to get request response information like the response status. In a complete request flow, a lot of middlewares required something (some requires the status, the number of bytes wrote, the route pattern used, ...). Moreover, some middlewares are also interacting with the response (like a panic or a timeout handler that sets the response status) causing unwanted behaviour (like a net/http log saying the response status should only we wrote one time). The naive approach of wrapping the http.ResponseWriter introduce some flaws and/or does not help to fix the already existing ones.

For example here:

type responseWriterWrapper struct{
    writer http.ResponseWriter
    status int
}

func (rww *responseWriterWrapper) WriteHeader(status int) {
    rww.status = status
    writer.WriteHeader(status)
}

// ...

If the original http.ResponseWriter was implementing http.Flusher, it is not the case anymore. To fix that we can add the Flush method:

func (rww *responseWriterWrapper) Flush() {
    if f, ok := (rww.writer).(http.Flusher); ok {
        f.Flush()
    }
}

but now our wrapper always implements the http.Flusher interface which can also cause unwanted behaviour.

For all these reasons I decided to write my last wrapper of http.ResponseWriter and it has the following features:

  • records the http response status, the number of bytes wrote, the execution time of the next handler, and helps to retrieve the route matching pattern.
  • writes the response status at the last possible time, to prevent the multiple status wrote error
  • keeps the same net/http interfaces implementation of the wrapped http.ResponseWriter
  • heavily tested
  • multi-thread safe
  • super simple to use

Usage / examples

// during the router setup...
router.Use(
    httpinfo.Record(),
    // other middlewares goes after, even the panic recover one
    myMiddleware,
)

func myMiddleware(next http.Handler) http.Handler {
    return func (rw http.ResponseWriter, r *http.Request ) {
        // call the next handler
        next.ServeHTTP(rw, r)

        // within any request handler, you're now able to get response info
        var (
            status        = httpinfo.Status(r)
            route         = httpinfo.Route(r)
            contentLength = httpinfo.ContentLength(r)
            latency       = httpinfo.ExecutionTime(r)
        )
        // ...
    }
}

More doc and examples in the httpinfo's godoc

License

This project is under the MIT licence, please see the LICENCE file.