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: (request) => request.remoteAddress ?? 'unknown', // Socket-Peer — kein spoofbarer Header});
const routes = path('api', post(limited(apiHandler)));Jede eindeutige IP (hier: der Socket-Peer request.remoteAddress) bekommt ein
Budget von 100/Minute. Überschüssige Requests bekommen 429 mit einem
Retry-After-Header. Nutze als Key einen Wert, den der Client nicht
fälschen kann — nicht x-forwarded-for, außer ein vertrauenswürdiger
Proxy setzt ihn (siehe Sicherheitshinweis unten), sonst rotiert ein
Angreifer den Header pro Request und umgeht das Limit.
Konfiguration
Abschnitt betitelt „Konfiguration“type RateLimitOptions = { cache: Cache; windowMs: number; max: number; key: (request: HttpRequest) => string | Promise<string>; keyPrefix?: string; // Default 'rl:' onLimit?: (context: RateLimitContext) => HttpResponse;};
type 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(request) | 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(context) | Überschreibt die 429-Response — volle Kontrolle über Status / Body / Header. |
Eigenes Keying
Abschnitt betitelt „Eigenes Keying“rateLimit({ cache, windowMs: 60_000, max: 100, key: (request) => { const userId = extractUserId(request); return userId ? `user:${userId}` : `ip:${request.remoteAddress ?? 'unknown'}`; },});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: 47Retry-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, RedisCacheOptions } from 'actor-ts';
const redisCacheOptions = RedisCacheOptions.create().withUrl('redis://...');rateLimit({ cache: new RedisCache(redisCacheOptions), 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: (request) => request.headers['x-user-id'] ?? request.remoteAddress ?? 'anon',});
const loginLimited = rateLimit({ cache, windowMs: 60_000, max: 10, key: (request) => `login:${request.remoteAddress ?? '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.
