1package httpsnoop
2
3import (
4 "io"
5 "net/http"
6 "time"
7)
8
9// Metrics holds metrics captured from CaptureMetrics.
10type Metrics struct {
11 // Code is the first http response code passed to the WriteHeader func of
12 // the ResponseWriter. If no such call is made, a default code of 200 is
13 // assumed instead.
14 Code int
15 // Duration is the time it took to execute the handler.
16 Duration time.Duration
17 // Written is the number of bytes successfully written by the Write or
18 // ReadFrom function of the ResponseWriter. ResponseWriters may also write
19 // data to their underlaying connection directly (e.g. headers), but those
20 // are not tracked. Therefor the number of Written bytes will usually match
21 // the size of the response body.
22 Written int64
23}
24
25// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
26// returns the metrics it captured from it.
27func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
28 return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
29 hnd.ServeHTTP(ww, r)
30 })
31}
32
33// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
34// resulting metrics. This is very similar to CaptureMetrics (which is just
35// sugar on top of this func), but is a more usable interface if your
36// application doesn't use the Go http.Handler interface.
37func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
38 m := Metrics{Code: http.StatusOK}
39 m.CaptureMetrics(w, fn)
40 return m
41}
42
43// CaptureMetrics wraps w and calls fn with the wrapped w and updates
44// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
45// but allows one to customize starting Metrics object.
46func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
47 var (
48 start = time.Now()
49 headerWritten bool
50 hooks = Hooks{
51 WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
52 return func(code int) {
53 next(code)
54
55 if !(code >= 100 && code <= 199) && !headerWritten {
56 m.Code = code
57 headerWritten = true
58 }
59 }
60 },
61
62 Write: func(next WriteFunc) WriteFunc {
63 return func(p []byte) (int, error) {
64 n, err := next(p)
65
66 m.Written += int64(n)
67 headerWritten = true
68 return n, err
69 }
70 },
71
72 ReadFrom: func(next ReadFromFunc) ReadFromFunc {
73 return func(src io.Reader) (int64, error) {
74 n, err := next(src)
75
76 headerWritten = true
77 m.Written += n
78 return n, err
79 }
80 },
81 }
82 )
83
84 fn(Wrap(w, hooks))
85 m.Duration += time.Since(start)
86}