Messaging
Part 2 of 6 · WebSockets & MQTTWebSocket Protocol — Handshake, Frames, Ping/Pong & Close Codes
RFC 6455 upgrade is HTTP 101 plus Accept = base64(SHA1(key + GUID)). Frames carry opcodes and masking; ping/pong is the application keepalive. Close 1001 drains deploys; 1006 is an abnormal drop with no close frame.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
How you keep a long-lived socket honest
Prefer
101 + ping shorter than LB idle + close codes
Upgrade is a cryptographic Accept check, not HTTP 200. Application ping/pong proves liveness through NATs. 1001 on shutdown lets clients reconnect to a healthy process.
- Non-101 means stay on HTTP — never treat 200 as success.
- Ping interval must be shorter than the proxy idle timeout.
- Log close code + reason on both ends and correlate with deploys.
Alternative
Open a socket and hope TCP keepalive is enough
Idle proxies drop silent TCP. TCP keepalive is OS-level and often too slow. Missing 1006 handling leaves the UI looking dead with a zombie process.
- Unmasked client frames are a protocol error; browsers close.
- Compression without size limits is a zip-bomb.
- Socket.IO fallbacks change what the LB actually sees.
Happy path, then the half-open
Interviews want the Accept hash, then what happens when the NAT goes quiet.
- 1
GET Upgrade
Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key, Version 13. - 2
101 Switching Protocols
Accept = base64(SHA1(key + RFC GUID)). Subprotocol and extensions negotiate after. - 3
Frames
FIN, opcode, MASK, payload. Client-to-server MUST be masked. - 4
Idle proxy
No ping → silent drop. Next write fails. Depth: ping/pong. - 5
Close 1006 or 1001
1006 = no close frame (reconnect). 1001 = going away (reconnect to a drained pool).
Overview
Learning goal: explain the RFC 6455 upgrade path, frame types, keepalive, and close codes so you can debug half-open sockets, proxy timeouts, and clean deploys.
The WebSocket is not "HTTP with extra headers." After 101, the bytes are frames. Application liveness is ping/pong, not the TCP keepalive you set on the socket. Close codes tell the client whether to reconnect.
You should be able to:
- Compute Accept from the RFC sample key.
- Say why client frames are masked and server frames are not.
- Drain with 1001 instead of killing the process.
Handshake (HTTP → WebSocket)
Client sends GET with Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key, Sec-WebSocket-Version: 13. Server replies 101 Switching Protocols with Sec-WebSocket-Accept = base64(SHA1(key + GUID)). The GUID is the RFC constant 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.
Any non-101 means stay on HTTP. Do not treat 200 as success. Subprotocols (Sec-WebSocket-Protocol) and extensions (permessage-deflate) negotiate after Accept.
Websocket over HTTP/2 (RFC 8441) exists, but browser and LB support vary. Interview answer: the classic path is HTTP/1.1 Upgrade; confirm your edge. Terminate TLS at the edge — L4 vs L7 — and forward clear or re-encrypt to workers. Many L7 proxies buffer HTTP unless configured for streaming/upgrades.
Sequence
- 1
1 Client → 2 L7 LB
1 GET Upgrade websocket
- 2
2 L7 LB → 3 WS server
2 Forward upgrade
- 3
3 WS server → 1 Client
3 101 Switching Protocols
- 4
1 Client → 3 WS server
4 Text frame hello
- 5
3 WS server → 1 Client
5 Text frame ack
- 6
3 WS server → 1 Client
6 Ping
- 7
1 Client → 3 WS server
7 Pong
- 8
3 WS server → 1 Client
8 Close 1001 going away
- 9
1 Client → 2 L7 LB
9 Reconnect with backoff
Lesson map
WebSocket Protocol — Handshake, Frames, Ping/Pong & Close Codes
RFC 6455 upgrade is HTTP 101 plus Accept = base64(SHA1(key + GUID)). Frames carry opcodes and masking; ping/pong is the application keepalive. Close 1001 drains deploys; 1006 is an abnormal drop with no close frame.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB c["1 Client"] lb["2 L7 LB"] s["3 WS server"] c -->|1 GET Upgrade| lb lb -->|2 Forward| s s -->|3 101 Switching| c c -->|4 Text frame| s s -->|5 Text frame ack| c s -->|6 Ping| c
Frames
Each frame has FIN, opcode, MASK bit, payload length, optional masking key, payload.
| Rule | Meaning |
|---|---|
| Client → server | MUST be masked |
| Server → client | MUST NOT be masked |
| Opcode 0x1 | text (UTF-8) |
| Opcode 0x2 | binary |
| Opcode 0x8 | close |
| Opcode 0x9 / 0xA | ping / pong |
| Opcode 0x0 | continuation |
| FIN=0 … FIN=1 | fragmentation of a large message |
Mixing text and binary mid-message is a protocol error. Prefer binary protobuf/msgpack when payloads are large and non-debug. Cap max message size and compression ratio — permessage-deflate without limits is a zip-bomb.
Ping/Pong and half-open detection
Idle proxies and NATs drop silent TCP. Application or library ping/pong proves liveness. Miss N pongs → close and reconnect with backoff.
Do not confuse TCP keepalive with WebSocket ping. TCP keepalive is OS/socket; ping is WS-layer. Interviewers expect both named. Ping interval must be shorter than the LB idle timeout — not the other way around.
Close codes
| Code | Meaning | Reconnect? |
|---|---|---|
| 1000 | Normal closure | Usually no (intentional logout) |
| 1001 | Going away (shutdown / navigate) | Yes — new process |
| 1002 | Protocol error | After a fix, not a tight loop |
| 1006 | Abnormal — no close frame | Yes — network blip, kill -9, bad proxy |
| 1008 | Policy violation | No until the client stops violating |
| 1011 | Server error | Yes, with backoff |
On deploy: send 1001, drain, let clients reconnect to healthy nodes. That pairs with scaling and sticky sessions. Connection draining concepts live in the LB health/drain lesson.
Comparative failure modes
| Miss | What you see |
|---|---|
| Missing ping | Silent zombie sockets until a write fails |
| Treating 200 as upgrade | Browser never opens WS |
| Unmasked client frames | Browser / peer closes |
| Ignoring 1006 | No reconnect; "app feels dead" |
| Compression without size limits | Memory blow / zip-bomb DoS |
L4 can pass TCP; path-based routing and auth cookies usually need L7. Depth: L4 vs L7.
Sandbox: Accept key and reconnect policy (Python)
RFC sample key dGhlIHNhbXBsZSBub25jZQ== must produce Accept s3pPLMBiTxaQ9kYGzzhZRbK+xOo=.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Same idea (TypeScript)
Browsers use the WebSocket API; Accept is computed server-side. This sandbox only models reconnect policy.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Operational checklist
- Negotiate subprotocol explicitly when you have a custom framing schema.
- Ping interval shorter than LB idle timeout.
- Log close code + reason on both ends; correlate with deploy markers.
- Cap max message size and compression ratio.
- Prefer binary when payloads are large and non-debug.
Pitfalls
A client sits idle 90s. The ALB idle timeout is 60s. You never sent a ping. What close code do you expect, if any? Where do you log it? Now drain a canary: which close code, and how do you stagger reconnects so you do not herd the remaining pods?
Interview Q&A
What proves a successful upgrade?
Answer
HTTP 101 plus Sec-WebSocket-Accept matching the SHA1 of the client key concatenated with the RFC GUID, then base64. A 200 is a failed upgrade.
Why mask client frames?
Answer
To mitigate proxy cache poisoning. Servers must reject unmasked client frames. Server-to-client frames must not be masked.
Ping vs TCP keepalive?
Answer
Ping is WebSocket-layer and under your control. TCP keepalive is OS/socket, often with long defaults. Both useful; different timeouts. Ping must beat the proxy idle timer.
What is close code 1006?
Answer
Abnormal closure — no close frame received. Network drop, kill -9, or a bad proxy. Reconnect with backoff. Contrast 1000 (intentional) and 1001 (going away).
Why drain with 1001?
Answer
It signals going away so clients reconnect instead of hanging on a dying process. Pair with jittered backoff and connection draining.
Text vs binary opcode?
Answer
0x1 is UTF-8 text; 0x2 is opaque bytes. Mixing mid-message is a protocol error. Fragment with continuation frames and FIN on the last.
Can L4 LB terminate WebSocket?
Answer
L4 can pass TCP. Path-based routing, cookie auth, and protocol-aware idle timeouts usually need L7. Depth: L4 vs L7 proxies.
Socket.IO caveat?
Answer
Fallback transports and custom packet framing. It may start with polling then upgrade. Load-test the real stack, not raw WebSocket alone.
What is Sec-WebSocket-Protocol?
Answer
Optional subprotocol negotiation (for example a custom JSON schema vs protobuf). If you send one, the server must pick a listed value or the client closes.
Why cap permessage-deflate?
Answer
A tiny compressed frame can explode in memory. Size limits and compression-ratio limits are DoS controls, not premature optimization.
Does HTTP/2 change the handshake?
Answer
RFC 8441 bootstraps WebSocket over HTTP/2, but classic interviews still start at HTTP/1.1 Upgrade. Confirm browser, CDN, and LB support before betting the design on it.