add iplimit middleware

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
juancwu 2026-04-26 23:28:40 +00:00
commit 522ac09cdc
5 changed files with 371 additions and 0 deletions

View file

@ -72,3 +72,22 @@ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
```
Loopback, private, link-local, and unspecified addresses are skipped to preserve API quota. Lookup failures are logged at warn level via [splinter](https://git.juancwu.dev/juancwu/splinter) (pass `nil` for `splinter.Default()` resolved at request time, or supply a custom `*splinter.Logger`) and let the request through with no context value — handlers should treat the `From` lookup as optional.
### `iplimit`
Per-IP token-bucket rate limiter backed by [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate). Each unique IP gets its own bucket; rejected requests get a 429 with a `Retry-After` header (whole seconds, ceiling).
```go
import (
"time"
"git.juancwu.dev/juancwu/lightmux-contrib/iplimit"
"git.juancwu.dev/juancwu/lightmux-contrib/realip"
"golang.org/x/time/rate"
)
// 5 req/s steady state, bursts of 10
mux.Use(realip.New(), iplimit.New(rate.Every(time.Second/5), 10))
```
Pair with `realip` upstream so the limiter keys on the originating client IP rather than the proxy peer. Loopback, private, link-local, and unspecified addresses pass through unlimited — they are typically internal callers (health checks, dev) that should not be rate limited. Idle buckets are evicted after 10 minutes of inactivity by an inline sweep, so no background goroutines are spawned.