WebSockets и SSE
1) Коротко: Що і для чого
WebSocket (WS/WSS) - апгрейд HTTP-з'єднання до повно-дуплексного каналу. Підходить для чатів, live-ігор, колаборації, двонаправленої телеметрії.
Server-Sent Events (SSE) - односторонній стрім від сервера до браузера (MIME'text/event-stream'). Ідеальний для тікерів, повідомлень, котирувань, прогресу завдань. Клієнт - «EventSource».
- Потрібен введення від клієнта в режимі реального часу (часто і багато) → WebSocket.
- Тільки пуш-оновлення з сервера, сумісність і простота важливіше → SSE.
2) Мережа та протоколи
2. 1 Транспорт і сумісність
WebSocket: стартує як HTTP'GET... Upgrade: websocket` (HTTP/1. 1). Для HTTP/2 можливий RFC 8441 (CONNECT +':protocol = websocket'), підтримка залежить від проксі. Працює поверх TLS (WSS) - обов'язково в проді.
SSE: звичайний тривалий HTTP-відповідь ('200 OK') з потоковою передачею. Чудово йде через HTTP/1. 1/2/3, сумісний з CDN/проксі (якщо не обривають довгі коннекти).
2. 2 Проксі/балансувальники/CDN
Перевірте: підтримка довгоживучих з'єднань, ідла і таймаутів, sticky-сесій (якщо стан на вузлі).
Для WS: увімкніть'proxy _ read _ timeout','upgrade'-заголовки, пер-з'єднання ліміти.
Для SSE: переконайтеся, що проксі не буферизує відповідь (інакше клієнт не побачить події вчасно).
3) Модель повідомлень і управління потоком
WebSocket: кадри текст або бінарні; є'ping/pong', але вбудованого backpressure немає - реалізуйте на додатку (черги, вікна, drop-policy).
SSE: текстові події (UTF-8); клієнт вбудовано вміє reconnect із затримкою; сервер може задавати'retry:`. Є'id:'і заголовок'Last-Event-ID'для відновлення з потрібної позиції.
- Квотуйте вихідні повідомлення per-клієнт.
- Обмежуйте чергу неприсланих подій; при переповненні - відкидати низькопріоритетні/агрегувати.
- Для WS: використовуйте «ковзне вікно» і ACK-схему на рівні програми.
4) Надійність з'єднань
4. 1 Детекція і keepalive
WS: відправляйте'ping'кожні N секунд; розрив по таймауту - reconnect з експоненціальним backoff + джиттер.
SSE: сервер шле «коментарі»':\ n'як heartbeat, щоб з'єднання не вважали idle; клієнт сам перепідключиться.
4. 2 Відновлення потоку
WS: тримайте offset/sequence повідомлень і запитуйте дельту після reconnect.
SSE: використовуйте'id:'для кожної події і'Last-Event-ID'в запиті - сервер шле пропущені події.
5) Автентифікація та авторизація
JWT Bearer в URL запиту (WS) небезпечний (витоки в логах). Використовуйте заголовок (через первинний HTTP-хендшейк) або куки з прапорами'Secure','HttpOnly','SameSite'.
Можливий mTLS (особливо для B2B), а також підписи (HMAC) поверх початкового запиту.
Для SSE з куками пам'ятайте про CORS ('Access-Control-Allow-Origin','Allow-Credentials').
Ротація токена: не обривайте стрім. Передавайте «скоро закінчиться» → клієнт перевідкриє з'єднання з новим токеном.
6) Формат даних і компресія
WebSocket: вмикайте permessage-deflate обережно (CPU); уникайте компресії вже-стиснених форматів (Proto, Avro). Бінарні payload економніше JSON.
SSE: це текст; для великих даних відправляйте посилання на REST/gRPC ресурс або chunk-файли; транспортний gzip для SSE - доречний, але стежте за буферизацією в проксі/CDN.
7) Масштабування та фан-аут
7. 1 Горизонтальне масштабування
Тримайте додаток бездефектним до перезапусків. Стан з'єднань - у фронт-шарі; дані - з брокера.
Sticky (хеш по session/user) потрібен, якщо є локальні черги.
Ідеал - stateless фронт: вузол лише мультиплексує підписки; події приходять із загального pub/sub.
7. 2 Pub/Sub і брокери
Для широкого фан-ауту використовуйте Kafka/NATS/Redis Streams.
Шар «Fanout Gateway» підписується на топіки і пушить клієнтам по WS/SSE.
Застосовуйте ключ маршрутизації (наприклад,'userId','matchId') для балансування навантаження між нодами.
8) Виробничі конфіги
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 (важливо відключити буферизацію)
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) Приклади коду
9. 1 Клієнт SSE (браузер)
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 (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 (браузер)
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) Безпека та лімітування
Rate limiting per-з'єднання і per-користувач: обмежуйте частоту вхідних повідомлень (WS) і швидкість вихідного потоку (WS/SSE).
Quota по часу життя з'єднання і сумарному трафіку.
Message size limit и max messages/sec.
WAF/бот-фільтри на етапі хендшейка; захист від connection flooding (багато коротких конектів).
Ізоляція по tenant/namespace: окремі пули ресурсів.
11) Спостережуваність
Метрики:- `connections_active`, `connections_new_total`, `bytes_in/out`,
- `messages_in/out_total`, `dropped_messages_total`,
- `reconnects_total`, `latency_delivery_ms{p50,p95,p99}`.
- Логи: IP/UA, userId/tenantId, причина закриття ('close _ code'), тривалість.
- Трейсинг: зв'язуйте події з вихідною командою (кореляційні ID); для WS використовуйте «віртуальні» спани по батчах.
12) Експлуатаційні нюанси
TLS-термінація ближче до клієнта (CDN/edge).
Проактивна ротація сполук (graceful) при деплоях: віддайте прапор «перепідключись».
Шардування по ключу, щоб рівномірно розподіляти «гарячі» канали.
Знімки стану для пізніх передплатників (snapshot + delta).
Кеш останнього значення (SSE особливо) - корисно для «холодного» клієнта.
13) Анти-патерни
Стрім великих бінарних шматків через SSE або JSON по WS - використовуйте завантаження по HTTP і посилання в повідомленні.
Авторизація тільки в момент підключення і відсутність пере-перевірок на довгих сесіях.
Глобальний sticky без потреби → дисбаланс і «гарячі» ноди.
Відключені таймаути/ліміти → завислі коннекти з'їдають пул.
Буферизація SSE проксі/CDN → «реальний час» перетворюється на хвилини затримки.
Відсутність sequence/offset → після reconnect клієнт втрачає цілісність даних.
14) Чек-лист впровадження
- Обрано модель: WS (двонаправлено) або SSE (односторонньо).
- Налаштовані таймаути, keepalive і reconnect (backoff + джиттер).
- Спроектовані sequence/offset і (для SSE)'id '/' Last-Event-ID'.
- Визначені ліміти: розмір/швидкість/з'єднання/квоти, захист від DoS.
- Конфіг проксі/інгресу: upgrade, proxy_buffering off (SSE), read/send timeouts.
- Масштабування: pub/sub-брокер, фан-аут-шлюз, sticky тільки при необхідності.
- Автентифікація: безпечна передача токена, ротація без обриву.
- Спостережуваність: метрики, логи, траси; дашборди та алерти.
- План релізів: graceful-drain з'єднань, сигнал клієнту про пере-підключення.
- Game Days: обриви мережі, падіння ноди, перевантаження брокера, довгі RTT.
15) FAQ
Чи можна кешувати SSE через CDN?
Зазвичай ні: це персоналізований потік. Для публічних каналів - можливо з коротким TTL і chunked-delivery, але легко наламати «реальний час».
Чи працює WebSocket поверх HTTP/2/3?
Браузерний WS стартує з HTTP/1. 1-upgrade; є RFC 8441 для h2, підтримка в проксі/сервері потрібна окремо. З h3 - в русі; для стрімінгу в h3 є WebTransport, але це інший API.
gRPC vs WS для браузера?
Чистий браузер не говорить gRPC; потрібен gRPC-Web через Envoy. Для інтерактивних UI часто простіше WS + REST.
16) Підсумки
WebSocket - коли потрібен діалог в реальному часі і компактний двонаправлений канал.
SSE - коли потрібен простий і надійний push від сервера до клієнта, мінімальна складність і автоматичний reconnect.
Успіх в проді - це правильні таймаути і ліміти, відновлення з offset, pub/sub-фан-аут, коректні налаштування проксі і чітка спостережуваність.