Server-Sent Events (SSE)
Server-Sent Events (SSE) est un standard web (API EventSource) qui permet au serveur de pousser des données vers le client en temps réel via une connexion HTTP persistante unidirectionnelle, en utilisant le type MIME text/event-stream.
SSE est le protocole que vous utilisez probablement tous les jours sans le savoir. Quand ChatGPT ou Claude affiche sa réponse mot par mot, c’est SSE. Quand un dashboard se met à jour en temps réel, c’est souvent SSE. Le principe est simple : le serveur ouvre un flux HTTP et envoie des événements texte au fil du temps, sans que le client ait besoin de relancer des requêtes. C’est plus simple que WebSocket, passe à travers les proxies HTTP standard, et suffit pour la majorité des cas où le flux va du serveur vers le client.
- Catégorie
- Protocole de streaming serveur → client
- Standard
- HTML Living Standard (API EventSource), HTTP
- Transport
- HTTP/1.1 ou HTTP/2 (connexion persistante)
- Direction
- Unidirectionnelle (serveur → client uniquement)
- Format
- Texte (
text/event-stream) - Reconnexion
- Automatique (intégrée au navigateur)
- Alternative
- WebSocket (bidirectionnel), long polling
Comment fonctionne SSE
Le client ouvre une requête HTTP GET vers un endpoint SSE. Le serveur répond avec le header Content-Type: text/event-stream et garde la connexion ouverte. Il envoie ensuite des événements au fil du temps, chacun séparé par une ligne vide. Quand le serveur n’a rien à envoyer, la connexion reste ouverte en attente.
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
event: token
data: {"text": "Bonjour"}
event: token
data: {"text": " ! Comment"}
event: token
data: {"text": " puis-je vous"}
event: token
data: {"text": " aider ?"}
event: done
data: [DONE]Chaque événement peut avoir un champ event (type d’événement), data (les données, souvent JSON), id (identifiant pour la reprise après déconnexion) et retry (délai de reconnexion en millisecondes).
Reconnexion automatique
C’est l’avantage majeur de SSE sur WebSocket. Si la connexion se coupe (réseau instable, déploiement serveur), le navigateur reconnecte automatiquement après le délai retry et envoie le dernier id reçu dans le header Last-Event-ID. Le serveur peut alors reprendre le flux là où il s’était arrêté. Zéro code côté client pour gérer la reconnexion.
SSE et streaming LLM
SSE est le protocole standard pour le streaming des réponses LLM. Les API d’OpenAI, Anthropic et Google utilisent SSE pour envoyer les tokens générés au fil de la génération, permettant à l’interface d’afficher la réponse progressivement.
L’endpoint POST /v1/chat/completions avec "stream": true retourne un flux SSE où chaque événement contient un ou plusieurs tokens :
import httpx
# Streaming d'une réponse LLM via SSE
with httpx.stream("POST", "https://api.anthropic.com/v1/messages",
headers={"x-api-key": API_KEY, "anthropic-version": "2023-06-01"},
json={
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"stream": True,
"messages": [{"role": "user", "content": "Bonjour !"}]
}
) as response:
for line in response.iter_lines():
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
# Parser le JSON et afficher le token
chunk = json.loads(data)
print(chunk.get("delta", {}).get("text", ""), end="")SSE est idéal pour ce cas d’usage car le flux est unidirectionnel (serveur → client), le format texte passe à travers tous les proxies HTTP, et la reconnexion automatique est gérée nativement. Pour le cas simple de streaming LLM, SSE est préférable à WebSocket en raison de sa simplicité.
Implémentation
Client (navigateur)
const source = new EventSource("/api/notifications");
source.addEventListener("notification", (event) => {
const data = JSON.parse(event.data);
console.log("Notification:", data.message);
});
source.addEventListener("error", (event) => {
if (source.readyState === EventSource.CLOSED) {
console.log("Connexion fermée par le serveur");
}
// Pas besoin de reconnecter manuellement :
// EventSource le fait automatiquement
});Serveur (Python avec FastAPI)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio, json
app = FastAPI()
async def event_generator():
"""Génère des événements SSE"""
for i in range(10):
data = json.dumps({"count": i, "message": f"Event {i}"})
yield f"event: updatendata: {data}nn"
await asyncio.sleep(1)
yield "event: donendata: [DONE]nn"
@app.get("/api/stream")
async def stream():
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
}
)SSE vs les alternatives
| Critère | SSE | WebSocket | Polling |
|---|---|---|---|
| Direction | Serveur → client | Bidirectionnel | Client → serveur (puis réponse) |
| Complexité | Faible (HTTP standard) | Moyenne (protocole dédié) | Très faible |
| Reconnexion | Automatique | Manuelle | Implicite (chaque requête) |
| Proxies/CDN | Compatible (HTTP standard) | Peut être bloqué | Compatible |
| Données binaires | Non (texte uniquement) | Oui | Oui (via HTTP) |
| Latence | Faible (connexion persistante) | Très faible | Variable (dépend de l’intervalle) |
| Usage LLM | Standard (OpenAI, Anthropic) | Bidirectionnel (function calling) | Non recommandé |
Cas d’usage
Streaming de réponses LLM. Le cas d’usage dominant en 2026. Les API d’OpenAI, Anthropic, Google, Mistral utilisent toutes SSE pour streamer les tokens générés. Chaque token est un événement SSE envoyé dès qu’il est produit par le modèle.
Notifications temps réel. Alertes système, notifications utilisateur, mises à jour de statut. Le serveur pousse les notifications dès qu’elles sont disponibles, sans que le client ait à les demander.
Dashboards live. Les métriques, graphiques et indicateurs se mettent à jour en temps réel via un flux SSE. Plus efficace que le polling (pas de requêtes inutiles) et plus simple que WebSocket (pas de bidirectionnalité nécessaire).
Progression de tâches longues. Upload de fichiers, entraînement de modèles ML, génération de rapports. Le serveur envoie des événements de progression (10%, 25%, 50%, terminé) via SSE.
Flux de données financières. Prix d’actions, taux de change, cours de cryptomonnaies. Le serveur envoie les mises à jour de prix en temps réel via un flux SSE.
Limites de SSE
Unidirectionnel. Le client ne peut pas envoyer de données au serveur via la connexion SSE. Pour envoyer des données, il doit utiliser une requête HTTP séparée (POST, PUT). C’est la limite principale par rapport à WebSocket.
Texte uniquement. SSE ne supporte que les données texte (UTF-8). Les données binaires (images, audio, fichiers) doivent être encodées en base64, ce qui augmente la taille. WebSocket supporte les frames binaires nativement.
Limite de connexions. Les navigateurs limitent le nombre de connexions SSE simultanées par domaine (typiquement 6 en HTTP/1.1). HTTP/2 multiplex les streams, levant cette limite. En pratique, utilisez HTTP/2 pour le SSE en production.
Pas de backpressure natif. Si le serveur produit des événements plus vite que le client ne peut les consommer, le buffer côté navigateur grossit. WebSocketStream (API expérimentale) et gRPC streaming gèrent le backpressure nativement.
Erreurs courantes
Oublier Cache-Control: no-cache. Sans ce header, un proxy ou CDN peut cacher la réponse SSE et la servir d’un coup au lieu de la streamer. Toujours ajouter Cache-Control: no-cache et Connection: keep-alive.
Bufferisation côté proxy. NGINX et d’autres reverse proxies bufferisent les réponses par défaut, ce qui empêche le streaming. Ajoutez X-Accel-Buffering: no (NGINX) ou configurez proxy_buffering off pour désactiver la bufferisation.
Pas de id sur les événements. Sans identifiant, la reconnexion automatique ne peut pas reprendre le flux là où il s’était arrêté. Ajoutez un id incrémental ou basé sur un timestamp à chaque événement critique.
Utiliser SSE quand WebSocket est nécessaire. Si votre application doit envoyer des signaux pendant le stream (annulation, function calling), SSE ne suffit pas. N’hésitez pas à passer à WebSocket pour ces cas.
Questions fréquentes sur les Server-Sent Events
Quelle est la différence entre SSE et WebSocket ?
SSE est unidirectionnel (serveur → client) et utilise HTTP standard. WebSocket est bidirectionnel et utilise un protocole dédié après un upgrade HTTP. SSE est plus simple, gère la reconnexion automatiquement, et passe à travers les proxies HTTP. WebSocket est nécessaire quand le client doit envoyer des données au serveur pendant la connexion. Pour le streaming LLM simple, SSE suffit. Pour le chat ou la collaboration, WebSocket est requis.
Pourquoi les API des LLM utilisent-elles SSE ?
Le streaming LLM est unidirectionnel : le serveur génère des tokens et les envoie au client. SSE est le choix naturel car il s’appuie sur HTTP standard (fonctionne avec n’importe quel client HTTP), gère la reconnexion automatiquement, passe à travers les proxies et CDN, et est supporté nativement par tous les navigateurs. Les API d’OpenAI, Anthropic, Google et Mistral utilisent toutes SSE avec le format text/event-stream.
SSE fonctionne-t-il derrière un reverse proxy ?
Oui, car SSE utilise HTTP standard. Cependant, les proxies qui bufferisent les réponses (comportement par défaut de NGINX) peuvent empêcher le streaming. Désactivez la bufferisation : proxy_buffering off dans NGINX, ou ajoutez le header X-Accel-Buffering: no côté serveur. Apache et Caddy passent SSE sans configuration spéciale dans la plupart des cas.
Combien de connexions SSE un serveur peut-il gérer ?
Avec un serveur async (FastAPI/uvicorn, Node.js, Go), des dizaines de milliers de connexions SSE par instance sont possibles car chaque connexion ne consomme qu’un file descriptor et très peu de mémoire. Le facteur limitant est souvent le navigateur (6 connexions max par domaine en HTTP/1.1, illimité en HTTP/2) ou les limites de file descriptors de l’OS. Utilisez HTTP/2 en production pour éliminer la limite côté navigateur.
Quelle est la différence entre SSE et le polling ?
Le polling envoie des requêtes HTTP répétées à intervalle régulier (par exemple toutes les 5 secondes) pour vérifier s’il y a du nouveau. SSE ouvre une seule connexion HTTP persistante et reçoit les données dès qu’elles sont disponibles. SSE est plus efficace (pas de requêtes inutiles), offre une latence plus faible (instantanée vs intervalle de polling), et consomme moins de bande passante. Le polling est plus simple à implémenter mais inadapté au temps réel.