Encore un protocole ?
Chrome et Firefox l'embarquent déjà, alors que sa spec n'est même pas finalisée. WebTransport n'a pas encore de numéro de RFC — mais il pourrait bien remplacer la moitié de ce qu'on utilise aujourd'hui.
Il y a quelque chose d'inhabituel avec WebTransport : les navigateurs l'embarquent avant que sa spécification soit finalisée. Chrome le supporte depuis un moment, Firefox l'a activé à son tour. Pourtant, le protocole est toujours à l'état de brouillon — il n'a même pas encore de numéro de RFC officiel. C'est rare, et ça dit quelque chose : le besoin est réel.
Mais quel besoin, au juste ? On a déjà WebSocket, SSE, HTTP/2, HTTP/3… pourquoi encore un protocole ? C'est toute la question de ce cours. La réponse courte tient en une phrase :
Une API de transport moderne dans le navigateur, bâtie sur HTTP/3 (donc sur QUIC). Elle donne au code client le pouvoir d'ouvrir des streams bidirectionnels fiables et des datagrammes non fiables — l'équivalent d'UDP, directement depuis une page web, sans l'héritage encombrant de WebSocket.
Pour comprendre pourquoi c'est un changement de cap, il faut remonter le fil : voir comment le web a multiplié les ruses pour faire parler le serveur, où WebSocket bute, ce que les streams de HTTP/2 et HTTP/3 ont apporté, et enfin ce que WebTransport débloque par-dessus tout ça.
Faire parler le serveur en premier
Le web est né sur un schéma simple : le client demande, le serveur répond. Toute l'histoire du temps réel, c'est une suite de ruses pour contourner cette règle — et WebTransport en est la dernière.
Le web a été conçu pour partager des documents. Son protocole, HTTP, repose donc sur un échange d'une simplicité absolue : le client envoie une requête, le serveur répond, et c'est terminé. Le serveur ne prend jamais l'initiative — il attend, sagement. Parfait pour afficher une page. Frustrant dès qu'on veut de l'interactivité, car comment le serveur te prévient-il qu'un message est arrivé s'il ne peut pas parler le premier ?
Toute l'histoire du temps réel sur le web est une suite de ruses pour contourner cette règle. Chacune ajoute une capacité, sans tout résoudre.
| Technique | L'astuce | Ce qui manque |
|---|---|---|
| Long polling | Le serveur garde la requête ouverte jusqu'à avoir quelque chose à dire | Une requête par message ; toujours à sens unique |
| SSE | Une requête, une réponse infinie découpée en « événements » (chunked, text/event-stream) | Le serveur parle, mais le client ne peut rien renvoyer sur ce canal |
| WebSocket | On upgrade la connexion HTTP en un canal brut, bidirectionnel | Un surcoût par trame, et une dépendance à TCP (voir section suivante) |
Le long polling et SSE sont des détournements astucieux du couple requête/réponse — on peut même bricoler du bidirectionnel en combinant les deux, mais ce n'est jamais naturel. C'est précisément ce qui a justifié WebSocket : un vrai canal à double sens. Et c'est cette même quête de naturel qui mène, des années plus tard, à WebTransport.
Ce qui manque à WebSocket
WebSocket a réglé la bidirectionnalité, et on s'en sert tous les jours. Mais il traîne un héritage de 2011 : un surcoût par trame, un masquage devenu inutile, et une dépendance à TCP dont il ne peut pas se défaire.
Soyons clairs : WebSocket est excellent, et on s'en sert quotidiennement. Mais il a été conçu en 2011, et il porte les marques de son époque. Trois limites, en particulier, expliquent pourquoi on a fini par chercher mieux.
1. Un surcoût par trame
WebSocket a son propre concept de trames, avec un en-tête sur chacune. Ce n'est pas énorme — quelques octets par trame, plus une clé de masquage — mais c'est un coût que tu n'avais pas avant, et qui s'accumule sur un canal très bavard.
2. Le masquage, une précaution devenue inutile
Chaque trame envoyée par le client est masquée (un XOR avec une clé). L'objectif d'origine : empêcher l'empoisonnement de cache des proxys intermédiaires qui ne comprenaient pas WebSocket. Mais c'était une parade conçue avant que TLS ne soit omniprésent. Sur un canal chiffré, personne ne peut intercepter ni altérer le trafic — le problème n'existe plus, et le masquage n'est qu'un travail cryptographique en pure perte.
3. Prisonnier de TCP
C'est la limite la plus profonde. WebSocket s'établit au-dessus de HTTP/1 (et peut l'être sur HTTP/2), donc au-dessus de TCP. Or TCP impose une livraison ordonnée et fiable : impossible de demander « envoie-moi ces paquets vite, tant pis si l'un se perd ou arrive dans le désordre ». Pour de l'audio ou de la vidéo temps réel, c'est exactement ce qu'on voudrait pouvoir faire.
Le multiplexing et son talon d'Achille
HTTP/2 a introduit les streams : plein de requêtes sur une seule connexion. Génial — mais tant qu'on est sur TCP, un seul paquet perdu bloque tout le monde. C'est là que QUIC entre en scène.
HTTP a évolué, et HTTP/2 a apporté une idée décisive : les streams. Au lieu d'une connexion TCP par requête (ou de requêtes mises bout à bout sur une même connexion), on découpe une seule connexion en multiples flux logiques. Chaque morceau de données est étiqueté d'un identifiant de stream ; le serveur peut donc démêler des dizaines de requêtes envoyées en même temps sur le même fil.
Plusieurs streams indépendants partagent une seule connexion. Chacun a son couloir, identifié par un id ; tout se mélange sur le câble, mais le destinataire sait les séparer. Une seule connexion sert ainsi des requêtes concurrentes — fini la file d'attente.
Magnifique sur le papier. Sauf que HTTP/2 reste au-dessus de TCP, et TCP livre les octets dans l'ordre, quoi qu'il arrive. Si un seul segment se perd, le noyau retient tous les streams en attendant sa retransmission — même ceux qui n'ont rien à voir. C'est le fameux blocage en tête de file (head-of-line blocking) : tes couloirs indépendants sont, en réalité, à la merci d'un seul paquet perdu.
L'angle mort du navigateur
Reste un problème de pouvoir. gRPC, le protocole de Google bâti sur les streams de HTTP/2, exploite ce découpage à fond — streams unidirectionnels, bidirectionnels, sérialisation par Protocol Buffers. Mais gRPC ne fonctionne pas nativement dans le navigateur, malgré ce qu'on entend parfois. Pourquoi ? Parce qu'il a besoin d'un accès bas niveau aux streams, que le navigateur refuse : depuis une page, tu n'as que l'API fetch. Tu envoies une requête, tu lis une réponse — mais tu ne décides pas des connexions ni des streams, le navigateur s'en charge à ta place.
C'est exactement le verrou que WebTransport vient ouvrir : donner au code client ce contrôle des streams, mais de façon sûre et encadrée.
Sessions, streams et datagrammes
Voici la pièce maîtresse : une session dans laquelle client et serveur ouvrent des streams fiables — ou des datagrammes non fiables. Autrement dit, de l'UDP dans le navigateur, enfin.
On y est. WebTransport ouvre une session au-dessus de HTTP/3, et fait pour toi ce travail bas niveau que le navigateur cachait jusqu'ici. Dans cette session, le client ou le serveur peut ouvrir trois sortes de canaux.
| Type de canal | Sens | Garanties |
|---|---|---|
| Stream bidirectionnel | Les deux parlent | Fiable, ordonné |
| Stream unidirectionnel | Un seul sens | Fiable, ordonné |
| Datagramme | Un seul sens, « tire et oublie » | Non fiable : ni ordre, ni retransmission |
C'est de l'UDP, exposé dans le navigateur. Tu envoies un paquet, le réseau le livre comme il peut — peut-être dans le désordre, peut-être pas du tout — et tu t'en accommodes. Jusqu'ici, la seule source de « presque-UDP » côté web était WebRTC. WebTransport en fait une primitive de première classe.
Pourquoi vouloir moins de garanties ? Parce que pour de l'audio ou de la vidéo en direct, attendre la retransmission d'un paquet perdu est pire que le perdre. Mieux vaut une image sautée qu'un gel de l'écran. WebTransport te rend ce choix : c'est toi, l'application, qui décides quoi faire d'un paquet en retard ou manquant — l'ignorer, le bufferiser, sauter une trame.
Le simulateur ci-dessous oppose les deux mondes. Envoie une rafale de paquets sur un stream fiable, puis sur un datagramme non fiable, avec une perte au milieu — et observe la différence de comportement.
Choisis le mode, puis envoie des paquets (l'un sera perdu) :
Deux saveurs, pas les mêmes pouvoirs
WebTransport existe au-dessus de HTTP/2 et de HTTP/3 — et ce ne sont pas les mêmes capacités. Le transport sous-jacent décide de ce que tu peux faire, à commencer par les datagrammes.
Détail qui a son importance : WebTransport existe en deux saveurs, selon le HTTP qui le porte. Il y a un brouillon au-dessus de HTTP/2, et un autre au-dessus de HTTP/3 — écrits par des équipes différentes, au point que les deux specs se ressemblent à peine. Et surtout, ils n'offrent pas les mêmes pouvoirs, parce que le transport sous-jacent n'est pas le même.
| WebTransport / HTTP/2 | WebTransport / HTTP/3 | |
|---|---|---|
| Transport | TCP | QUIC (sur UDP) |
| Streams bidirectionnels | Oui | Oui |
| Streams unidirectionnels | Oui | Oui |
| Datagrammes non fiables | Non (TCP est ordonné) | Oui |
| Blocage en tête de file | Oui (au niveau TCP) | Non (streams QUIC indépendants) |
La logique est implacable : sur TCP, impossible d'offrir des datagrammes non fiables, puisque TCP ordonne et garantit tout par construction. Il faut UDP pour ça — et c'est exactement ce que QUIC apporte. C'est aussi pour cette raison que HTTP/3 a été inventé : éliminer le blocage en tête de file qui handicape HTTP/2.
Le simulateur ci-dessous te laisse choisir le transport sous-jacent. Bascule entre HTTP/2 et HTTP/3 et observe quelles capacités s'allument.
Choisis le transport, observe les capacités :
Les atouts de QUIC
WebTransport n'hérite pas que de l'absence de blocage. Il hérite de deux superpouvoirs que TCP ne pourra jamais offrir : une connexion qui survit au changement de réseau, et une reprise sans aller-retour.
On a dit que WebTransport vit sur HTTP/3, donc sur QUIC. Ce choix ne se résume pas à « éviter le blocage en tête de file » : QUIC apporte deux capacités que TCP, par construction, ne pourra jamais avoir. Comprendre ces atouts, c'est comprendre pourquoi WebTransport vise l'avenir et pas le rattrapage.
La connexion qui survit au changement de réseau
Comment TCP identifie-t-il une connexion ? Par un quadruplet : IP source, port source, IP destination, port destination. Change l'un des quatre — par exemple ton téléphone qui bascule du Wi-Fi à la 4G, donc une nouvelle IP — et pour TCP, c'est une connexion différente. L'ancienne est morte ; ton WebSocket tombe, et il faut tout refaire : handshake TCP, puis handshake TLS.
QUIC n'identifie pas une connexion par l'adresse IP, mais par un Connection ID indépendant du réseau. Quand ton IP change, la connexion migre : le Connection ID reste le même, aucun nouveau handshake, la session continue sans rien perdre. Pour une application mobile, c'est la différence entre une coupure et une continuité parfaite.
0-RTT : reprendre sans payer l'aller-retour
Quand un client se reconnecte à un serveur qu'il connaît déjà, QUIC peut glisser des données applicatives dans le tout premier paquet, avant même la fin du handshake. C'est le 0-RTT : zéro aller-retour d'attente, la reprise est quasi instantanée.
Des boutons de réglage
Fidèle à la philosophie du contrôle, WebTransport expose des préférences plutôt que de tout cacher : congestionControl permet d'indiquer si tu privilégies le débit ou la latence, et reliability renseigne sur le support du transport fiable ou non. Tu orientes le comportement sans réécrire la pile réseau.
Tout ce socle — QUIC, son handshake, son 0-RTT (hérité de TLS 1.3) — est creusé dans HTTP · De 1.1 à 3 et TLS/HTTPS.
Le simulateur ci-dessous met les deux mondes face au changement de réseau. Bascule le réseau et compare ce qui arrive à une session WebSocket/TCP et à une session WebTransport/QUIC.
Change de réseau et compare les deux sessions :
Une connexion, mille usages
Fini d'« upgrader » toute la connexion comme avec WebSocket. WebTransport ne réserve qu'un stream — si bien qu'une même connexion porte tes pages, une session WebTransport et un flux SSE, en même temps.
Comment ouvre-t-on une session WebTransport ? La réponse révèle une des plus belles idées du protocole. Pour la saisir, repartons de WebSocket.
Avec HTTP/1, WebSocket s'établit via un en-tête Connection: Upgrade : on prend la connexion TCP existante et on la convertit entièrement en canal WebSocket. À partir de là, cette connexion ne sert plus qu'à ça. C'était acceptable en HTTP/1 — mais en HTTP/2 et /3, ce serait du gâchis : on perdrait une connexion précieuse, capable de multiplexer des dizaines de streams, juste pour un seul usage.
Plutôt que d'upgrader toute la connexion, on n'en réserve qu'un stream. Une méthode HTTP spéciale,
CONNECT étendue (introduite par la RFC 8441 pour faire passer WebSocket sur HTTP/2), négocie ce stream. Si le serveur accepte (réponse 200), ce stream devient le canal — et son identifiant sert d'identifiant de session pour ouvrir d'autres streams ensuite.
La conséquence est spectaculaire. Une seule connexion vers un domaine, sur le port 443, peut désormais porter simultanément :
Tu peux continuer à cliquer sur des liens et charger des pages pendant qu'une session WebTransport fait circuler cinq streams et qu'un flux d'événements tourne en parallèle — le tout sur la même connexion. On peut même ouvrir plusieurs sessions sur une seule connexion, chacune avec sa propre authentification.
Le simulateur ci-dessous est cette connexion unique. Ajoute différents canaux et regarde-les cohabiter, chacun avec son identifiant de stream.
Ajoute des canaux sur la même connexion :
L'API en pratique
Assez de théorie. Voici à quoi ressemble vraiment WebTransport dans du JavaScript de navigateur : ouvrir une session, créer un stream, pousser un datagramme — le tout sur l'API Streams standard.
Les concepts sont posés ; voyons le code réel. L'API WebTransport du navigateur est étonnamment compacte, et elle s'appuie entièrement sur l'API Streams standard du web (ReadableStream / WritableStream) — donc tu récupères le contrôle de flux (backpressure) gratuitement.
Ouvrir une session
const wt = new WebTransport('https://exemple.com:4433/chat');
await wt.ready; // la session est prête
// wt.closed est une Promise qui se résout à la fermeture
Un stream bidirectionnel fiable
const stream = await wt.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode('salut'));
const reader = stream.readable.getReader();
const { value, done } = await reader.read();
Des datagrammes non fiables
// wt.datagrams est un flux duplex (lecture + écriture)
const dgWriter = wt.datagrams.writable.getWriter();
dgWriter.write(payload); // tire et oublie : ni ordre ni retransmission
const dgReader = wt.datagrams.readable.getReader();
const { value } = await dgReader.read();
Le serveur aussi peut ouvrir des streams. Côté client, on les reçoit via
wt.incomingBidirectionalStreams et wt.incomingUnidirectionalStreams — eux-mêmes des ReadableStream dont chaque élément est un stream entrant à lire.
Régler et fermer
// préférence exprimée à la création
const wt = new WebTransport(url, { congestionControl: 'low-latency' });
await wt.getStats(); // statistiques HTTP/3 de la connexion
wt.close({ closeCode: 0, reason: 'fini' });
fetch().body ou les flux Node. Si cette API te semble nouvelle, les flux et le backpressure sont au cœur des cours Streaming HTTP et PHP Streaming.
Lequel choisir, et pour quoi
SSE, WebSocket, WebRTC, gRPC, WebTransport : cinq façons de dépasser le request/response. Voici la carte pour t'y retrouver — et un pari sur ce qui survivra.
Cinq façons de dépasser le vieux request/response coexistent aujourd'hui. Aucune n'est « la meilleure » dans l'absolu — chacune répond à un besoin. Voici la carte.
| Techno | Sens | Transport | Non fiable ? | Pour quoi |
|---|---|---|---|---|
| SSE | Serveur → client | HTTP/TCP | Non | Flux d'événements descendants (notifs, ticks) |
| WebSocket | Bidirectionnel | TCP | Non | Bidirectionnel mature, support universel |
| WebRTC | Bidirectionnel (P2P) | UDP | Oui | Pair-à-pair audio/vidéo |
| gRPC | Bi/unidirectionnel | HTTP/2 | Non | Service-à-service (pas natif navigateur) |
| WebTransport | Bi/unidirectionnel | HTTP/3 (QUIC) | Oui | Temps réel moderne, fiable et non fiable |
Comment choisir
- Tu ne fais que pousser vers le client (notifications, cours de bourse) ? SSE suffit, et c'est le plus simple.
- Tu veux du bidirectionnel avec un support partout, aujourd'hui ? WebSocket reste le choix sûr.
- Tu fais de l'audio/vidéo pair-à-pair ? WebRTC est conçu pour ça.
- Tu veux le temps réel moderne — streams fiables et datagrammes non fiables, sans blocage en tête de file — et tu peux viser HTTP/3 ? WebTransport.
WebTransport n'invente pas le temps réel : il en réunit les meilleures idées sur la fondation la plus solide (QUIC). Une session, des streams au choix fiables ou non, le tout multiplexé sur une connexion qui sert aussi le reste du site. C'est l'aboutissement d'une longue lignée de ruses pour faire parler le serveur.
Pour explorer les territoires voisins : HTTP · De 1.1 à 3 (QUIC, streams, blocage en tête de file), Chat temps réel serverless (WebSocket en pratique), Streaming HTTP (SSE), TCP & Sockets et TLS/HTTPS.