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:
- Method GET. Anything else:
405 Method Not AllowedwithAllow: GET. - HTTP/1.1 (the protocol needs
Hostand persistent connections) and exactly oneHostheader. Otherwise400. Upgradecontains the tokenwebsocketandConnectioncontains the tokenupgrade. Both are comma-separated lists, compared case-insensitively, and a header may repeat.Connection: keep-alive, Upgradeis what browsers send. Otherwise400.Sec-WebSocket-Key: exactly one header, and its value is the base64 of a 16-byte nonce. That is 24 characters: 22 characters fromA-Z a-z 0-9 + /followed by==. Otherwise400.Sec-WebSocket-Version: 13, exactly one header. A missing or repeated header is400. A different number is426 Upgrade RequiredwithSec-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:
- Append the fixed GUID
258EAFA5-E914-47DA-95CA-C5AB0DC85B11to the key text exactly as received (24 + 36 = 60 characters). - Take the SHA-1 digest of those bytes: 20 bytes.
- 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
Connectionto the whole stringUpgrade, which rejectskeep-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…