The WebSocket Protocol
🔌 The WebSocket Protocol
The HTTP Upgrade Handshake
The client sends a normal-looking HTTP GET request, but with headers asking the server to switch protocols mid-connection:
The Raw Handshake, Side by Side
Sec-WebSocket-Accept is computed by hashing the client's key together with a fixed constant — proof the server actually understood the request, not just an echo. Once the 101 response arrives, the HTTP connection is done being HTTP — the same underlying TCP connection now carries the WebSocket protocol instead.
Why Start as HTTP at All?
Because it starts as an ordinary request, a WebSocket handshake sails through the same ports (80/443) and most proxies/firewalls that already allow normal web traffic — no special network configuration needed in most environments.
One Handshake, Then Done
The Upgrade dance happens exactly once, at connection start — everything after this is the WebSocket protocol itself, not more HTTP requests.
↔️ Full-Duplex vs Half-Duplex
Even a kept-alive HTTP connection is fundamentally half-duplex — one side sends a complete request, then waits for a complete response, strict turn-taking. A WebSocket connection is genuinely full-duplex: either side can send a message at any moment, including while the other side is still sending one.
Frames, Not More HTTP Requests
After the handshake, messages travel as lightweight binary frames — no headers, no status lines, just a small frame header plus the payload:
| Opcode | Meaning |
|---|---|
0x1 | Text frame (UTF-8 payload) |
0x2 | Binary frame |
0x8 | Close |
0x9 | Ping |
0xA | Pong |
Masking (Client → Server Only)
Every frame the browser sends is XOR-masked with a random key — a security measure preventing a malicious page from crafting bytes that could poison a misbehaving proxy's cache. Server-to-client frames are never masked.
Minimal Overhead
A small text message can travel in a frame with as little as 2 bytes of header — compare that to a full set of HTTP headers on every polling request from Chapter 1.
Per-Message Overhead: Polling vs an Open WebSocket
HTTP Polling
Every single message is a brand-new HTTP request: request line, a full set of headers (cookies, user-agent, etc.), a fresh TCP handshake if the connection isn't kept alive — easily hundreds of bytes of overhead per message.
Established WebSocket
The connection and its headers exist exactly once, at handshake time. Every message after that is just a frame — a few bytes of header plus the actual payload.
🔒 ws:// vs wss://
The exact same relationship as http/https from the HTTPS/TLS course — wss:// runs the WebSocket connection over TLS, encrypting everything after the handshake the same way HTTPS encrypts a normal page:
Ping/Pong: Protocol-Level Keepalive
The protocol itself includes ping and pong frames — the server (or client) periodically sends a ping, and the other side automatically replies with a pong, letting either end detect a connection that's gone dead without any application code writing that logic. This is distinct from any application-level heartbeat message you might add on top of it.
Closing Properly: The Close Handshake
A well-behaved close exchanges close frames carrying a status code and optional reason, rather than either side just abruptly dropping the TCP connection:
| Code | Meaning |
|---|---|
1000 | Normal closure |
1001 | Going away (e.g. page navigating off) |
1006 | Abnormal closure (connection just died — no close frame was ever received) |
💻 Coding Challenges
Challenge 1: Read a Handshake
Given a captured handshake request missing the Upgrade header, explain what the server should do in response, and why the connection would fail to establish.
Goal: Practice recognizing the exact headers required for a valid WebSocket handshake.
Challenge 2: Explain Full-Duplex With a Scenario
Describe a concrete scenario in a chat application where full-duplex communication produces a noticeably better user experience than a half-duplex (request/response) alternative could.
Goal: Practice connecting the abstract protocol property to a real, observable user-facing difference.
Challenge 3: Diagnose a Mixed-Content Block
A page served over https://example.com tries to open new WebSocket("ws://example.com/chat") and the connection is silently blocked by the browser. Explain why, and provide the fix.
Goal: Practice connecting the ws/wss distinction to a real, commonly-hit browser security restriction.
It's easy to mentally model a WebSocket as "a request that stays open" and treat it like any other HTTP request for logging, monitoring, or load-balancing purposes — it isn't. A single WebSocket connection can live for hours, carries no natural request boundary the way HTTP does, and needs to stay pinned to the same backend server for its entire lifetime (Chapter 7 covers why). Tooling built around "count requests per second" or "route each request independently" often needs real adjustment to handle a connection that's alive, and expected to stay alive, for far longer than any normal HTTP request ever would.
🎯 What's Next
With the protocol itself understood, the next chapter puts it to work: Building a Raw WebSocket Server — Node's ws library, and handling connection, message, and close events directly.