Jour 9 Day 9 · vendredi 7 août 2026 Friday 7 August 2026 Web Intermédiaire

WebSockets & le temps réel WebSockets & real-time

Polling, SSE, WebSocket : choisir la bonne technique temps réel et savoir la scaler sur plusieurs instances — le sujet qui départage les candidats sur les questions de chat et de notifications. Polling, SSE, WebSocket: picking the right real-time technique and knowing how to scale it across instances — the topic that separates candidates on chat and notification questions.

L’essentiel

HTTP est un protocole requête/réponse : le client demande, le serveur répond, la transaction est finie. Le serveur ne peut pas prendre l’initiative d’envoyer un message — problème dès qu’on veut du temps réel : chat, notifications, cours de bourse, curseurs collaboratifs.

Le WebSocket répond à cette limite : une connexion TCP persistante et full-duplex (les deux côtés émettent quand ils veulent), établie par un handshake HTTP puis affranchie du modèle requête/réponse. Une seule connexion, un overhead de quelques octets par message, une latence minimale.

Mais le réflexe « temps réel = WebSocket » est une erreur d’entretien classique : pour un flux unidirectionnel serveur → client (notifications, progression d’un job), les Server-Sent Events font le travail sur du HTTP simple, avec reconnexion automatique native. Savoir comparer honnêtement les options vaut plus que de réciter la RFC.

Comment ça marche

Le handshake : le client envoie une requête HTTP GET avec Connection: Upgrade, Upgrade: websocket et un header Sec-WebSocket-Key (nonce aléatoire). Le serveur répond 101 Switching Protocols avec Sec-WebSocket-Accept (hash SHA-1 du nonce concaténé à un GUID fixe — preuve qu’il parle bien WebSocket, pas de la crypto). À partir de là, la connexion TCP reste ouverte et les deux côtés échangent des frames.

Client                             Serveur
  │ GET /chat HTTP/1.1               │
  │ Connection: Upgrade              │
  │ Upgrade: websocket               │
  │ Sec-WebSocket-Key: <nonce>       │
  │─────────────────────────────────▶│
  │                                  │
  │ HTTP/1.1 101 Switching Protocols │
  │ Sec-WebSocket-Accept: <hash>     │
  │◀─────────────────────────────────│
  │                                  │
  │◀════ frames full-duplex ════════▶│
  │      (text, binary, ping/pong)   │

Les frames : chaque message est découpé en frames avec un opcode (text, binary, ping, pong, close). Les frames client → serveur sont maskées (XOR avec une clé aléatoire) pour éviter des attaques d’empoisonnement de cache sur des proxies naïfs. Les frames de contrôle ping/pong servent de heartbeat.

Heartbeat & reconnexion : une connexion TCP peut être morte sans que personne ne le sache — un proxy, un NAT ou un firewall coupe silencieusement les connexions inactives (souvent après 30-60 s). D’où le heartbeat : le serveur envoie un ping périodique, pas de pong en retour = connexion morte, on nettoie. Côté client, la reconnexion se fait avec un backoff exponentiel + jitter (sinon, au redémarrage du serveur, tous les clients reviennent en même temps et l’écroulent — thundering herd), suivie d’une resynchronisation d’état : les messages ratés pendant la coupure ne reviennent pas tout seuls.

Tout le nécessaire côté client tient en quelques lignes de WebSocket natif :

function connect(attempt = 0) {
  const ws = new WebSocket("wss://api.example.com/chat");

  ws.onopen = () => {
    attempt = 0;                                      // reset du backoff
    ws.send(JSON.stringify({ type: "auth", token })); // auth en 1er message
  };

  ws.onmessage = (e) => render(JSON.parse(e.data));

  ws.onclose = () => {
    // backoff exponentiel + jitter : évite le thundering herd
    const delay = Math.min(30_000, 1000 * 2 ** attempt)
                + Math.random() * 1000;
    setTimeout(() => connect(attempt + 1), delay);
  };
}
connect();

Scaling horizontal : c’est LA question piège. Un WebSocket est stateful : la connexion vit sur une instance précise. Avec 3 instances derrière un load balancer, Alice est connectée à l’instance A et Bob à l’instance B — si Alice envoie un message dans un salon, l’instance A ne peut pas le pousser à Bob directement. Deux briques : des sticky sessions au niveau du load balancer (pour que le handshake et la connexion restent sur la même instance), et un pub/sub (Redis typiquement) pour broadcaster entre instances : A publie le message sur un channel, toutes les instances abonnées le reçoivent et le relaient à leurs clients connectés.

