feat(infra): rate limiter token-bucket in-memory + tipos y core funcs
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.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user