Skip to content
Lesson 16 of 16

Step 1 of 5 · Reading · ~3 min

Read

Caching, Compression & Beyond

WebSocket Upgrade Handshake

WebSocket (RFC 6455) hitches a ride on HTTP. A normal HTTP/1.1 request asks to switch protocols, the server answers 101 Switching Protocols, and from then on the same TCP connection carries bidirectional frames instead of HTTP messages. Chat, collaborative editing and live dashboards all start with this one exchange. Your program will validate the request exactly as a server must and write back the exact response bytes.

The client's request

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

Five things must hold, and the order of the checks decides the status code:

  1. Method GET. Anything else: 405 Method Not Allowed with Allow: GET.
  2. HTTP/1.1 (the protocol needs Host and persistent connections) and exactly one Host header. Otherwise 400.
  3. Upgrade contains the token websocket and Connection contains the token upgrade. Both are comma-separated lists, compared case-insensitively, and a header may repeat. Connection: keep-alive, Upgrade is what browsers send. Otherwise 400.
  4. Sec-WebSocket-Key: exactly one header, and its value is the base64 of a 16-byte nonce. That is 24 characters: 22 characters from A-Z a-z 0-9 + / followed by ==. Otherwise 400.
  5. Sec-WebSocket-Version: 13, exactly one header. A missing or repeated header is 400. A different number is 426 Upgrade Required with Sec-WebSocket-Version: 13, which tells the client what the server speaks.

Proving the server understands WebSocket

The key is not a secret and not a signature. It exists so that a plain HTTP server, a cache or a confused proxy cannot answer a WebSocket handshake by accident. The server must derive one value from the key:

  1. Append the fixed GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 to the key text exactly as received (24 + 36 = 60 characters).
  2. Take the SHA-1 digest of those bytes: 20 bytes.
  3. Base64-encode the digest: 28 characters ending in a single =.

For the key above the result is s3pPLMBiTxaQ9kYGzzhZRbK+xOo=. Note that 60 bytes plus SHA-1's padding spills into a second 64-byte block, so a hash that is only right for short inputs fails here. C, C++ and Rust have no SHA-1 in their standard library, so those starters ship the hash and the encoder, and you wire them up. Python, JavaScript and Go have both built in.

The response bytes

HTTP/1.1 101 Switching Protocols\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n
\r\n

Errors are short and close the connection:

  • 400: HTTP/1.1 400 Bad Request, Content-Length: 0, Connection: close, empty line.
  • 405: HTTP/1.1 405 Method Not Allowed, Allow: GET, Content-Length: 0, Connection: close, empty line.
  • 426: HTTP/1.1 426 Upgrade Required, Sec-WebSocket-Version: 13, Content-Length: 0, Connection: close, empty line.

After the 101 there is no more HTTP. Both sides speak frames (a 2 to 14 byte header, a masking key for client frames, then the payload), which is a protocol of its own. The handshake is the gate.

Common mistakes

  • Hashing the key without the GUID, or hashing its decoded bytes instead of its text.
  • Base64-encoding the hex string of the digest instead of the 20 raw bytes.
  • Comparing Connection to the whole string Upgrade, which rejects keep-alive, Upgrade.
  • A hash that handles only one block of input.
  • Answering 400 for a wrong version, so the client never learns what to retry with.

Your exercise

The starter reads the request, collects the headers, and prints your response bytes. You write the validation and the accept computation.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…