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 }