add realip, requestlog, recoverer middlewares

Initial implementation of lightmux-contrib, a sibling module to
lightmux that hosts opinionated middlewares with one sub-package per
middleware:

- realip: resolves the originating client IP from CF-Connecting-IP,
  True-Client-IP, X-Real-IP, or X-Forwarded-For. Optional peer-CIDR
  allowlist via netip.Prefix.
- requestlog: emits a structured http.request record (method, path,
  status, duration, client) per request via splinter.
- recoverer: catches panics, wraps with errx under op "recoverer",
  logs with stack, and writes a 500 response.

Each package exposes a single New(...) constructor returning
func(http.Handler) http.Handler. The contrib module intentionally
does not import lightmux — middlewares interoperate via the standard
stdlib middleware shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
juancwu 2026-04-26 14:03:04 +00:00
commit b26ef7439e
10 changed files with 624 additions and 0 deletions

36
recoverer/recoverer.go Normal file
View file

@ -0,0 +1,36 @@
// Package recoverer catches panics inside HTTP handlers, logs them with stack
// trace, and writes a 500 response.
package recoverer
import (
"log"
"net/http"
"runtime/debug"
"git.juancwu.dev/juancwu/errx"
)
const op = "recoverer"
// New returns a panic-recovery middleware.
func New() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
rec := recover()
if rec == nil {
return
}
var err error
if e, ok := rec.(error); ok {
err = errx.Wrap(op, e)
} else {
err = errx.Newf(op, "panic: %v", rec)
}
log.Printf("%v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}()
next.ServeHTTP(w, r)
})
}
}