Logo GH

WebSockets и SSE

1) Short: what and for what

WebSocket (WS/WSS) - HTTP connection upgrade to full duplex channel. Suitable for chats, live games, collaboration, bidirectional telemetry.
Server-Sent Events (SSE) - one-way stream from server to browser (MIME 'text/event-stream'). Ideal for tickers, notifications, quotes, task progress. Client - 'EventSource'.

Selection rule:
  • You need input from the client in real time (often a lot) → WebSocket.
  • Only push updates from the server, compatibility and simplicity are more important than SSE →.

2) Network and protocols

2. 1 Transport and compatibility

WebSocket: Starts as HTTP 'GET... Upgrade: websocket` (HTTP/1. 1). For HTTP/2, RFC 8441 (CONNECT + ': protocol = websocket') is possible, support depends on the proxy. Works on top of TLS (WSS) - mandatory in sales.
SSE: normal long HTTP response ('200 OK') with streaming. It goes well through the HTTP/1. 1/2/3, compatible with CDN/proxy (if long connections are not terminated).

2. 2 Proxies/balancers/CDN

Check: support for long-lived connections, idles and timeouts, sticky sessions (if the state is on the node).
For WS: include 'proxy _ read _ timeout', 'upgrade' headers, per-connection limits.
For SSE, make sure the proxy does not buffer the response (otherwise the client will not see the events in time).

3) Message model and flow control

WebSocket: text or binary frames; there is a'ping/pong', but there is no built-in backpressure - implement on the application (queues, windows, drop-policy).
SSE: text events (UTF-8); the client is able to reconnect with a delay built in; server can specify'retry: '. There is an'id:' and a 'Last-Event-ID' header to resume from the desired position.

Backpressure (general practices):
  • Quote outgoing messages per-client.
  • Limit the queue of unsent events; on overflow - discard low priority/aggregate.
  • For WS, use a sliding window and an application-level ACK.

4) Reliability of connections

4. 1 Detection and keepalive

WS: send 'ping' every N seconds; timeout gap - reconnect with exponential backoff + jitter.
SSE: the server sends "comments" ':\n' as heartbeat so that the connection does not count idle; the client will reconnect itself.

4. 2 Flow recovery

WS: keep offset/sequence messages and request delta after reconnect.
SSE: use 'id:' for each event and 'Last-Event-ID' in the request - the server sends missed events.

5) Authentication and authorization

JWT Bearer in request URL (WS) is insecure (leaks in logs). Use a header (via the primary HTTP handshake) or cookies with the flags' Secure ',' HttpOnly ',' SameSite '.
mTLS (especially for B2B) is possible, as well as signing (HMAC) over the original request.
For SSE with cookies, remember about CORS ('Access-Control-Allow-Origin', 'Allow-Credentials').

Token rotation: Don't cut off the stream. Pass "will expire soon" → the client will reopen the connection with the new token.

6) Data format and compression

WebSocket: enable permessage-deflate carefully (CPU); avoid compression of already compressed formats (Proto, Avro). Binary payload is more economical than JSON.
SSE: this is text; for large data, send a link to a REST/gRPC resource or chunk files; transport gzip for SSE - appropriate, but watch for buffering in proxy/CDN.

7) Scaling and fan-out

7. 1 Horizontal scaling

Keep the app defect-free for restarts. Connection status - in the front layer; data - from the broker.
Sticky (hash by session/user) is needed if there are local queues.
The ideal is the stateless front: the node only multiplexes subscriptions; events come from a common pub/sub.

7. 2 Pub/Sub and Brokers

For a wide fan-out, use Kafka/NATS/Redis Streams.
The "Fanout Gateway" layer subscribes to topics and fluffs clients via WS/SSE.
Use a routing key (for example, 'userId', 'matchId') to balance the load between nodes.

8) Production configs

8. 1 NGINX — WebSocket

nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; }

server {
listen 443 ssl http2;
server_name ws. example. com;

location /ws {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1. 1;
proxy_read_timeout 75s; # increase for long sessions proxy_send_timeout 15s;
proxy_pass http://ws-backend;
}
}

8. 2 NGINX - SSE (important to disable buffering)

nginx location /events {
proxy_http_version 1. 1;
proxy_set_header Connection "";
proxy_buffering off; # is critical for proxy_cache off threads;
chunked_transfer_encoding on;
proxy_read_timeout 60m;
proxy_pass http://sse-backend;
}

8. 3 Kubernetes (Ingress Annotations, NGINX Ingress)

yaml metadata:
annotations:
nginx. ingress. kubernetes. io/proxy-read-timeout: "3600"
nginx. ingress. kubernetes. io/proxy-send-timeout: "3600"
nginx. ingress. kubernetes. io/enable-websocket: "true"
nginx. ingress. kubernetes. io/proxy-buffering: "off"  # для SSE

9) Code examples

9. 1 SSE client (browser)

js const es = new EventSource("/events? channel=odds", { withCredentials: true });

es. addEventListener("message", (e) => {
const data = JSON. parse(e. data);
renderOdds(data);
});

es. addEventListener("error", () => {
//EventSource will reconnect itself; can be shown spinner
});

9. 2 SSE server (Node. js/Express)

js app. get('/events', (req, res) => {
res. writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res. write ('retry: 3000\n\n') ;//3s backoff to client

const sub = subscribe(req. query. channel, (event) => {
res. write(`id: ${event. id}\n`);
res. write(`event: message\n`);
res. write(`data: ${JSON. stringify(event. payload)}\n\n`);
});

req. on('close', () => sub. unsubscribe());
});

9. 3 WebSocket client (browser)

js const ws = new WebSocket("wss://ws. example. com/ws");
ws. onopen = () => ws. send(JSON. stringify({ type: "join", room: "chat-1" }));
ws. onmessage = (m) => handle(JSON. parse(m. data));
ws. onclose = () => scheduleReconnect();

10) Security and limiting

Rate limiting per-connection and per-user: limit the rate of incoming messages (WS) and the rate of outgoing flow (WS/SSE).
Quota by connection lifetime and total traffic.
Message size limit и max messages/sec.
WAF/bot filters at the handshake stage; protection against connection flooding (many short connections).
Tenant/namespace isolation: individual resource pools.

11) Observability

Metrics:
  • `connections_active`, `connections_new_total`, `bytes_in/out`,
  • `messages_in/out_total`, `dropped_messages_total`,
  • `reconnects_total`, `latency_delivery_ms{p50,p95,p99}`.
  • Logs: IP/UA, userId/tenantId, reason for closing ('close _ code'), duration.
  • Tracing: associate events with the original command (correlation IDs); for WS, use "virtual" butch spans.

12) Operational nuances

TLS termination closer to the client (CDN/edge).

Proactive rotation of connections (graceful) during depleys: give the flag "reconnect."

Sharding on the key to evenly distribute the "hot" channels.
Status snapshots for late subscribers (snapshot + delta).
Last value cache (SSE especially) - useful for a cold client.

13) Anti-patterns

Stream large binary chunks via SSE or JSON over WS - use HTTP download and link in the message.
Authorization only at the time of connection and the absence of re-checks for long sessions.
Global sticky needlessly → imbalance and hot nodes.
Disabled timeouts/limits → frozen connections eat up the pool.
SSE proxy/CDN buffering → "real time" becomes minutes of delay.
Lack of sequence/offset → after reconnect, the client loses data integrity.

14) Implementation checklist

  • WS (bidirectional) or SSE (one-sided) is selected.
  • Configured timeouts, keepalive and reconnect (backoff + jitter).
  • Designed sequence/offset and (for SSE) 'id '/' Last-Event-ID'.
  • Size/speed/connections/quotas, DoS protection defined.
  • Proxy/ingress config: upgrade, proxy_buffering off (SSE), read/send timeouts.
  • Scaling: pub/sub broker, fan-out gateway, sticky only if needed.
  • Authentication: secure token transfer, no-break rotation.
  • Observability: metrics, logs, traces; dashboards and alerts.
  • Release plan: graceful-drain connections, signal the client to reconnect.
  • Game Days: network breaks, node drops, broker overload, long RTTs.

15) FAQ

Can SSE be cached via CDN?

Usually not: It's a personalized flow. For public channels - possibly with short TTL and chunked-delivery, but it is easy to break "real time."

Does WebSocket work on top of HTTP/2/3?
Browser WS starts with HTTP/1. 1-upgrade; there is RFC 8441 for h2, support in the proxy/server is required separately. With h3 - in motion; for streaming, h3 has WebTransport, but it is a different API.

gRPC vs WS for Browser?
A clean browser does not say gRPC; need gRPC-Web via Envoy. For interactive UIs, WS + REST is often easier.

16) Totals

WebSocket - when you need real-time dialogue and a compact bidirectional channel.
SSE - when you need a simple and reliable push from server to client, minimal complexity and automatic reconnect.
Success in sales is correct timeouts and limits, recovery from offset, pub/sub fan-out, correct proxy settings and clear observability.

Contact

Get in Touch

Reach out with any questions or support needs.We are always ready to help!

Telegram
@Gamble_GC
Start Integration

Email is required. Telegram or WhatsApp — optional.

Your Name optional
Email optional
Subject optional
Message optional
Telegram optional
@
If you include Telegram — we will reply there as well, in addition to Email.
WhatsApp optional
Format: +country code and number (e.g., +380XXXXXXXXX).

By clicking this button, you agree to data processing.