# httpmw `net/http` middleware for transport-layer concerns: panic recovery, CORS, request ID injection, and structured request logging. ## Overview Four composable `func(http.Handler) http.Handler` middleware functions: | Middleware | Responsibility | |---|---| | `Recover()` | Catches panics; writes 500; captures stack trace | | `RequestID(generator)` | Generates a unique ID; injects it via `logz.WithRequestID`; sets `X-Request-ID` header | | `RequestLogger(logger)` | Logs method, path, status, latency, and request ID after each request | | `CORS(origins)` | Sets CORS headers; short-circuits OPTIONS with 204 | No authentication or identity logic lives here — see `httpauth-firebase` for that. ## Install ``` go get code.nochebuena.dev/go/httpmw ``` ## Usage ```go // Recommended order — outermost middleware first mux.Use(httpmw.Recover()) mux.Use(httpmw.RequestID(uuid.NewString)) mux.Use(httpmw.RequestLogger(logger)) mux.Use(httpmw.CORS([]string{"https://example.com"})) ``` Pass `[]string{"*"}` to `CORS` to allow any origin (development only). ## Middleware ### Recover ```go mux.Use(httpmw.Recover()) ``` Wraps the handler in a `defer/recover`. On panic, writes `500 Internal Server Error` and captures `debug.Stack()`. No logger is required. ### RequestID ```go mux.Use(httpmw.RequestID(uuid.NewString)) ``` Calls `generator()` on every request, stores the ID with `logz.WithRequestID`, and writes it to the `X-Request-ID` response header. Must run before `RequestLogger` so the ID is in context when the logger reads it. ### RequestLogger ```go mux.Use(httpmw.RequestLogger(logger)) ``` Logs `method`, `path`, `status`, `latency`, and `request_id` after the inner handler returns. Uses `logger.Error` for 5xx responses and `logger.Info` for all others. ### CORS ```go mux.Use(httpmw.CORS([]string{"https://app.example.com", "https://admin.example.com"})) ``` Sets `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` for matching origins. OPTIONS requests are short-circuited with 204 No Content. Allowed methods: `GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS` Allowed headers: `Content-Type, Authorization, X-Request-ID` ## Logger interface `RequestLogger` accepts any value satisfying the `Logger` interface — satisfied by `logz.Logger` via duck typing: ```go type Logger interface { Info(msg string, args ...any) Error(msg string, err error, args ...any) With(args ...any) Logger } ``` ## StatusRecorder `StatusRecorder` is exported for use in custom middleware that needs to inspect the written status code: ```go rec := &httpmw.StatusRecorder{ResponseWriter: w, Status: http.StatusOK} next.ServeHTTP(rec, r) fmt.Println(rec.Status) // e.g. 404 ``` ## Dependencies - `code.nochebuena.dev/go/logz`