5 min read #networking #HTTP
On this page

WebSocket and SSE

The HTTP request-response model is not suitable for real-time scenarios—WebSocket upgrades to a full-duplex channel, while SSE maintains simplicity with a unidirectional stream. The choice depends on "whether the server needs to push proactively" and "whether binary frames are required."

Overview

WebSocket (RFC 6455, 2011) creates a persistent bidirectional TCP channel on top of the HTTP handshake—browsers and servers can send messages to each other at any time. This solves the "request-response" unidirectional limitation of HTTP, enabling real-time applications such as chat, games, live streaming bullet comments, and Grafana Live. SSE (Server-Sent Events) is a simpler unidirectional alternative: the server pushes an event stream to the client, based on plain HTTP, with better firewall compatibility. Both have heartbeat mechanisms (ping/pong vs. SSE's automatic reconnection) to keep long-lived connections alive in NAT/proxy environments.

WebSocket Upgrade

Client → Server: GET /ws HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==   ← random, base64
  Sec-WebSocket-Version: 13
  Sec-WebSocket-Protocol: chat                    ← optional subprotocol

Server → Client: HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  ← SHA1(Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
  Sec-WebSocket-Protocol: chat

  → Handshake complete → TCP connection upgraded to bidirectional WebSocket

WebSocket Frame

 0                   1                   2                   3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |        (16/64)              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Masking-key (if MASK set, 4 bytes)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Payload Data (variable, masked if from client)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

FIN(1): 1 = this is the final frame of the message
opcode(4): 0=continuation, 1=text(UTF-8), 2=binary, 8=close, 9=ping, 10=pong
MASK(1): client→server must mask, server→client must not mask

Masking (client → server):
  1. Client: randomly generate a 4-byte masking key
  2. For each byte of the payload: payload[i] = original[i] XOR key[i % 4]
  3. Purpose: prevent cache poisoning by intermediate proxies (an attacker forces the browser to send crafted WS payloads
     → proxy misinterprets them as HTTP responses → corrupts cache)

Close frame (opcode=8):
  [status code (2 bytes, big-endian)][reason (UTF-8, optional)]
  1000: normal closure
  1001: going away (server shutdown, page navigated away)

Ping/Pong (Heartbeat)

Ping frame (opcode=9): optional payload (application-specific)
Pong frame (opcode=10): must copy the ping's payload

→ Application-layer heartbeat (distinct from TCP keepalive)
→ Detection: dead connections (even if TCP is still alive, the remote app may be hung)

Proxy/server timeout: nginx proxy_read_timeout applies to WebSocket →
  Without ping/pong, the connection may be timed out and disconnected by the proxy

SSE (Server-Sent Events)

Unidirectional (server → client) stream, based on HTTP chunked encoding:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

id: 42                         ← last event ID (auto-reconnect from this ID)
event: alert                   ← optional event type (otherwise defaults to "message")
data: {"status":"firing"}      ← event data

data: multi-line data
data: second line
                               ← empty line → dispatch event

Client API: EventSource (built-in HTML5):
  const es = new EventSource("/events");
  es.onmessage = (e) => { console.log(e.data); };
  es.addEventListener("alert", (e) => { ... });

WebSocket vs SSE

WebSocketSSE
DirectionBidirectionalServer → Client
ProtocolWebSocket (upgrade)HTTP chunked
Binary dataNative (opcode=2)Requires base64 encoding
Firewall compatibilityPoor (some proxies do not support upgrade)Good (plain HTTP)
Automatic reconnectionManualAutomatic via EventSource (since last-event-id)
Suitable forChat, games, real-time collaborationNotifications, feeds, real-time data push

References

  • RFC: 6455
  • SSE: html.spec.whatwg.org/multipage/server-sent-events.html
  • WS Testing: websocat (CLI WebSocket client)

Keywords: WebSocket, upgrade, frame, masking, ping/pong, SSE, EventSource, heartbeat