WebSocket Rate Limiting — Complete Guide to Message Control
In this tutorial, you will learn about Websocket Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.
WebSocket rate limiting controls message frequency per connection, preventing abuse and ensuring fair resource allocation across all connected clients in real-time applications.
What You'll Learn
- Rate limiting strategies for WebSocket connections
- Token bucket and Sliding Window algorithms
- Implementing per-connection and per-user limits
Why It Matters
Unlike HTTP, WebSocket connections are persistent and bidirectional. A single client sending 10,000 messages per second can degrade service for all other connected clients.
Real-World Use
Durga Antivirus Pro WebSocket server enforces 60 messages per minute per connection using a sliding window algorithm. When exceeded, the server sends a rate limit warning and drops excess messages.
flowchart LR
M["Incoming Message"] --> C["Check Rate Limit"]
C -->|"Under Limit"| P["Process"]
C -->|"Over Limit"| W["Warning"]
W -->|"Exceeded"| D["Drop or Disconnect"]
style C fill:#dbeafe,stroke:#2563eb
Code Examples
// Sliding window rate limiter per connection
const WebSocket = require('ws');
const rateLimiters = new Map();
function checkRateLimit(ws, maxMessages = 60, windowMs = 60000) {
if (!rateLimiters.has(ws)) {
rateLimiters.set(ws, { timestamps: [], warned: false });
}
const limiter = rateLimiters.get(ws);
const now = Date.now();
// Remove timestamps outside the window
limiter.timestamps = limiter.timestamps.filter(t => now - t < windowMs);
if (limiter.timestamps.length >= maxMessages) {
if (!limiter.warned) {
ws.send(JSON.stringify({ type: 'rate_limit', message: 'Slow down' }));
limiter.warned = true;
}
return false;
}
limiter.timestamps.push(now);
limiter.warned = false;
return true;
}
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
ws.on('message', (data) => {
if (!checkRateLimit(ws)) {
return; // Drop excess messages
}
handleMessage(ws, data);
});
ws.on('close', () => {
rateLimiters.delete(ws); // Clean up on disconnect
});
});
Expected output: Clients exceeding 60 messages/minute receive a rate limit warning; excess messages are dropped.
# Token bucket rate limiter for WebSocket
import asyncio
import time
class TokenBucket:
def __init__(self, rate=10, burst=20):
self.rate = rate # Tokens per second
self.burst = burst # Max accumulated tokens
self.tokens = burst
self.last_refill = time.time()
def refill(self):
now = time.time()
elapsed = now - self.last_refill
self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
self.last_refill = now
def consume(self):
self.refill()
if self.tokens >= 1:
self.tokens -= 1
return True
return False
class RateLimitedWebSocket:
def __init__(self, websocket, rate=10, burst=20):
self.ws = websocket
self.bucket = TokenBucket(rate, burst)
async def handle_message(self, message):
if not self.bucket.consume():
await self.ws.send(json.dumps({
'type': 'rate_limit',
'message': 'Message rate exceeded'
}))
return
await process_message(self.ws, message)
Expected output: Token bucket allows bursts up to 20 messages then limits to 10 messages/second sustained.
// Per-user rate limiting across multiple connections
const rateLimits = new Map(); // userId -> { tokens, lastRefill }
function checkUserRateLimit(userId, maxMessages = 100, windowMs = 60000) {
if (!rateLimits.has(userId)) {
rateLimits.set(userId, { count: 0, startTime: Date.now() });
}
const limit = rateLimits.get(userId);
if (Date.now() - limit.startTime > windowMs) {
limit.count = 0;
limit.startTime = Date.now();
}
limit.count++;
return limit.count <= maxMessages;
}
// Shared limit across all connections for the same user
server.on('connection', (ws) => {
ws.on('message', () => {
if (!checkUserRateLimit(ws.userId)) {
ws.send(JSON.stringify({ type: 'rate_limit', message: 'Global rate limit' }));
return;
}
});
});
Expected output: User rate limit aggregates across all their connections; prevents circumvention by opening multiple WebSockets.
Common Mistakes
1. No Rate Limiting at All
WebSocket connections without rate limiting allow a single client to flood the server, degrading service for all.
2. Per-Connection Limits Only
Users can open multiple connections to bypass per-connection limits. Implement per-user limits too.
3. Disconnecting Immediately on First Violation
A single burst should not disconnect. Send warnings, then progressively stricter measures.
4. Not Cleaning Up Limiters
Rate limiter state for disconnected connections accumulates memory. Clean up on close.
5. Applying Limits to System Messages
Server-generated heartbeats and acknowledgments should not count against client rate limits.
Practice Questions
- Why is rate limiting important for WebSocket connections?
- What is the difference between token bucket and sliding window algorithms?
- Why should you implement both per-connection and per-user rate limiting?
- How do you handle a client that consistently exceeds rate limits?
- Why should you not disconnect on the first rate limit violation?
Answers:
- Unlike HTTP, WebSocket is persistent; a single client can send unlimited messages, degrading service.
- Token bucket allows bursts up to a capacity; sliding window enforces a hard limit over a precise time window.
- Per-connection limits are bypassed by opening multiple connections; per-user limits prevent this.
- Send warnings, progressively reduce rate, and only disconnect after repeated violations.
- Legitimate clients may burst legitimately (e.g., re-sync after reconnect). Warnings give them a chance to adjust.
Challenge: Implement a multi-tier WebSocket rate limiter with: per-connection sliding window (60 msg/min), per-user token bucket (200 msg/min across all connections), progressive warnings (warn, reduce rate, disconnect after 3 violations in 5 minutes).
FAQ
Mini Project
Build a WebSocket rate limiter with: per-connection sliding window (60 msg/min with 10 msg/min burst allowed), per-user token bucket (200 msg/min across all connections), progressive warnings (warn at 80%, drop at 100%, disconnect at 150% of limit). Include cleanup on disconnect.
What's Next
Learn about WebSocket authentication for identifying users, or explore WebSocket compression for bandwidth optimization.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro