httpsnoop

repository·master·Indexed 22 days ago

https://github.com/felixge/httpsnoop

A Go package for capturing HTTP metrics—such as response time, bytes written, and status codes—from http.Handlers. It wraps http.ResponseWriter while preserving optional interface compatibility for http.Flusher, http.Hijacker, http.Pusher, io.StringWriter, and others to prevent breakage in middleware and libraries.

Tokens
4.7K
Snippets
10
Records
23
Agent score
78%

What's inside httpsnoop

  1. How httpsnoop handles ResponseWriter interfaces

    master

    A common mistake when instrumenting http.Handler is naive wrapping of http.ResponseWriter, which hides optional interfaces like http.Flusher, http.CloseNotifier, http.Hijacker, http.Pusher, and io.ReaderFrom. This can break applications that rely on these interfaces.

    httpsnoop solves this by detecting which additional interfaces the original http.ResponseWriter implements and returning a wrapped version that implements that exact same set of interfaces. This ensures compatibility with non-trivial applications while still allowing metric capture.

  2. Capture HTTP metrics with httpsnoop

    master
    The httpsnoop package provides a way to capture HTTP-related metrics—such as response time, bytes written, and HTTP status codes—from your application's http.Handlers. It achieves this by wrapping the http.ResponseWriter interface. The package offers both high-level handler wrapping and a low-level API for manual http.ResponseWriter wrapping.
  3. Supported interfaces for wrapped http.ResponseWriter

    master

    The httpsnoop package generates specialized wrapper types that implement various combinations of standard Go interfaces. This ensures that if the underlying http.ResponseWriter supports specific features (like Hijacking, Pushing, or Deadlines), the wrapped version preserves that capability.

    Commonly supported interfaces include:

    • http.ResponseWriter (Base interface)
    • http.Hijacker (via Hijack())
    • http.Pusher (via Push())
    • io.ReaderFrom (via ReadFrom())
    • io.StringWriter (via WriteString())
    • net.Error / Deadliner (via SetReadDeadline() and SetWriteDeadline())
    • Custom interfaces like fullDuplexEnabler (via EnableFullDuplex())

    The specific type returned by a snooping function depends on the intersection of these interfaces supported by the original writer.

  4. Understand the generated ResponseWriter wrappers

    master

    The httpsnoop package uses code generation to create specialized wrappers for http.ResponseWriter. These wrappers (named rwXXX where XXX is a combination index) are designed to preserve the original interface capabilities of the underlying http.ResponseWriter while adding snooping functionality.

    When you use a snooping function, it returns a wrapper that implements a specific combination of standard Go interfaces. This ensures that if the original ResponseWriter supported features like http.Pusher, http.Hijacker, or io.StringWriter, the wrapped version continues to support them, preventing type assertion failures in middleware or downstream handlers.

    Commonly supported interface combinations include:

    • http.ResponseWriter + httpFlushError + io.ReaderFrom + http.Pusher + io.StringWriter (e.g., rw147, rw151, rw155, rw159, rw171, rw175, rw183)
    • http.ResponseWriter + httpFlushError + http.Hijacker + deadliner (e.g., rw168, rw169, rw170, rw171, rw172, rw173, rw174, rw175)
    • http.ResponseWriter + httpFlushError + http.Hijacker + fullDuplexEnabler (e.g., rw164, rw165, rw166, rw167, rw168)

    All generated wrappers implement the Unwrap() http.ResponseWriter method, allowing you to retrieve the original, unwrapped http.ResponseWriter.

  5. Understand the generated http.ResponseWriter wrappers

    master

    The httpsnoop package uses code generation to create a wide variety of http.ResponseWriter wrappers. These wrappers are designed to intercept calls to the standard http.ResponseWriter while preserving as many optional interface implementations as possible (e.g., http.Hijacker, http.Pusher, io.StringWriter, etc.).

    Each generated type (e.g., rw60, rw61, rw95) represents a specific combination of interfaces. This ensures that when you wrap a response writer, the resulting object still satisfies the same interface checks as the original, preventing breakage in middleware or libraries that rely on type assertions for advanced features like HTTP/2 Server Push or connection hijacking.

  6. Access the underlying ResponseWriter with Unwrap

    master

    Because httpsnoop wraps the http.ResponseWriter to capture metrics, it may hide additional interfaces implemented by the original writer (such as http.Flusher, http.Hijacker, etc.).

    If you need to access the original http.ResponseWriter and type-assert it to its original interfaces, use httpsnoop.Unwrap(w).

    // Use Unwrap to get the original ResponseWriter
    originalW := httpsnoop.Unwrap(w)
    // Now you can type-assert to specific interfaces
    if flusher, ok := originalW.(http.Flusher); ok {
    	flusher.Flush()
    }
  7. Capture HTTP metrics with CaptureMetrics

    master

    Use httpsnoop.CaptureMetrics to wrap an http.Handler and automatically capture request metrics including the HTTP status code, response duration, and the number of bytes written. This is useful for logging or monitoring request performance.

    CaptureMetrics returns an httpsnoop.Metrics struct containing:

    • Code: The HTTP status code.
    • Duration: The time taken to process the request.
    • Written: The number of bytes written to the response.

    Note that CaptureMetrics handles edge cases like WriteHeader being called multiple times or not at all, and manages concurrent calls to http.ResponseWriter methods.

    // myH is your app's http handler, perhaps a http.ServeMux or similar.
    var myH http.Handler
    // wrappedH wraps myH in order to log every request.
    wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    	m := httpsnoop.CaptureMetrics(myH, w, r)
    	log.Printf(
    		"%s %s (code=%d dt=%s written=%d)",
    		r.Method,
    		r.URL,
    		m.Code,
    		m.Duration,
    		m.Written,
    	)
    })
    http.ListenAndServe(":8080", wrappedH)
  8. Unwrap a wrapped ResponseWriter

    master

    All generated wrapper types in httpsnoop implement the Unwrap() http.ResponseWriter method. This allows you to retrieve the original, underlying http.ResponseWriter from the wrapper.

    // Assuming 'w' is one of the generated rwXXX types
    originalWriter := w.Unwrap()
  9. Unwrap a wrapped http.ResponseWriter

    master

    Every generated wrapper type in httpsnoop implements the Unwrap() http.ResponseWriter method. This allows you to access the underlying, original http.ResponseWriter that was passed into the snooping wrapper.

    // Assuming 'w' is one of the generated rw types
    originalWriter := w.Unwrap()
  10. Unwrap nested http.ResponseWriter wrappers

    master

    When using httpsnoop to wrap an http.ResponseWriter, you may end up with multiple layers of wrappers. Use the Unwrap function to traverse through all layers of Unwrapper implementations to retrieve the original, underlying http.ResponseWriter.

    // Unwrap returns the underlying http.ResponseWriter from within zero or more
    // layers of httpsnoop wrappers.
    originalW := httpsnoop.Unwrap(wrappedW)
  11. Wrap an http.ResponseWriter with interceptor hooks

    master

    Use Wrap(w http.ResponseWriter, hooks Hooks) to create a wrapped version of an existing http.ResponseWriter. The returned object implements the exact same set of optional interfaces as the original (e.g., http.Flusher, http.Hijacker, io.StringWriter, etc.).

    Each field in the Hooks struct acts as a middleware for a specific method. If a hook is provided, it intercepts the call, allowing you to modify arguments or return values. If no hook is provided for a method, the call is passed through to the underlying ResponseWriter directly.

    Key Behaviors:

    • Interface Fidelity: The wrapped ResponseWriter preserves all optional interfaces implemented by the original w.
    • Precedence: Exact matching hooks take precedence. For example, if a WriteString hook is configured, it will be called even if a Write hook is also present.
    • Fallbacks:
      • If WriteString is called but only a Write hook is configured, the call is routed through the Write hook using []byte(s).
      • If FlushError is called but only a Flush hook is configured, FlushError is routed through the Flush hook while preserving the original error.
    // Example conceptual usage
    wrapped := httpsnoop.Wrap(originalResponseWriter, httpsnoop.Hooks{
    	Write: func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc {
    		return func(b []byte) (int, error) {
    			// Do something before or after the write
    			return next(b)
    		}
    	},
    })
  12. Unwrap the underlying http.ResponseWriter

    master
    All generated rw types (e.g., rw306, rw307, etc.) implement an Unwrap() http.ResponseWriter method. This allows you to retrieve the original, unwrapped http.ResponseWriter that was passed into the httpsnoop wrapper, bypassing the instrumentation.