🎤 En entretien — « et sur plusieurs instances ? » arrive systématiquement après votre belle explication du handshake. Avoir les deux briques prêtes — sticky sessions + Redis pub/sub — et savoir dire pourquoi chacune est nécessaire fait toute la différence.

Concepts clés à maîtriser

Quatre techniques pour du temps réel, à comparer honnêtement :

Short pollingLong pollingSSEWebSocket
Directionclient → serveurserveur → client (simulé)serveur → clientbidirectionnelle
TransportHTTP répétéHTTP maintenu ouvertHTTP streaming (text/event-stream)TCP après upgrade
Latenceintervalle/2 en moyennequasi nullequasi nulleminimale
Reconnexionsans objetà chaque messageautomatique (EventSource, Last-Event-ID)à la main (backoff)
Donnéestout HTTPtout HTTPtexte seulementtexte + binaire
Idéal pourdonnées peu fréquentescompatibilité legacynotifications, dashboards, flux LLMchat, jeux, édition collaborative

💡 SSE sous-estimé — reconnexion et reprise gratuites, du HTTP standard qui passe partout, et la vieille limite des 6 connexions par domaine en HTTP/1.1 est levée par le multiplexage HTTP/2. Avant de dégainer un WebSocket, une seule question : le client a-t-il vraiment besoin d’émettre ?

  • socket.io vs WebSocket natif : socket.io est une bibliothèque au-dessus du transport (WebSocket quand c’est possible, long polling en fallback) qui ajoute reconnexion automatique, rooms, namespaces, acknowledgements et un adapter Redis prêt à l’emploi pour le multi-instance. Coût : un protocole propriétaire (un client WebSocket brut ne peut pas s’y connecter) et une dépendance des deux côtés. En 2026, le fallback compte moins qu’avant ; ce sont les rooms et l’adapter qui justifient socket.io.
  • Sécurité : wss:// (TLS) obligatoire — comme https. L’API WebSocket du navigateur ne permet pas de headers custom : l’auth du handshake passe par le cookie de session, un token en query string (attention aux logs serveur) ou un premier message d’authentification. Et surtout : pas de CORS sur les WebSockets — le serveur doit vérifier lui-même le header Origin, sinon n’importe quel site peut ouvrir une connexion authentifiée par cookie (cross-site WebSocket hijacking).

En entretien

« Pourquoi ne pas simplement faire du polling ? » — Le short polling gaspille des requêtes vides et impose une latence moyenne de la moitié de l’intervalle. Ça reste défendable pour des données peu fréquentes (toutes les 30 s) — c’est simple, stateless, cacheable. Dès que la fréquence monte ou que la latence compte, SSE ou WebSocket.

« Décris le handshake WebSocket » — GET HTTP avec Upgrade: websocket, Connection: Upgrade et Sec-WebSocket-Key ; réponse 101 Switching Protocols avec Sec-WebSocket-Accept dérivé de la clé. La connexion TCP est ensuite réutilisée pour des frames full-duplex. Bonus : commencer en HTTP permet de passer les proxies et de partager le port 443.

« Comment scaler un chat sur plusieurs instances ? » — Sticky sessions au load balancer pour que chaque connexion vive sur une instance stable, et Redis pub/sub entre les instances : celle qui reçoit un message le publie sur un channel, les autres le relaient à leurs clients du salon concerné. Mentionner l’adapter Redis de socket.io qui implémente exactement ça.

« SSE ou WebSocket pour des notifications ? » — SSE : le flux est unidirectionnel, l’API EventSource gère la reconnexion et la reprise nativement, ça passe par du HTTP standard (pas de config proxy/LB spéciale). Le WebSocket ajouterait de la complexité (heartbeat, reconnexion manuelle) sans bénéfice puisque le client n’émet pas.

« Comment authentifies-tu une connexion WebSocket ? » — Au handshake : cookie de session (mais vérifier Origin contre une allowlist, sinon hijacking cross-site) ou token court-vécu passé en query string ou échangé dans le premier message. Ensuite, l’identité est attachée à la connexion — pas besoin de re-authentifier chaque message, mais il faut gérer l’expiration du token sur les connexions longues.

Pièges & idées reçues

