c3d9fbd8d3
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
40 lines
1.1 KiB
Go
40 lines
1.1 KiB
Go
package infra
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
// RateLimiterByKey retorna un Middleware que aplica rate limiting usando keyFunc para extraer la clave del request.
|
|
// Permite limitar por API key, user ID, o cualquier dimension custom.
|
|
// Cuando keyFunc devuelve "" no se aplica limit (request pasa sin tocar el bucket).
|
|
// Cuando se supera el limite responde 429 con headers X-RateLimit-* y Retry-After.
|
|
func RateLimiterByKey(rl *RateLimiter, keyFunc func(r *http.Request) string) Middleware {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
key := keyFunc(r)
|
|
if key == "" {
|
|
// Sin clave no se aplica limit.
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
result := RateLimiterCheck(rl, key)
|
|
headers := RateLimitHeaders(result, rl.burst)
|
|
for k, v := range headers {
|
|
w.Header()[k] = v
|
|
}
|
|
|
|
if !result.Allowed {
|
|
HTTPErrorResponse(w, HTTPError{
|
|
Status: http.StatusTooManyRequests,
|
|
Code: "rate_limited",
|
|
Message: "too many requests",
|
|
})
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|