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.
46 lines
968 B
Go
46 lines
968 B
Go
package infra
|
|
|
|
import "time"
|
|
|
|
// RateLimiterCleanup arranca una goroutine que purga periodicamente las entries
|
|
// del limiter que no han tenido actividad en maxAge.
|
|
// Retorna una funcion que detiene la goroutine cuando se invoca.
|
|
// interval controla la frecuencia del barrido. maxAge es la edad maxima sin actividad.
|
|
func RateLimiterCleanup(rl *RateLimiter, maxAge time.Duration, interval time.Duration) func() {
|
|
if interval <= 0 {
|
|
interval = time.Minute
|
|
}
|
|
if maxAge <= 0 {
|
|
maxAge = 10 * time.Minute
|
|
}
|
|
|
|
stop := make(chan struct{})
|
|
go func() {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
case now := <-ticker.C:
|
|
rl.mu.Lock()
|
|
for key, client := range rl.clients {
|
|
if now.Sub(client.lastSeen) > maxAge {
|
|
delete(rl.clients, key)
|
|
}
|
|
}
|
|
rl.mu.Unlock()
|
|
}
|
|
}
|
|
}()
|
|
|
|
var stopped bool
|
|
return func() {
|
|
if stopped {
|
|
return
|
|
}
|
|
stopped = true
|
|
close(stop)
|
|
}
|
|
}
|