⚠️ Pas de CORS sur les WebSockets — le navigateur n’applique aucune politique d’origine au handshake. Un serveur qui authentifie par cookie sans vérifier le header Origin laisse n’importe quel site ouvrir une connexion authentifiée : c’est le cross-site WebSocket hijacking. Allowlist d’origines côté serveur, systématiquement.

  • « Temps réel = WebSocket » — pour du serveur → client pur, SSE est plus simple à opérer et souvent suffisant. Le WebSocket se justifie quand le client émet aussi.
  • Oublier le heartbeat : les proxies et NAT coupent silencieusement les connexions inactives ; sans ping/pong, le serveur garde des connexions zombies et le client croit être connecté.
  • Reconnexion naïve : reconnecter immédiatement en boucle transforme chaque redémarrage serveur en auto-DDoS. Backoff exponentiel avec jitter, et resynchronisation de l’état manqué.
  • « socket.io, c’est des WebSockets » — c’est un protocole au-dessus : un client socket.io ne parle pas à un serveur WebSocket natif, et inversement.
  • Ignorer la backpressure : un client lent avec un serveur qui pousse vite = buffer qui gonfle en mémoire. Surveiller bufferedAmount côté client, fermer ou throttler les connexions saturées côté serveur.

Pour aller plus loin

The essentials

HTTP is a request/response protocol: the client asks, the server answers, the transaction is over. The server cannot take the initiative to send a message — a problem as soon as you want real-time: chat, notifications, stock tickers, collaborative cursors.

WebSocket addresses that limit: a persistent, full-duplex TCP connection (both sides send whenever they want), established through an HTTP handshake and then freed from the request/response model. One connection, a few bytes of overhead per message, minimal latency.

But the reflex “real-time = WebSocket” is a classic interview mistake: for a unidirectional server → client stream (notifications, job progress), Server-Sent Events do the job over plain HTTP, with built-in automatic reconnection. Comparing the options honestly is worth more than reciting the RFC.

How it works

The handshake: the client sends an HTTP GET request with Connection: Upgrade, Upgrade: websocket and a Sec-WebSocket-Key header (random nonce). The server answers 101 Switching Protocols with Sec-WebSocket-Accept (SHA-1 hash of the nonce concatenated with a fixed GUID — proof it speaks WebSocket, not cryptography). From then on, the TCP connection stays open and both sides exchange frames.

Client                             Server
  │ GET /chat HTTP/1.1               │
  │ Connection: Upgrade              │
  │ Upgrade: websocket               │
  │ Sec-WebSocket-Key: <nonce>       │
  │─────────────────────────────────▶│
  │                                  │
  │ HTTP/1.1 101 Switching Protocols │
  │ Sec-WebSocket-Accept: <hash>     │
  │◀─────────────────────────────────│
  │                                  │
  │◀════ full-duplex frames ════════▶│
  │      (text, binary, ping/pong)   │

Frames: each message is split into frames carrying an opcode (text, binary, ping, pong, close). Client → server frames are masked (XOR with a random key) to prevent cache-poisoning attacks on naive proxies. The ping/pong control frames serve as a heartbeat.

Heartbeat & reconnection: a TCP connection can be dead without anyone knowing — a proxy, NAT or firewall silently drops idle connections (often after 30-60 s). Hence the heartbeat: the server sends a periodic ping; no pong back = dead connection, clean it up. Client-side, reconnection uses exponential backoff + jitter (otherwise, when the server restarts, every client comes back at once and knocks it over — thundering herd), followed by a state resync: messages missed during the outage don’t come back on their own.

Everything the client needs fits in a few lines of native WebSocket:

function connect(attempt = 0) {
  const ws = new WebSocket("wss://api.example.com/chat");

  ws.onopen = () => {
    attempt = 0;                                      // reset the backoff
    ws.send(JSON.stringify({ type: "auth", token })); // auth as first message
  };

  ws.onmessage = (e) => render(JSON.parse(e.data));

  ws.onclose = () => {
    // exponential backoff + jitter: avoids the thundering herd
    const delay = Math.min(30_000, 1000 * 2 ** attempt)
                + Math.random() * 1000;
    setTimeout(() => connect(attempt + 1), delay);
  };
}
connect();

Horizontal scaling: THE trap question. A WebSocket is stateful: the connection lives on one specific instance. With 3 instances behind a load balancer, Alice is connected to instance A and Bob to instance B — if Alice sends a message to a room, instance A cannot push it to Bob directly. Two building blocks: sticky sessions at the load balancer (so the handshake and the connection stay on the same instance), and a pub/sub (typically Redis) to broadcast across instances: A publishes the message on a channel, every subscribed instance receives it and relays it to its own connected clients.

