Événements envoyés par le serveur
SSE est intégré au noyau bext — aucun indicateur de fonctionnalité n'est requis. bext fournit à la fois un générateur Response::sse de bas niveau pour les flux personnalisés et un SseChannelHub de haut niveau pour les chaînes pub/sub nommées.
Générateur de réponses#
Le moyen le plus simple d'envoyer SSE est le générateur Response::sse. Transmettez un flux d'événements asynchrone et un intervalle de reconnexion facultatif :
use bext::Response;
async fn stream_updates(req: Request) -> Response {
let stream = async_stream::stream! {
for i in 0..10 {
yield SseEvent::new()
.id(i.to_string())
.event("tick")
.data(format!(r#"{{"count":{i}}}"#));
tokio::time::sleep(Duration::from_secs(1)).await;
}
};
Response::sse(stream)
.reconnect_ms(3000)
.build()
}
La réponse définit automatiquement Content-Type: text/event-stream, Cache-Control: no-cache et Connection: keep-alive.
Canaux nommés#
SseChannelHub permet à plusieurs éditeurs et abonnés de partager des chaînes nommées sans câbler manuellement les flux :
use bext::sse::SseChannelHub;
// Publish from anywhere (handler, plugin, background task)
sse.publish("chat:room-42", SseEvent::new()
.event("message")
.data(r#"{"user":"alice","text":"hello"}"#));
// Subscribe in a handler
async fn listen(req: Request, sse: &SseChannelHub) -> Response {
let stream = sse.subscribe("chat:room-42");
Response::sse(stream).build()
}
Les chaînes sont créées paresseusement lors de la première publication ou de l'abonnement, et récupérées lorsque le dernier abonné se déconnecte.
Modèles de canaux#
Les noms de canaux prennent en charge les caractères génériques de style global pour la diffusion :
| Modèle | Matchs |
|---|---|
chat:room-42 |
Canal exact uniquement. |
chat:room-* |
Toutes les pièces sous chat. |
notifications:* |
Tous les canaux de notification. |
Contre-pression#
Les clients lents sont une réalité. bext gère la contre-pression sans bloquer le serveur ou les autres abonnés :
- Chaque abonné dispose d'un tampon limité (64 événements par défaut).
- Lorsque le tampon se remplit, les nouveaux événements sont supprimés pour cet abonné.
- Une métrique
sse_events_dropped_totalest émise par canal, par client. - L'abonné reçoit un événement synthétique
droppedavec le décompte de événements manqués afin que le client puisse demander une actualisation complète si nécessaire.
Configurez la taille du tampon par canal :
[sse]
default_buffer_size = 64
[sse.channels.chat]
buffer_size = 256
Reconnexion du dernier ID d'événement#
Lorsqu'un client se reconnecte avec un en-tête Last-Event-ID, bext relit les événements manqués à partir d'un tampon en anneau en mémoire :
- Taille de sonnerie par défaut : 1 000 événements par canal.
- Si l'identifiant demandé est plus ancien que la sonnerie, le client reçoit un
Événement
replay_incompletepour qu'il sache qu'il doit effectuer une récupération complète.
[sse]
replay_ring_size = 1000
Le côté client est standard — le EventSource du navigateur envoie
Last-Event-ID automatiquement lors de la reconnexion.
Exemple : réponses en streaming d'IA#
Les API LLM diffusent universellement les complétions via SSE. Voici un gestionnaire qui transmet un flux LLM en amont au navigateur :
async fn ai_stream(req: Request) -> Response {
let prompt = req.json::<PromptRequest>().await?;
let upstream = llm_client.stream_completion(&prompt).await?;
let stream = upstream.map(|chunk| {
SseEvent::new()
.event("token")
.data(serde_json::to_string(&chunk).unwrap())
});
Response::sse(stream)
.reconnect_ms(5000)
.build()
}
Sur le client :
const source = new EventSource("/api/ai/stream?prompt=...");
source.addEventListener("token", (e) => {
const chunk = JSON.parse(e.data);
appendToOutput(chunk.text);
});
Métrique#
bext expose des métriques spécifiques à SSE :
| Métrique | Description |
|---|---|
sse_active_connections |
Connexions SSE actuellement ouvertes. |
sse_events_sent_total |
Événements envoyés sur tous les canaux. |
sse_events_dropped_total |
Les événements ont été abandonnés en raison de la contre-pression. |
sse_channels_active |
Chaînes avec au moins un sous. |
Compression#
Les réponses SSE sont automatiquement compressées avec gzip ou brotli lorsque le client envoie Accept-Encoding. Étant donné que SSE est une connexion de longue durée, bext vide le compresseur après chaque événement pour éviter les retards de mise en mémoire tampon.
Le tampon de réponses d'acheminement /api/* de bext jusqu'à son achèvement avant la livraison — les flux SSE envoyés à partir d'un gestionnaire /api/ ne seront PAS vidés en direct. Placez votre gestionnaire SSE en dehors du préfixe /api/ (ou sous une route de page de niveau supérieur) pour obtenir un véritable streaming. Voir le Le piège de la mise en mémoire tampon SSE pour plus de détails.
Si un client lent remplit son tampon de 64 événements, il reçoit un message synthétique
Événement dropped plutôt qu’une déconnexion matérielle. Votre client doit écouter cet événement et déclencher une actualisation complète, sinon il manquera silencieusement les mises à jour.
En rapport#
- WebSockets — alternative bidirectionnelle lorsque le client doit transmettre des données au serveur
- WebTransport — Alternative basée sur HTTP/3 avec des datagrammes peu fiables et plusieurs flux
- Comprend : En temps réel — primitives pub/sub de niveau supérieur construites sur SSE et WebSocket
- Comprend : Diffusion en continu — streaming au niveau de la page (différent de SSE)
- Guide de mise en cache — comment
Cache-Control: no-cacheinteragit avec le cache périphérique de bext
Links#
- WHATWG HTML Living Standard — Événements envoyés par le serveur — spécification canonique SSE incluant
EventSourceet le format de flux d'événements - MDN : Source d'événements — référence de l'API du navigateur