Files
2026-08-09 18:03:09 +01:00

43 lines
1.4 KiB
Go

package ratelimit
import (
"net"
"net/http"
"strings"
)
// HTTPMiddleware wraps next with per-client-IP rate limiting, returning 429
// for requests over the limit. realIPHeader (e.g. "X-Forwarded-For"), if
// non-empty, is trusted for the client IP instead of RemoteAddr — only set
// this when the server is genuinely behind a reverse proxy that sets it;
// trusting it otherwise lets any client spoof their rate-limit identity.
func (l *Limiter) HTTPMiddleware(realIPHeader string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := ClientIP(r, realIPHeader)
if !l.Allow(ip) {
w.Header().Set("Retry-After", "60")
http.Error(w, "rate limit exceeded, try again shortly", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
// ClientIP resolves the request's client IP the same way HTTPMiddleware
// does, for callers that need it outside a rate-limit context (e.g. an IP
// allowlist middleware). See HTTPMiddleware's doc comment for the
// realIPHeader trust caveat.
func ClientIP(r *http.Request, realIPHeader string) string {
if realIPHeader != "" {
if v := r.Header.Get(realIPHeader); v != "" {
parts := strings.Split(v, ",")
return strings.TrimSpace(parts[0])
}
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return host
}