Why architecture matters here
WebSocket failures come from ignoring HTTP and TCP realities. Proxies buffer streaming; load balancers cut idle connections; browsers throttle background sockets. Without heartbeat, half-open connections linger. Without backpressure, one slow client bloats server memory.
The architecture matters because you must decide at each layer: framing format, subprotocol, LB stickiness, deploy drain, and reconnect policy.
With the pieces mapped, WebSocket becomes reliable enough for money-critical products.
The architecture: every piece explained
The top strip is the connection setup. Client sends an HTTP 1.1 upgrade request. Server accept returns 101 Switching Protocols. Framing uses opcodes (text, binary, ping, pong, close) with a mask on client-to-server. Subprotocol declares the app-level format (JSON, MessagePack, custom).
The middle row is the resilience machinery. Heartbeat uses ping/pong to detect dead peers. Backpressure requires application-level flow control since WebSocket has no window. Reconnect uses exponential backoff with jitter and optionally resumes state. TLS + origin check mandates wss:// and validates Origin header.
The lower rows are ops. Load balancer must support long-lived connections and either sticky sessions or session-aware routing. Observability tracks connection count, message rate, and error codes. Ops covers connection limits, drain on deploy, and client retry policy.
End-to-end flow
End-to-end: client connects to wss://chat.example.com; upgrade succeeds. Ping every 30s; pong response confirms liveness. Messages flow text-framed as JSON per the subprotocol. Client's network hiccups; connection drops; client's exponential backoff reconnects after 2s, resuming from last received message ID. Server deploys; sends close frames to existing connections; LB drains gracefully; clients reconnect to new backends. Observability shows connection churn during deploy; error rate baseline.