capture_metrics.go 2.5 KB

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