Production-ready SSE streaming with event replay, topic subscriptions, and client metadata
- Overview
- Features
- Architecture
- Functional Diagram
- Quick Start
- Installation
- Usage
- API Reference
- Project Structure
- Configuration
- Performance
- Testing
- Production Deployment
- Advanced Topics
- Troubleshooting
- Contributing
- License
The Real-Time Streaming API is a high-performance, production-ready solution for real-time data streaming using Server-Sent Events (SSE). Built with FastAPI and Python's asyncio, it provides:
- Non-blocking event broadcasting to thousands of concurrent clients
- Intelligent backpressure handling to prevent memory exhaustion
- Automatic client lifecycle management with graceful connection handling
- Heartbeat mechanism for connection health monitoring
| Feature | SSE | WebSockets |
|---|---|---|
| Protocol | HTTP-based | Custom TCP |
| Direction | Unidirectional (server β client) | Bidirectional |
| Auto-reconnect | Built-in browser support | Manual implementation |
| Proxy/Firewall | Works through most | May require configuration |
| Overhead | Lower for streaming | Higher for simple streams |
| Browser Support | Native EventSource API | WebSocket API |
Choose SSE when: You need to push data from server to clients (dashboards, notifications, live feeds, metrics).
- β Async/Await Architecture - Fully non-blocking with Python asyncio
- β Per-Client Event Buffers - Independent asyncio.Queue for each client
- β O(1) Broadcast - Constant-time event fan-out per client
- β Backpressure Protection - Automatic slow client detection and disconnection
- β Heartbeat Mechanism - Keep-alive events with connection health monitoring
- β Graceful Lifecycle - Clean startup/shutdown with resource cleanup
- β Memory Safety - Bounded queues prevent memory exhaustion
- β Client Disconnect Detection - Automatic cleanup of dead connections
- β Structured Logging - Comprehensive logging with timestamp and levels
- β Context Managers - Guaranteed resource cleanup
- β Error Handling - Robust exception handling throughout
- β Event ID - Unique identifiers for event tracking
- β Event Type - Named events (data, heartbeat, system)
- β Retry Directive - Auto-reconnect timing (5000ms default)
- β JSON Data - Structured event payloads
| Feature | Description | Usage |
|---|---|---|
| π CORS | Cross-origin requests enabled | Works with any frontend domain |
| π‘ Topic Subscriptions | Filter events by type | ?topics=metric,alert |
| π·οΈ Client Metadata | Name and tag clients | ?client_name=dashboard&tags=prod |
| π Event Replay | Resume after disconnect | Last-Event-ID header (automatic) |
# Only receive metrics and alerts
curl "http://localhost:8000/stream?topics=metric,alert"# Identify your client with name and tags
curl "http://localhost:8000/stream?client_name=dashboard-1&tags=production,finance"# Browser sends this automatically on reconnect
Last-Event-ID: abc-123
# Server replays all events after that ID
# No missed events during brief disconnections!
flowchart TB
subgraph FastAPI["π FastAPI Application"]
direction TB
subgraph Lifecycle["Lifespan Manager"]
START["βΆοΈ Start"]
STOP["βΉοΈ Shutdown"]
end
subgraph SM["Stream Manager"]
CLIENTS["π Client Dictionary"]
REGISTER["β Register/Unregister"]
BROADCAST["π‘ Broadcast Events"]
BACKPRESSURE["β οΈ Backpressure Check"]
HISTORY["π Event History (1000)"]
end
subgraph Producers["Event Producers"]
EP["π Event Producer<br/>metrics, logs, alerts"]
HP["π Heartbeat Producer<br/>30s interval"]
end
subgraph Endpoints["API Endpoints"]
DEMO["π GET /demo"]
STREAM["π‘ GET /stream"]
HEALTH["β€οΈ GET /health"]
CLIENTSEP["π₯ GET /clients"]
end
end
subgraph Clients["Connected Clients"]
C1["π€ Client 1<br/>Queue[100]"]
C2["π€ Client 2<br/>Queue[100]"]
CN["π€ Client N<br/>Queue[100]"]
end
START --> SM
START --> Producers
EP -->|"broadcast()"| BROADCAST
HP -->|"broadcast()"| BROADCAST
BROADCAST --> HISTORY
BROADCAST --> CLIENTS
CLIENTS --> C1
CLIENTS --> C2
CLIENTS --> CN
STREAM <-->|"SSE"| C1
STREAM <-->|"SSE"| C2
STREAM <-->|"SSE"| CN
BACKPRESSURE -.->|"disconnect slow"| CLIENTS
flowchart TB
subgraph Producers["Event Producers"]
EP["Event Producer - Generates metrics, logs, alerts"]
HP["Heartbeat Producer - 30 second intervals"]
end
subgraph Manager["Stream Manager"]
BC["Broadcast Engine"]
REG["Client Registry - Dictionary of Queues"]
BP["Backpressure Handler"]
end
subgraph Clients["Connected Clients"]
C1[("Client 1 - Queue 100")]
C2[("Client 2 - Queue 100")]
CN[("Client N - Queue 100")]
end
subgraph API["FastAPI Endpoints"]
ROOT["GET / - Health Check"]
HEALTH["GET /health - Detailed Status"]
STREAM["GET /stream - SSE Endpoint"]
end
EP -->|StreamEvent| BC
HP -->|StreamEvent| BC
BC -->|put_nowait| REG
REG -->|O-1 per client| C1
REG -->|O-1 per client| C2
REG -->|O-1 per client| CN
BP -.->|Disconnect slow clients| REG
STREAM <-->|EventSource| C1
STREAM <-->|EventSource| C2
STREAM <-->|EventSource| CN
flowchart LR
subgraph Pipeline["Event Pipeline"]
P["π Producer<br/>Generate Events"]
SM["π‘ Stream Manager<br/>Broadcast O(N)"]
Q["π¦ Client Queue<br/>Buffer (max: 100)"]
SSE["π€ SSE Response<br/>Format Protocol"]
end
P --> SM --> Q --> SSE
SM --> CHECK{{"Queue Full?"}}
CHECK -->|NO| CONTINUE["β
Continue"]
CHECK -->|YES| DISCONNECT["β Disconnect<br/>Slow Client"]
flowchart TB
subgraph Normal["β
Normal Flow (Queue 30% Full)"]
P1["Producer"] --> SM1["Stream Manager"] --> Q1["Queue ββββββββββ"]
Q1 --> OK["β OK"]
end
subgraph Slow["β Slow Client (Queue 100% Full)"]
P2["Producer"] --> SM2["Stream Manager"] --> Q2["Queue ββββββββββ"]
Q2 --> FULL["β FULL"]
FULL --> EX["QueueFull Exception"]
EX --> DC["Disconnect Client"]
DC --> RM["Remove from Registry"]
RM --> LOG["Log Warning"]
end
# Clone the repository
git clone https://github.com/your-username/Real-Time-Streaming-API.git
cd Real-Time-Streaming-API
# Install dependencies
pip install -r requirements.txt# Development mode with auto-reload
uvicorn main:app --reload
# The server starts at http://localhost:8000Open http://localhost:8000/demo in your browser to see the interactive UI!
Or use curl:
curl -N -H "Accept: text/event-stream" http://localhost:8000/streamid: 550e8400-e29b-41d4-a716-446655440000
event: data
data: {"type": "metric", "name": "cpu_usage", "value": 45.2}
retry: 5000
id: 550e8400-e29b-41d4-a716-446655440001
event: heartbeat
data: {"timestamp": "2024-01-01T12:00:00", "clients": 3}
retry: 5000
- Python 3.11+ (recommended)
- pip (Python package manager)
| Package | Version | Purpose |
|---|---|---|
| FastAPI | 0.109.0 | Web framework |
| Uvicorn | 0.27.0 | ASGI server |
| Pydantic | 2.5.3 | Data validation |
| Requests | 2.31.0 | HTTP client (examples) |
| Pytest | 7.4.3 | Testing framework |
| Pytest-asyncio | 0.21.1 | Async test support |
# Create virtual environment (recommended)
python -m venv venv
source venv/bin/activate # Linux/Mac
.\venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt# Development (auto-reload)
uvicorn main:app --reload
# Production (multiple workers)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4Open examples/browser_client.html in your browser. Features:
- Connect/Disconnect buttons
- Event counter and statistics
- Auto-reconnect handling
- Visual event stream display
python examples/python_client.pyOr use programmatically:
import requests
import json
response = requests.get(
"http://localhost:8000/stream",
headers={"Accept": "text/event-stream"},
stream=True
)
for line in response.iter_lines():
if line:
print(line.decode('utf-8'))curl -N -H "Accept: text/event-stream" http://localhost:8000/streamconst EventSource = require('eventsource');
const es = new EventSource('http://localhost:8000/stream');
es.addEventListener('data', (e) => {
console.log('Data:', JSON.parse(e.data));
});
es.addEventListener('heartbeat', (e) => {
console.log('Heartbeat:', JSON.parse(e.data));
});
es.onerror = (e) => {
console.error('Error:', e);
};| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Server status with version and features |
GET |
/health |
Detailed health with history size |
GET |
/clients |
List all connected clients with metadata |
GET |
/stream |
SSE event stream with filtering |
Returns server status with version and enabled features.
Response:
{
"status": "running",
"version": "1.2.0",
"clients": 5,
"history_size": 342,
"endpoints": {
"stream": "/stream",
"health": "/health",
"clients": "/clients"
},
"features": ["cors", "topic_subscriptions", "client_metadata", "event_replay"]
}Returns detailed health information including event history size.
Response:
{
"status": "healthy",
"connected_clients": 5,
"event_history_size": 342,
"producers": {
"event_producer": "running",
"heartbeat_producer": "running"
}
}List all connected clients with their metadata.
Response:
{
"count": 2,
"clients": [
{
"client_id": "abc-123",
"client_name": "main-dashboard",
"tags": ["production", "finance"],
"topics": ["metric", "alert"],
"connected_at": "2024-01-18T10:30:00",
"events_received": 156,
"queue_size": 3
}
]
}Opens an SSE connection and streams events with optional filtering.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
topics |
string | Comma-separated topics: metric,log,alert,heartbeat |
client_name |
string | Human-readable client identifier |
tags |
string | Comma-separated tags for grouping |
Request Headers:
| Header | Description |
|---|---|
Accept |
text/event-stream (required) |
Last-Event-ID |
Resume from this event ID (automatic on reconnect) |
Example Requests:
# Basic stream (all events)
curl -N -H "Accept: text/event-stream" http://localhost:8000/stream
# Filtered stream with metadata
curl -N "http://localhost:8000/stream?topics=metric,alert&client_name=my-dashboard&tags=prod"Response Headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
Real-Time-Streaming-API/
βββ π app/ # Core application package
β βββ __init__.py # Package initialization
β βββ api.py # FastAPI app & endpoints
β βββ models.py # Pydantic models & SSE format
β βββ stream_manager.py # Client management & broadcast
β βββ producer.py # Event & heartbeat producers
β
βββ π examples/ # Client examples
β βββ browser_client.html # Web-based SSE client
β βββ python_client.py # Python SSE consumer
β βββ curl_client.sh # Shell script client
β
βββ π main.py # Application entry point
βββ π requirements.txt # Python dependencies
βββ π test_streaming.py # Test suite
β
βββ π README.md # This file
βββ π ARCHITECTURE.md # System architecture details
βββ π DESIGN.md # Design decisions & rationale
βββ π DIAGRAMS.md # ASCII diagrams
βββ π QUICKSTART.md # Quick start guide
βββ π SUMMARY.md # Project summary
β
βββ π .gitignore # Git ignore rules
| File | Purpose |
|---|---|
app/api.py |
FastAPI application, lifespan management, SSE endpoint |
app/models.py |
StreamEvent model, EventType enum, SSE formatting |
app/stream_manager.py |
Client registration, broadcast, backpressure |
app/producer.py |
EventProducer, HeartbeatProducer classes |
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Server bind address |
PORT |
8000 |
Server port |
LOG_LEVEL |
info |
Logging level |
Configure in app/api.py:
# Queue size per client (default: 100 events)
stream_manager = StreamManager(max_queue_size=100)
# Heartbeat interval (default: 30 seconds)
heartbeat_producer = HeartbeatProducer(stream_manager, interval=30)# Development
uvicorn main:app --reload --log-level debug
# Production
uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--log-level warning \
--access-log| Metric | Value |
|---|---|
| Throughput | 10,000+ events/second |
| Concurrent Clients | 1,000+ connections |
| Event Latency | <10ms (p99) |
| Memory per Client | ~110KB |
Per Client:
βββ Queue Buffer: 8KB + (100 events Γ 1KB) = ~108KB
βββ Metadata: ~2KB
βββββββ
~110KB per client
Total (1000 clients): ~110MB
Vertical Scaling (Single Server):
Max Clients = Available Memory / Memory per Client
= 8GB / 110KB
β 80,000 clients
Horizontal Scaling (Multi-Server):
- Use Redis Pub/Sub or Redis Streams
- Load balancer with sticky sessions
- See Advanced Topics for implementation
# Run all tests
pytest test_streaming.py -v
# Run specific test
pytest test_streaming.py::test_broadcast_to_multiple_clients -v
# Run with coverage
pytest test_streaming.py --cov=app --cov-report=htmlCore Tests:
| Test | Description |
|---|---|
test_stream_manager_registration |
Client register/unregister |
test_broadcast_to_multiple_clients |
Event fan-out to N clients |
test_backpressure_handling |
Slow client disconnection |
test_sse_format |
SSE protocol formatting |
test_event_producer_lifecycle |
Producer start/stop |
test_heartbeat_producer |
Heartbeat generation |
test_concurrent_clients |
100+ concurrent connections |
test_context_manager |
Resource cleanup |
v1.2 Tests (Client Metadata):
| Test | Description |
|---|---|
test_client_metadata |
Registration with name/tags |
test_get_all_clients_info |
List all clients |
test_client_info_events_received_counter |
Event counting |
test_context_manager_with_metadata |
Context manager with params |
test_anonymous_client_default_name |
Default name handling |
v1.2 Tests (Event Replay):
| Test | Description |
|---|---|
test_event_history_basic |
Buffer stores events |
test_event_history_get_events_after |
Retrieve events after ID |
test_event_history_ring_buffer_overflow |
Old events discarded |
test_replay_events_to_queue |
Replay to client queue |
# Terminal 1: Start server
uvicorn main:app --reload
# Terminal 2: Connect multiple clients
for i in {1..10}; do
curl -N -H "Accept: text/event-stream" http://localhost:8000/stream &
done
# Terminal 3: Check health
curl http://localhost:8000/health- Configure Uvicorn workers based on CPU cores
- Set appropriate queue sizes for expected load
- Enable structured access logs
- Configure CORS if serving web clients
- Add authentication (JWT recommended)
- Set up monitoring (Prometheus/Grafana)
- Configure reverse proxy (nginx)
- Enable TLS/SSL encryption
upstream sse_backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /stream {
proxy_pass http://sse_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# SSE-specific settings
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# Disable nginx buffering for SSE
add_header X-Accel-Buffering no;
}
location / {
proxy_pass http://sse_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]# docker-compose.yml
version: '3.8'
services:
streaming-api:
build: .
ports:
- "8000:8000"
environment:
- LOG_LEVEL=info
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3Add JWT authentication to the stream endpoint:
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def verify_token(token: str = Depends(security)):
if not validate_jwt(token.credentials):
raise HTTPException(status_code=401, detail="Invalid token")
return token
@app.get("/stream")
async def stream_events(request: Request, token: str = Depends(verify_token)):
# ... stream logicfrom slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.get("/stream")
@limiter.limit("10/minute")
async def stream_events(request: Request):
# ... stream logicFor multi-server deployment:
import redis.asyncio as redis
class RedisStreamManager:
def __init__(self):
self.redis = redis.from_url("redis://localhost:6379")
async def broadcast(self, event: StreamEvent):
await self.redis.xadd(
'events',
{'data': event.json()},
maxlen=10000
)
async def consume(self, client_id: str, last_id: str = '$'):
while True:
messages = await self.redis.xread(
{'events': last_id},
count=10,
block=5000
)
for stream, entries in messages:
for message_id, data in entries:
yield parse_event(data), message_id
last_id = message_idfrom prometheus_client import Counter, Gauge, Histogram
# Metrics
connected_clients = Gauge('sse_connected_clients', 'Number of connected SSE clients')
events_sent = Counter('sse_events_sent_total', 'Total SSE events sent')
event_latency = Histogram('sse_event_latency_seconds', 'Event delivery latency')
# In StreamManager
async def broadcast(self, event):
with event_latency.time():
for client_id, queue in self._clients.items():
queue.put_nowait(event)
events_sent.inc()# Find process using port 8000
netstat -ano | findstr :8000 # Windows
lsof -i :8000 # Linux/Mac
# Use different port
uvicorn main:app --port 8001# Ensure you're in project root
cd "c:\Realtime-app-stuffs\Real-Time-Streaming-API"
# Reinstall dependencies
pip install -r requirements.txtAdd CORS middleware to app/api.py:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)- Check proxy/firewall timeout settings
- Increase
proxy_read_timeoutin nginx - Verify heartbeat interval is less than proxy timeout
- Reduce
max_queue_sizeper client - Check for client connection leaks
- Monitor disconnect rate
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone your fork
git clone https://github.com/your-username/Real-Time-Streaming-API.git
cd Real-Time-Streaming-API
# Create virtual environment
python -m venv venv
source venv/bin/activate
# Install dev dependencies
pip install -r requirements.txt
# Run tests
pytest test_streaming.py -vThis project is licensed under the MIT License - see the LICENSE file for details.
- FastAPI - Modern Python web framework
- Uvicorn - Lightning-fast ASGI server
- Pydantic - Data validation using Python type hints
Built with β€οΈ for real-time applications