• v0.9.0 ad2a9e465e

    Rene Nochebuena released this 2026-03-18 18:05:57 -06:00 | 2 commits to main since this release

    v0.9.0

    code.nochebuena.dev/go/httpmw

    Overview

    httpmw provides standalone net/http middleware functions for transport-layer concerns:
    panic recovery, CORS, request ID injection, and structured request logging. Each function
    is independent; no authentication or identity logic is included (see httpauth-firebase
    for that).

    This is the initial stable release. The API has been designed through multiple architecture
    reviews and validated end-to-end via the todo-api POC. It is versioned v0.9.0 rather than
    v1.0.0 because it has not yet been exercised across all production edge cases, and minor
    API refinements may follow.

    What's Included

    • Recover() func(http.Handler) http.Handler
      — catches panics from inner handlers, captures the stack trace, and writes 500; requires
      no logger injection
    • CORS(origins []string) func(http.Handler) http.Handler
      — sets Access-Control-Allow-* headers for matching origins; responds 204 to OPTIONS
      preflight requests; pass []string{"*"} for development
    • RequestID(generator func() string) func(http.Handler) http.Handler
      — generates a request ID using the provided function (e.g. uuid.NewString), injects it
      into the context via logz.WithRequestID, and sets the X-Request-ID response header
    • RequestLogger(logger Logger) func(http.Handler) http.Handler
      — logs method, path, status code, latency, and request ID after the inner handler returns;
      uses logger.Error for 5xx responses and logger.Info for all others
    • Logger interface — duck-typed; satisfied by logz.Logger:
      type Logger interface {
          Info(msg string, args ...any)
          Error(msg string, err error, args ...any)
          With(args ...any) Logger
      }
      
    • StatusRecorder — exported http.ResponseWriter wrapper that captures the written status
      code; useful for custom logging middleware

    Installation

    go get code.nochebuena.dev/go/httpmw@v0.9.0
    
    import "code.nochebuena.dev/go/httpmw"
    
    // Recommended middleware order (outermost first):
    mux.Use(httpmw.Recover())
    mux.Use(httpmw.RequestID(uuid.NewString))
    mux.Use(httpmw.RequestLogger(logger))
    mux.Use(httpmw.CORS([]string{"https://example.com"}))
    

    Design Highlights

    Direct logz import for context helpers. RequestID uses logz.WithRequestID and
    RequestLogger uses logz.GetRequestID to store and retrieve the request ID in the
    context. These helpers use an unexported context key; re-implementing the key in httpmw
    would break interoperability with logz.Logger.WithContext downstream. This is a
    documented exception to the global ADR-001 duck-type rule — Logger injection is still
    duck-typed, but context key access requires the direct import.

    Recover requires no configuration. It captures the stack trace via debug.Stack
    internally but does not currently log it. The stack is retained for future logger injection.
    This keeps Recover usable with zero setup.

    CORS is allowlist-based. Allowed methods (GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS)
    and allowed headers (Content-Type, Authorization, X-Request-ID) are package-wide constants.
    Per-route configuration is not supported.

    No middleware is installed by default. The package exports functions; the application
    chooses which middleware to use and in what order.

    Known Limitations & Edge Cases

    • Logger.With returns Logger (the httpmw interface type), not logz.Logger. A
      caller that implements Logger and calls With must ensure their implementation returns
      a value that also satisfies the interface. Wrapping a logz.Logger directly requires a
      thin adapter struct at the call site.
    • Recover does not log panics in the current release. Panic information is captured but
      discarded. If panic logging is required, wrap Recover with a custom middleware that logs
      first, or wait for logger injection to be added.
    • No rate-limiting middleware is included by design. Rate limiting belongs at the
      infrastructure layer (reverse proxy, API gateway) or in a dedicated module.
    • No authentication or RBAC middleware is included by design. See httpauth-firebase.
    • CORS headers are package-wide constants. Per-route CORS configuration requires the
      router's built-in support.
    • RequestID must run before RequestLogger in the middleware chain. If the order is
      reversed, request IDs will be empty in log records.

    v0.9.0 → v1.0.0 Roadmap

    • Add optional logger injection to Recover so panics are logged with the stack trace.
    • Evaluate whether Logger.With should return any or be removed from the interface to
      eliminate the adapter requirement at call sites.
    • Consider exposing the CORS allowed methods and headers as Config fields.
    • Validate behaviour under high concurrency for StatusRecorder (ensure no data races
      when the writer is used concurrently).
    Downloads