Rate Limiting
Runique propose deux approches de rate limiting : déclarative au niveau des routes, ou fine dans le handler.
Approche déclarative — au niveau des routes
Directement dans url.rs, via le trait RouterExt :
use runique::prelude::*;
pub fn routes() -> Router {
urlpatterns! {
"/" => view!{ index }, name = "index",
// ...
}
// Route unique
.rate_limit("/upload-image", "upload_image", view!(upload_image_submit), 5, 60, vec![Method::POST])
}
Plusieurs routes partageant le même compteur :
.rate_limit_many(5, 60, vec![Method::POST], vec![
("/upload-image".into(), "upload_image".into(), view!(upload_image_submit)),
("/inscription".into(), "inscription".into(), view!(soumission_inscription)),
])
Le
spawn_cleanupest appelé automatiquement — pas de fuite mémoire.
Approche handler — logique fine
Pour une logique par utilisateur, par action, ou avec une clé custom :
use runique::prelude::*;
static LIMITER: LazyLock<RateLimiter> = LazyLock::new(|| {
RateLimiter::new()
.max_requests(10)
.retry_after(60)
});
pub async fn login(/* ... */) -> impl IntoResponse {
if !LIMITER.is_allowed(&ip) {
return StatusCode::TOO_MANY_REQUESTS.into_response();
}
// ...
}
Quand utiliser laquelle ?
| Cas | Approche |
|---|---|
| Route publique exposée, limite globale par IP | Déclarative (.rate_limit()) |
| Limite par utilisateur connecté | Handler |
| Logique différente selon le contexte | Handler |
| Plusieurs routes, même quota partagé | .rate_limit_many() |
Configuration
RateLimiter::new().max_requests(5).retry_after(60) // 5 requêtes par minute
RateLimiter::new().max_requests(3).retry_after(300) // 3 requêtes par 5 minutes
RateLimiter::new().max_requests(100).retry_after(60) // 100 requêtes par minute
Comportement
- La clé de limitation est l'adresse IP de la requête — l'extension
ClientIpposée par le middlewaretrusted_proxies(toujours actif) si présente, sinon l'adresse TCP brute du pair (ConnectInfo) - Le support de
X-Forwarded-Forpasse par ce mécanismetrusted_proxies(validation contre une liste de CIDR de confiance), pas par une lecture directe du header ici —X-Real-IPn'est lu nulle part dans le code - Fenêtre fixe : le compteur repart à zéro après
retry_aftersecondes - Réponse
429 Too Many Requestsquand la limite est dépassée, avec headerRetry-After: <secondes>
⚠️ Sécurité : la fiabilité de la clé IP dépend entièrement de la configuration de
trusted_proxies(voir Trusted Proxies) — c'est elle qui détermine siX-Forwarded-Forest validé ou ignoré. Sans reverse proxy de confiance correctement déclaré, un attaquant peut forger ce header pour changer de clé de rate limiting à volonté.
API
RateLimiter::new()
Crée un rate limiter avec les valeurs par défaut (60 req / 60 s).
.max_requests(max: u32)
Nombre de requêtes autorisées dans la fenêtre.
.retry_after(secs: u64)
Durée de la fenêtre en secondes.
is_allowed(key: &str) -> bool
Retourne true si la clé est sous la limite, false sinon.
retry_after_secs(key: &str) -> u64
Secondes restantes avant réinitialisation de la fenêtre pour cette clé. Retourne 0 si la fenêtre est déjà expirée ou si la clé est inconnue. Utilisé pour remplir le header Retry-After dans les réponses 429.
.only_methods(methods: Vec<Method>)
Restreint le rate limiting aux méthodes HTTP spécifiées. Les requêtes avec d'autres méthodes passent librement sans être comptabilisées.
use axum::http::Method;
RateLimiter::new()
.max_requests(5)
.retry_after(60)
.only_methods(vec![Method::POST])
Cas d'usage : protéger une route de login contre le brute force sur les soumissions POST uniquement, tout en laissant passer GET (affichage du formulaire) librement.
Sans
.only_methods(), toutes les méthodes HTTP sont comptabilisées.
.spawn_cleanup(period: Duration)
Lance une tâche de nettoyage en arrière-plan qui purge périodiquement les entrées expirées. Sans ce nettoyage, la map interne croît indéfiniment pour chaque IP distincte. À appeler une fois après construction du limiter.
let limiter = RateLimiter::new().max_requests(5).retry_after(60);
limiter.spawn_cleanup(Duration::from_secs(60));
let limiter = Arc::new(limiter);
Avec l'approche déclarative (
.rate_limit()/.rate_limit_many()),spawn_cleanupest appelé automatiquement.