036c0a8d63
Implementa fase 1 del issue 0016: - Tipos RateLimiter, RateLimitConfig y RateLimitResult en types/infra/ - rate_limiter_new: constructor con validacion rate/burst > 0 - rate_limiter_check: evalua token bucket por key, calcula Allowed/Remaining/ResetAt/RetryAfter - rate_limit_headers (pure): construye headers IETF X-RateLimit-* y Retry-After - rate_limiter_cleanup: goroutine GC de entries inactivas con stop idempotente Sin dependencias externas (no Redis). sync.Mutex + map. Algoritmo token bucket estandar con recarga lineal proporcional al tiempo transcurrido.
28 lines
896 B
Go
28 lines
896 B
Go
package infra
|
|
|
|
import (
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// RateLimitHeaders construye los headers IETF estandar de rate limiting a partir de un resultado.
|
|
// Setea X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset y, si no esta permitido, Retry-After.
|
|
// limit es la capacidad total (burst) que se anuncia al cliente.
|
|
// Funcion pura — solo formatea, no hace I/O.
|
|
func RateLimitHeaders(result RateLimitResult, limit int) http.Header {
|
|
h := http.Header{}
|
|
h.Set("X-RateLimit-Limit", strconv.Itoa(limit))
|
|
h.Set("X-RateLimit-Remaining", strconv.Itoa(result.Remaining))
|
|
h.Set("X-RateLimit-Reset", strconv.FormatInt(result.ResetAt.Unix(), 10))
|
|
if !result.Allowed {
|
|
// Retry-After segun RFC 7231: numero entero de segundos.
|
|
retryAfter := int(math.Ceil(result.RetryAfter))
|
|
if retryAfter < 1 {
|
|
retryAfter = 1
|
|
}
|
|
h.Set("Retry-After", strconv.Itoa(retryAfter))
|
|
}
|
|
return h
|
|
}
|