Rate-Limit-Middleware
rateLimit ist ein Request-Limiter pro Key. Er gibt einen
Handler-Wrapper zurück — rufe ihn mit den Optionen auf und
übergib der zurückgegebenen Funktion den Handler, den Du schützen
willst. Überschüssige Requests bekommen 429 mit einem
Retry-After-Header.
import { rateLimit } from 'actor-ts/http';import { InMemoryCache } from 'actor-ts';
const limited = rateLimit({ cache: new InMemoryCache(), windowMs: 60_000, // 1-Minuten-Fenster max: 100, // 100 Requests pro Key pro Minute key: (req) => req.headers['x-forwarded-for'] ?? 'unknown',});
const routes = path('api', post(limited(apiHandler)));Jede eindeutige IP bekommt ein Budget von 100/Minute.
Überschüssige Requests bekommen 429 mit einem Retry-After-
Header.
Konfiguration
Abschnitt betitelt „Konfiguration“interface RateLimitOptions { cache: Cache; windowMs: number; max: number; key: (req: HttpRequest) => string | Promise<string>; keyPrefix?: string; // Default 'rl:' onLimit?: (ctx: RateLimitContext) => HttpResponse;}
interface RateLimitContext { key: string; count: number; max: number; windowMs: number; retryAfterSeconds: number;}| Feld | Zweck |
|---|---|
cache | Backing-Store (In-Memory oder Redis). |
windowMs | Größe des Fixed-Windows. |
max | Maximale Requests pro Key pro Fenster. |
key(req) | Leitet den Rate-Limit-Key her — typischerweise Client-IP, User-ID oder API-Key. Erforderlich. |
keyPrefix | Cache-Key-Namespace. Default 'rl:', damit mehrere Limiter im selben Redis nicht kollidieren. |
onLimit(ctx) | Überschreibt die 429-Response — volle Kontrolle über Status / Body / Header. |
Eigenes Keying
Abschnitt betitelt „Eigenes Keying“rateLimit({ cache, windowMs: 60_000, max: 100, key: (req) => { const userId = extractUserId(req); return userId ? `user:${userId}` : `ip:${req.ip}`; },});Pro User bei Authentifizierung; pro IP, wenn nicht. Häufiges Muster:
- Authentifizierte Routen — Limit pro User.
- Öffentliche Routen — Limit pro IP.
Kombiniere mit der Cache-Abstraktion des Frameworks, damit der Limiter über Pods hinweg mit Redis-Backing funktioniert.
Wie das Zählen funktioniert
Abschnitt betitelt „Wie das Zählen funktioniert“Fixed-Window (Default): - Das Fenster startet, wenn der erste Request für einen Key kommt. - Inkrementiert einen Zähler im Cache. - Wenn der Zähler max übersteigt, bekommen weitere Requests im Fenster 429. - TTL auf dem Zähler sorgt dafür, dass frische Fenster sauber starten.Die Implementierung nutzt das incr mit TTL des Caches —
atomar über Pods hinweg, wenn Redis im Backing steht.
Response-Header
Abschnitt betitelt „Response-Header“Die Middleware setzt:
X-RateLimit-Limit: 100X-RateLimit-Remaining: 47X-RateLimit-Reset: 1716297600 ← Unix-Zeitstempel, wann das Fenster zurückgesetzt wirdRetry-After: 23 ← (nur bei 429) Sekunden bis zum ResetClients nutzen die, um zu backoffen, statt zufällig zu retrien.
Cluster-fähiges Rate-Limiting
Abschnitt betitelt „Cluster-fähiges Rate-Limiting“import { RedisCache } from 'actor-ts';
rateLimit({ cache: new RedisCache({ url: 'redis://...' }), windowMs: 60_000, max: 100,});Mit Redis-gestütztem Cache teilen sich alle Pods den Zähler — ein Client, der irgendeinen Pod trifft, sammelt gegen dasselbe Budget an.
Mit InMemoryCache hat jeder Pod seinen eigenen Zähler — ein
Client, der über 4 Pods verteilt ist, bekommt effektiv das
4-fache Limit. Für großzügige Limits akzeptabel; für strikte
problematisch.
Wann einsetzen
Abschnitt betitelt „Wann einsetzen“Drei gute Einsatzfälle:
- Öffentliche APIs — Missbrauch / DoS verhindern.
- Tier-basierte Limits — unterschiedliche
keypro Tier (free / paid / enterprise). - Per-Aktion-Limits — Login-Versuche, Passwort-Resets, E-Mail-Versandraten.
Geschichtete Rate-Limits
Abschnitt betitelt „Geschichtete Rate-Limits“// Unterschiedliche Limits pro Handler:const apiLimited = rateLimit({ cache, windowMs: 60_000, max: 1000, key: (req) => req.headers['x-user-id'] ?? req.headers['x-forwarded-for'] ?? 'anon',});
const loginLimited = rateLimit({ cache, windowMs: 60_000, max: 10, key: (req) => `login:${req.headers['x-forwarded-for'] ?? 'unknown'}`,});
const routes = concat( path('login', post(loginLimited(loginHandler))), path('orders', get(apiLimited(ordersHandler))),);Strengere Limits auf sensiblen Handlern; großzügigere auf allgemeinem Traffic. Komponiere, indem Du jeden Handler mit dem passenden Limiter einwickelst.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- HTTP-Übersicht — das große Bild.
- Response-Cache-Middleware — komplementäre Lese-Middleware.
- Idempotency-Key-Middleware — für Write-Dedup.
- Cache-Übersicht — der Backing-Store.