Server-Sent Events en Rust : envoyer des données en temps réel sans WebSocket

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Quand WebSocket est trop, ou pourquoi SSE est de nouveau à la mode

\n\n

Imaginez : vous devez envoyer des données du serveur au client en temps réel. Taux de change, notifications de nouveaux messages, logs serveur, progression du traitement d'un fichier. La première chose qui vient à l'esprit est WebSocket. Mais que faire si vous n'avez pas besoin d'un canal bidirectionnel ? Que faire si le client écoute seulement et le serveur parle seulement ?

\n\n

C'est là qu'interviennent les Server-Sent Events (SSE) — un protocole qui permet au serveur d'envoyer des données au client via une connexion HTTP ordinaire. Sans handshakes superflus, sans protocoles complexes, sans maux de tête avec les proxies et les équilibreurs de charge. Et oui, avec Rust, cela fonctionne tout simplement divinement.

\n\n

Dans cet article, nous allons voir comment mettre en place SSE en Rust à partir de zéro, en utilisant les frameworks populaires. Vous verrez des exemples fonctionnels, découvrirez les pièges et comprendrez quand SSE est votre meilleur choix.

\n\n

Que sont les Server-Sent Events et pourquoi Rust ?

\n\n

SSE est un standard W3C (EventSource API) qui permet au serveur d'envoyer des données en streaming au client via HTTP. Le client se connecte à un endpoint et le serveur commence à envoyer des messages au format text/event-stream. Tout fonctionne sur HTTP/1.1 ou HTTP/2 standard, ne nécessite pas de proxies spéciaux et est supporté par tous les navigateurs modernes.

\n\n

Pourquoi Rust ? Parce que Rust nous offre :

\n
    \n
  • De l'asynchrone sans surprises — Tokio et async-std permettent de gérer des milliers de connexions SSE sur un seul thread
  • \n
  • La sécurité mémoire — pas de fuites, pas de race conditions dans le streaming multithread
  • \n
  • Une consommation minimale de ressources — une seule connexion SSE consomme quelques kilo-octets
  • \n
  • Un écosystème — Actix Web, Rocket, Axum — tous supportent SSE nativement ou avec des extensions minimales
  • \n
\n\n

Comment fonctionne SSE : décortiquons le protocole

\n\n

Le client crée un EventSource dans le navigateur, en spécifiant une URL. Le serveur répond avec l'en-tête Content-Type: text/event-stream et commence à envoyer des lignes au format :

\n\n
data: Bonjour le monde !\\n\\n
\n\n

Chaque message est un bloc où data: contient la charge utile. Les messages sont séparés par deux caractères de nouvelle ligne. On peut transmettre du JSON, des nombres, des chaînes — n'importe quoi. Il existe également les champs id, event et retry.

\n\n

Voici un exemple minimal de message SSE :

\n\n
id: 1\nevent: update\ndata: {"status": "processing", "progress": 42}\n\n
\n\n

Le client en JavaScript reçoit cela comme un événement update avec le champ data contenant du JSON.

\n\n

Écrivons un serveur SSE en Rust avec Actix Web

\n\n

Commençons par le framework le plus populaire — Actix Web. Installons les dépendances :

\n\n
[dependencies]\nactix-web = "4"\nactix-rt = "2"\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n\n

Écrivons maintenant un endpoint simple qui enverra l'heure actuelle toutes les 2 secondes :

\n\n
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, middleware};\nuse actix_web::rt::time;\nuse futures::stream::StreamExt;\nuse std::time::Duration;\n\n#[get("/events")]\nasync fn stream_events(req: HttpRequest) -> HttpResponse {\n    let mut rx = tokio::sync::broadcast::channel::<String>(100).1;\n\n    let stream = async_stream::stream! {\n        loop {\n            let message = format!("data: {}\\n\\n", chrono::Local::now());\n            yield Ok::<_, actix_web::Error>(\n                web::Bytes::from(message)\n            );\n            time::sleep(Duration::from_secs(2)).await;\n        }\n    };\n\n    HttpResponse::Ok()\n        .insert_header(("Content-Type", "text/event-stream"))\n        .insert_header(("Cache-Control", "no-cache"))\n        .insert_header(("Connection"                

Blogs

Book Recommendations