Skip to main content

Beyond HTTP, Part 2: Two-Way Streets

· 9 min read
Pere Pages
Software Engineer
A browser window on the left and a server on the right joined by a broad channel carrying amber arrows flowing right and teal arrows flowing left at the same time, showing full-duplex two-way traffic

In Part 1 the server learned to talk without being asked — but the browser could still only listen. A chat, a multiplayer game, a collaborative editor: these need both sides speaking freely over one connection. This is Part 2 of the series, and it's about the browser's true two-way channels — WebSockets, the workhorse, and WebTransport, its modern successor.

note

This reflects the state of the web platform as of 2026. WebTransport reached cross-browser availability only in early 2026, so double-check current support on MDN or caniuse before relying on it in production.

Beyond HTTP is a three-part series. Part 1 — server push. Part 2 — two-way channels (you are here). Part 3 — browser to browser: WebRTC.

The mental model: one open line, both sides talk

A two-way channel is a phone call that neither side hangs up — either party can speak the instant it has something to say. Server-Sent Events from Part 1 kept the line open but muted the browser; here the browser gets its voice back. Once the channel is established, there is no request and no response, just messages flowing in both directions whenever either end sends one. That property has a name: full-duplex, both directions at once.

Everything in this post is a variation on that idea. The two technologies differ in what they run on top of — and that single choice of transport explains almost all of their differences.

WebSockets: an HTTP handshake, then a raw channel

A WebSocket starts life as an ordinary HTTP request and then asks to be upgraded into a persistent two-way connection. This is the clever part of its design: it borrows HTTP just long enough to get through firewalls and reuse the same port, then leaves HTTP behind entirely.

The browser sends a normal-looking request with a special header asking to switch protocols:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

If the server agrees, it answers not with 200 OK but with 101 Switching Protocols. From that moment the connection is no longer HTTP — it is a bare, full-duplex pipe over Transmission Control Protocol (TCP), the reliable ordered-delivery protocol most of the web runs on.

On the client the API is small. You open a socket, and from then on send() and an onmessage handler are all you need — in either direction, at any time:

const socket = new WebSocket('wss://example.com/chat');

socket.onopen = () => socket.send('hello');
socket.onmessage = (event) => render(event.data);

Note the wss:// scheme — the encrypted form, WebSocket over Transport Layer Security (TLS), the secure default just as https:// is for pages. WebSocket messages are framed, so unlike a raw stream you receive whole messages, not arbitrary byte chunks, and they can carry text or binary. An optional subprotocol (negotiated in the handshake) lets both ends agree on a message format, like a mini application protocol of your own.

The WebSocket gotchas

WebSockets give you a channel and almost nothing else — everything above "bytes in, bytes out" is your problem. That minimalism is why they're everywhere, but it's worth knowing what you're signing up for:

  • No automatic reconnection. Unlike Server-Sent Events, a dropped WebSocket stays dropped. You write the reconnect-and-resume logic yourself.
  • No built-in message types or routing. A WebSocket is one undifferentiated stream of frames. Channels, request/response correlation, acknowledgements — you invent them (this is what subprotocols and libraries like Socket.IO add on top).
  • Head-of-line blocking. Because it rides on a single TCP connection, one lost packet stalls every message behind it until the network resends it — even unrelated ones. TCP guarantees order, and that guarantee becomes a bottleneck under packet loss.
  • Scaling needs stickiness. A WebSocket is a long-lived connection pinned to one server process. Spread users across many servers and you need sticky routing plus a way to broadcast between servers (a message bus), because the user on server A isn't connected to server B.

None of these are dealbreakers — WebSockets have powered real-time apps for over a decade. But the last two, especially head-of-line blocking, are exactly what the next technology set out to fix.

WebTransport: two-way over HTTP/3

WebTransport is a newer two-way API built on HTTP/3 and QUIC, designed to fix WebSocket's transport-level weaknesses. QUIC is a modern transport protocol that runs over the User Datagram Protocol (UDP) instead of TCP, and it changes the game in two ways that matter here.

First, independent streams. A single WebTransport connection can carry many concurrent streams, and because QUIC tracks them separately, a lost packet in one stream doesn't stall the others — the head-of-line blocking that plagues WebSockets is gone. Second, unreliable datagrams. Alongside reliable ordered streams, WebTransport offers fire-and-forget datagrams that are not retransmitted if lost — perfect for data where the newest value matters more than every value (a player's position, a live cursor), where a late-arriving old packet is worthless anyway.

const transport = new WebTransport('https://example.com:4433/wt');
await transport.ready;

// Reliable, ordered — like a WebSocket message:
const stream = await transport.createBidirectionalStream();

// Unreliable, fire-and-forget — the newest value wins:
const writer = transport.datagrams.writable.getWriter();
writer.write(positionUpdate);

The trade-off used to be maturity, and this is the fact worth updating in your head: as of early 2026 WebTransport is supported across all major browsers, so its "too new to use" era is largely over. WebSockets still win on ubiquity of tooling, server support, and battle-tested libraries, but WebTransport is no longer an experiment — it's the better default for anything latency-sensitive that suffers under head-of-line blocking.

WebSocket vs WebTransport (and where SSE fits)

Choose by how much control you need over reliability and how much you value maturity over transport efficiency. Here is the whole picture, including Part 1's Server-Sent Events for contrast:

FeatureServer-Sent EventsWebSocketWebTransport
DirectionServer → browserFull-duplexFull-duplex
Underlying transportHTTPTCPHTTP/3 (QUIC / UDP)
Message typesText onlyText + binaryBinary streams + datagrams
Auto-reconnectBuilt inDIYDIY
Head-of-line blockingPer connectionYes (TCP)None
Unreliable modeNoNoYes (datagrams)
Tooling & maturityVery matureVery matureNewly cross-browser

best good medium weak — green is the stronger choice for that row; red is the weaker one.

In practice: reach for WebSockets when you want a proven, well-supported two-way channel and simple ordered messaging is enough — most chats and dashboards. Reach for WebTransport when the workload is latency-critical and hurt by head-of-line blocking, or when you genuinely want unreliable datagrams — real-time games, live media control, high-frequency telemetry.

Where this leaves us

WebSockets and WebTransport both give the browser a full, equal voice — but always in a conversation with a server. The connection runs client to server, and if two users want to talk, their bytes still pass through the middle. The last frontier is cutting the server out of the data path entirely and letting one browser talk straight to another — which is a different problem, and a different technology. That is Part 3: WebRTC.

Glossary

TermWhat it is
Full-duplexBoth ends can send at the same time over one connection.
WebSocketA two-way channel that begins as an HTTP request upgraded to a raw TCP connection.
TCPTransmission Control Protocol — reliable, ordered byte delivery; the basis of most of the web.
101 Switching ProtocolsThe HTTP status a server returns to accept a WebSocket upgrade.
Head-of-line blockingOne lost packet stalling all messages queued behind it on the same connection.
WebTransportA two-way browser API over HTTP/3, offering independent streams and unreliable datagrams.
QUICA modern transport protocol over UDP that underpins HTTP/3; streams don't block each other.
UDPUser Datagram Protocol — fast, connectionless delivery with no ordering or retransmission.
DatagramA single fire-and-forget packet that is not retransmitted if lost.

References