README.md
1# httpsnoop
2
3Package httpsnoop provides an easy way to capture http related metrics (i.e.
4response time, bytes written, and http status code) from your application's
5http.Handlers.
6
7Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
8which is also exposed for users interested in a more low-level API.
9
10[![GoDoc](https://godoc.org/github.com/felixge/httpsnoop?status.svg)](https://godoc.org/github.com/felixge/httpsnoop)
11[![Build Status](https://travis-ci.org/felixge/httpsnoop.svg?branch=master)](https://travis-ci.org/felixge/httpsnoop)
12
13## Usage Example
14
15```go
16// myH is your app's http handler, perhaps a http.ServeMux or similar.
17var myH http.Handler
18// wrappedH wraps myH in order to log every request.
19wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20 m := httpsnoop.CaptureMetrics(myH, w, r)
21 log.Printf(
22 "%s %s (code=%d dt=%s written=%d)",
23 r.Method,
24 r.URL,
25 m.Code,
26 m.Duration,
27 m.Written,
28 )
29})
30http.ListenAndServe(":8080", wrappedH)
31```
32
33## Why this package exists
34
35Instrumenting an application's http.Handler is surprisingly difficult.
36
37However if you google for e.g. "capture ResponseWriter status code" you'll find
38lots of advise and code examples that suggest it to be a fairly trivial
39undertaking. Unfortunately everything I've seen so far has a high chance of
40breaking your application.
41
42The main problem is that a `http.ResponseWriter` often implements additional
43interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
44`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
45in your own struct that also implements the `http.ResponseWriter` interface
46will hide the additional interfaces mentioned above. This has a high change of
47introducing subtle bugs into any non-trivial application.
48
49Another approach I've seen people take is to return a struct that implements
50all of the interfaces above. However, that's also problematic, because it's
51difficult to fake some of these interfaces behaviors when the underlying
52`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
53because an application may choose to operate differently, merely because it
54detects the presence of these additional interfaces.
55
56This package solves this problem by checking which additional interfaces a
57`http.ResponseWriter` implements, returning a wrapped version implementing the
58exact same set of interfaces.
59
60Additionally this package properly handles edge cases such as `WriteHeader` not
61being called, or called more than once, as well as concurrent calls to
62`http.ResponseWriter` methods, and even calls happening after the wrapped
63`ServeHTTP` has already returned.
64
65Unfortunately this package is not perfect either. It's possible that it is
66still missing some interfaces provided by the go core (let me know if you find
67one), and it won't work for applications adding their own interfaces into the
68mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
69`http.ResponseWriter` and type-assert the result to its other interfaces.
70
71However, hopefully the explanation above has sufficiently scared you of rolling
72your own solution to this problem. httpsnoop may still break your application,
73but at least it tries to avoid it as much as possible.
74
75Anyway, the real problem here is that smuggling additional interfaces inside
76`http.ResponseWriter` is a problematic design choice, but it probably goes as
77deep as the Go language specification itself. But that's okay, I still prefer
78Go over the alternatives ;).
79
80## Performance
81
82```
83BenchmarkBaseline-8 20000 94912 ns/op
84BenchmarkCaptureMetrics-8 20000 95461 ns/op
85```
86
87As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
88overhead of ~500 ns per http request on my machine. However, the margin of
89error appears to be larger than that, therefor it should be reasonable to
90assume that the overhead introduced by `CaptureMetrics` is absolutely
91negligible.
92
93## License
94
95MIT
96