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

View file

@ -1,2 +1,51 @@
# lightmux-contrib
Opinionated middleware collection for [lightmux](https://git.juancwu.dev/juancwu/lightmux). Each middleware lives in its own sub-package so consumers only pull in the dependencies they actually use.
## Installation
```sh
go get git.juancwu.dev/juancwu/lightmux-contrib
```
## Packages
### `realip`
Replaces `r.RemoteAddr` with the originating client IP from `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`, or `X-Forwarded-For` (in that order).
```go
import "git.juancwu.dev/juancwu/lightmux-contrib/realip"
mux.Use(realip.New()) // always trust headers
mux.Use(realip.New(netip.MustParsePrefix("10.0.0.0/8"))) // gated by peer CIDR
```
With no arguments, `realip.New()` always honors the proxy headers — only register it when the service sits behind a trusted proxy. With one or more `netip.Prefix` arguments, the headers are honored only when the immediate peer's IP falls within one of them.
### `requestlog`
Emits a structured `http.request` record (method, path, status, duration, client) per request via [splinter](https://git.juancwu.dev/juancwu/splinter).
```go
import "git.juancwu.dev/juancwu/lightmux-contrib/requestlog"
mux.Use(requestlog.New(nil)) // splinter.Default() resolved at request time
mux.Use(requestlog.New(custom)) // custom *splinter.Logger
```
When pairing with `realip`, register `realip` first so the `client` field is the resolved client IP rather than the proxy peer:
```go
mux.Use(realip.New(), requestlog.New(nil))
```
### `recoverer`
Catches panics inside handlers, wraps the value with [errx](https://git.juancwu.dev/juancwu/errx) under op `recoverer`, logs it with stack via the standard `log` package, and writes a 500 response.
```go
import "git.juancwu.dev/juancwu/lightmux-contrib/recoverer"
mux.Use(recoverer.New())
```