feat(infra): rate limit middlewares HTTP por IP y por key + tests
Implementa fase 2 del issue 0016: - rate_limit_middleware: limita por IP (X-Forwarded-For > X-Real-IP > RemoteAddr) - rate_limiter_by_key: middleware configurable con keyFunc custom (API key, user ID...) - Cuando se rechaza responde 429 con HTTPError JSON y headers Retry-After + X-RateLimit-* - Tests con httptest.NewRecorder cubriendo: limite, burst, IPs independientes, XFF prioritario, recarga temporal, JSON body, headers IETF, GC stop idempotente, key vacia salta limit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// rateLimitClientIP extrae la IP del cliente del request.
|
||||
// Prioridad: X-Forwarded-For (primer valor) > X-Real-IP > RemoteAddr.
|
||||
// Para X-Forwarded-For multi-hop solo se usa el primer IP (cliente original).
|
||||
func rateLimitClientIP(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.Split(xff, ",")
|
||||
ip := strings.TrimSpace(parts[0])
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
||||
return strings.TrimSpace(xri)
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// RateLimitMiddleware retorna un Middleware que aplica rate limiting por IP del cliente.
|
||||
// Si el request supera el limite responde 429 con headers Retry-After y X-RateLimit-*.
|
||||
// La IP se extrae con prioridad X-Forwarded-For > X-Real-IP > RemoteAddr.
|
||||
func RateLimitMiddleware(rl *RateLimiter) Middleware {
|
||||
return RateLimiterByKey(rl, rateLimitClientIP)
|
||||
}
|
||||
Reference in New Issue
Block a user