🎤 In an interview — “and across several instances?” systematically follows your nice handshake explanation. Having both building blocks ready — sticky sessions + Redis pub/sub — and knowing why each one is needed makes all the difference.

Key concepts to master

Four real-time techniques, compared honestly:

Short pollingLong pollingSSEWebSocket
Directionclient → serverserver → client (simulated)server → clientbidirectional
Transportrepeated HTTPHTTP held openHTTP streaming (text/event-stream)TCP after upgrade
Latencyinterval/2 on averagenear zeronear zerominimal
Reconnectionn/aafter every messageautomatic (EventSource, Last-Event-ID)manual (backoff)
Dataanything HTTPanything HTTPtext onlytext + binary
Best forinfrequent datalegacy compatibilitynotifications, dashboards, LLM streamschat, games, collaborative editing

💡 SSE is underrated — free reconnection and resumption, standard HTTP that gets through everything, and the old HTTP/1.1 limit of 6 connections per domain is lifted by HTTP/2 multiplexing. Before reaching for a WebSocket, one question: does the client actually need to send?

  • socket.io vs native WebSocket: socket.io is a library on top of the transport (WebSocket when possible, long polling as fallback) adding automatic reconnection, rooms, namespaces, acknowledgements and a ready-made Redis adapter for multi-instance setups. Cost: a proprietary protocol (a raw WebSocket client cannot connect to it) and a dependency on both sides. In 2026 the fallback matters less than it used to; rooms and the adapter are what justify socket.io.
  • Security: wss:// (TLS) mandatory — like https. The browser’s WebSocket API does not allow custom headers: handshake auth goes through the session cookie, a token in the query string (beware of server logs) or a first authentication message. Above all: no CORS on WebSockets — the server must check the Origin header itself, otherwise any website can open a cookie-authenticated connection (cross-site WebSocket hijacking).

In an interview

“Why not just do polling?” — Short polling wastes empty requests and imposes an average latency of half the interval. It remains defensible for infrequent data (every 30 s) — it’s simple, stateless, cacheable. Once frequency rises or latency matters, SSE or WebSocket.

“Describe the WebSocket handshake” — HTTP GET with Upgrade: websocket, Connection: Upgrade and Sec-WebSocket-Key; response 101 Switching Protocols with Sec-WebSocket-Accept derived from the key. The TCP connection is then reused for full-duplex frames. Bonus: starting as HTTP gets through proxies and shares port 443.

“How do you scale a chat across several instances?” — Sticky sessions at the load balancer so each connection lives on a stable instance, and Redis pub/sub between instances: the one receiving a message publishes it on a channel, the others relay it to their clients in the relevant room. Mention socket.io’s Redis adapter, which implements exactly this.

“SSE or WebSocket for notifications?” — SSE: the flow is unidirectional, the EventSource API handles reconnection and resumption natively, and it travels over standard HTTP (no special proxy/LB configuration). A WebSocket would add complexity (heartbeat, manual reconnection) with no benefit since the client doesn’t send.

“How do you authenticate a WebSocket connection?” — At the handshake: session cookie (but check Origin against an allowlist, otherwise cross-site hijacking) or a short-lived token passed in the query string or exchanged in the first message. After that, identity is attached to the connection — no need to re-authenticate every message, but you must handle token expiry on long-lived connections.

Pitfalls & misconceptions

⚠️ No CORS on WebSockets — the browser applies no origin policy to the handshake. A server that authenticates via cookies without checking the Origin header lets any website open an authenticated connection: that’s cross-site WebSocket hijacking. Allowlist origins server-side, always.

  • “Real-time = WebSocket” — for pure server → client, SSE is simpler to operate and often enough. WebSocket earns its place when the client sends too.
  • Forgetting the heartbeat: proxies and NAT silently drop idle connections; without ping/pong, the server keeps zombie connections and the client believes it’s still connected.
  • Naive reconnection: reconnecting immediately in a loop turns every server restart into a self-inflicted DDoS. Exponential backoff with jitter, plus resyncing the missed state.
  • “socket.io is WebSockets” — it’s a protocol on top: a socket.io client cannot talk to a native WebSocket server, and vice versa.
  • Ignoring backpressure: a slow client with a fast-pushing server = a buffer ballooning in memory. Watch bufferedAmount on the client, close or throttle saturated connections on the server.

Going further

S'entraîner sur ce sujet → Practice this topic →