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:
parent
9dc0fc5d26
commit
b26ef7439e
10 changed files with 624 additions and 0 deletions
78
realip/realip.go
Normal file
78
realip/realip.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Package realip resolves the originating client IP from common reverse-proxy
|
||||
// and CDN headers (Cloudflare, nginx) and replaces r.RemoteAddr so downstream
|
||||
// handlers and middlewares see the real client.
|
||||
package realip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var headers = []string{
|
||||
"CF-Connecting-IP",
|
||||
"True-Client-IP",
|
||||
"X-Real-IP",
|
||||
"X-Forwarded-For",
|
||||
}
|
||||
|
||||
// New returns a real-IP middleware.
|
||||
//
|
||||
// With no trusted prefixes, it always honors the proxy headers — only register
|
||||
// it when the service sits behind a trusted proxy.
|
||||
//
|
||||
// With one or more prefixes, the headers are honored only when the immediate
|
||||
// peer (parsed from r.RemoteAddr) falls within one of them; requests from
|
||||
// outside the allowlist pass through untouched.
|
||||
func New(trusted ...netip.Prefix) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if len(trusted) > 0 && !peerTrusted(r.RemoteAddr, trusted) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if ip := extract(r); ip != "" {
|
||||
r2 := *r
|
||||
r2.RemoteAddr = ip
|
||||
next.ServeHTTP(w, &r2)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func extract(r *http.Request) string {
|
||||
for _, h := range headers {
|
||||
v := r.Header.Get(h)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if i := strings.IndexByte(v, ','); i >= 0 {
|
||||
v = v[:i]
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
if net.ParseIP(v) != nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func peerTrusted(remoteAddr string, trusted []netip.Prefix) bool {
|
||||
var peer netip.Addr
|
||||
if ap, err := netip.ParseAddrPort(remoteAddr); err == nil {
|
||||
peer = ap.Addr()
|
||||
} else if a, err2 := netip.ParseAddr(remoteAddr); err2 == nil {
|
||||
peer = a
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
for _, p := range trusted {
|
||||
if p.Contains(peer) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue