Managing SSE Connections and Retries
To manage Server-Sent Events (SSE) connection behavior such as reconnection intervals, event tracking, and keep-alive pings, use the ServerSentEvent model in conjunction with the EventSourceResponse class.
Configuring Retries and Event IDs
You can control how the browser interacts with your stream by yielding ServerSentEvent instances. This allows you to set specific event IDs for state tracking and define how quickly a client should attempt to reconnect if the connection drops.
from collections.abc import AsyncIterable
from fastapi import FastAPI
from fastapi.sse import EventSourceResponse, ServerSentEvent
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
items = [
Item(name="Plumbus", price=32.99),
Item(name="Portal Gun", price=999.99),
]
@app.get("/items/stream", response_class=EventSourceResponse)
async def stream_items() -> AsyncIterable[ServerSentEvent]:
# Send a comment as a keep-alive or metadata
yield ServerSentEvent(comment="stream of item updates")
for i, item in enumerate(items):
# Set a custom event type, a unique ID, and a 5-second retry interval
yield ServerSentEvent(
data=item,
event="item_update",
id=str(i + 1),
retry=5000
)
Key Components of ServerSentEvent
When using ServerSentEvent from fastapi.sse, you have granular control over the SSE wire format:
id: An optional string that the browser tracks. If the connection is lost, the browser sends this value back in theLast-Event-IDHTTP header during reconnection, allowing your server to resume the stream from the last successful event.retry: An integer representing milliseconds. It tells the browser how long to wait before attempting to reconnect after a connection failure.event: A string that names the event. In the browser, you can listen for this specific event usingeventSource.addEventListener("item_update", ...)instead of the defaultmessageevent.comment: A string that starts with:in the SSE format. These are ignored by the browser'sEventSourcebut are useful for keeping the connection alive through proxies or load balancers that might time out idle connections.
Handling Raw Data vs. JSON Payloads
By default, the data field in ServerSentEvent is automatically JSON-serialized. If you need to send pre-formatted strings, HTML, or CSV data without JSON encoding (which would add extra quotes), use the raw_data field.
@app.get("/logs/stream", response_class=EventSourceResponse)
async def stream_logs() -> AsyncIterable[ServerSentEvent]:
logs = [
"2025-01-01 INFO Application started",
"2025-01-01 DEBUG Connected to database",
]
for log_line in logs:
# raw_data sends the string exactly as-is into the data: field
yield ServerSentEvent(raw_data=log_line)
Automatic Keep-Alive Pings
The EventSourceResponse includes a built-in mechanism to prevent connection timeouts. If your generator is idle (not yielding any data), the server automatically sends a : ping comment every 15 seconds. This interval is defined by the internal _PING_INTERVAL constant in fastapi/sse.py.
Troubleshooting and Validation
The ServerSentEvent model enforces several rules to ensure compliance with the SSE specification:
- Mutually Exclusive Data: You cannot set both
dataandraw_dataon the same event. Usedatafor objects that need JSON serialization andraw_datafor strings that should be sent as-is. - ID Restrictions: The
idfield cannot contain null (\0) characters. This is enforced by the_check_id_no_nullvalidator. - Retry Values: The
retryfield must be a non-negative integer. Negative values will trigger a Pydantic validation error. - JSON Strings: If you pass a plain string to
data, it will be wrapped in quotes (e.g.,data: "hello"). To send a string without quotes, useraw_data.