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:
2026-04-18 17:14:25 +02:00
parent 036c0a8d63
commit c3d9fbd8d3
10 changed files with 707 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
---
name: rate_limiter_by_key
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func RateLimiterByKey(rl *RateLimiter, keyFunc func(r *http.Request) string) Middleware"
description: "Middleware HTTP configurable que aplica rate limiting con un extractor de clave custom. Permite limitar por API key, user ID, header arbitrario, etc. Si keyFunc devuelve cadena vacia el request pasa sin limit."
tags: [rate_limit, http, middleware, custom, key, server, infra]
uses_functions: [rate_limiter_check_go_infra, rate_limit_headers_go_infra, http_error_response_go_infra]
uses_types: [RateLimiter_go_infra, Middleware_go_infra, HTTPError_go_infra]
returns: [Middleware_go_infra]
returns_optional: false
error_type: "error_go_core"
imports: [net/http]
params:
- name: rl
desc: "puntero al RateLimiter compartido entre todos los requests"
- name: keyFunc
desc: "funcion que extrae la clave del request (API key, user ID, etc.). Cadena vacia salta el limit"
output: "Middleware que aplica rate limit segun keyFunc, responde 429 con HTTPError JSON al exceder"
tested: true
tests: ["aplica limit por la clave devuelta por keyFunc", "key vacia salta el limit", "responde 429 con body JSON al exceder", "headers X-RateLimit-* siempre presentes en respuesta"]
test_file_path: "functions/infra/rate_limiter_by_key_test.go"
file_path: "functions/infra/rate_limiter_by_key.go"
---
## Ejemplo
```go
// Limit por API key
rl := RateLimiterNew(100, 200)
mw := RateLimiterByKey(rl, func(r *http.Request) string {
return r.Header.Get("X-API-Key")
})
// Limit por user ID extraido del JWT (suponiendo middleware previo que lo setea)
mwUser := RateLimiterByKey(rl, func(r *http.Request) string {
return r.Context().Value("user_id").(string)
})
```
## Notas
Funcion impura — el middleware muta el estado del limiter. Reutiliza `RateLimiterCheck`, `RateLimitHeaders` y `HTTPErrorResponse` del registry. Si el keyFunc devuelve "" se interpreta como "sin clave identificable" y se deja pasar el request: util para endpoints publicos donde solo se quiere limitar requests autenticados. La respuesta 429 sigue el formato JSON estandar `{"status":429,"code":"rate_limited","message":"too many requests"}`.