Урок по треку Сети и HTTP 6 мин

WebSocket, SSE и polling

Long polling, Server-Sent Events и WebSocket: как работают и что выбрать под задачу.

Задача

HTTP устроен как «клиент спросил, сервер ответил». А чату, бирже, уведомлениям нужно, чтобы сервер сам сообщил о новом событии. Есть четыре подхода.

Short polling

Клиент опрашивает сервер по таймеру:

setInterval(async () => {
  const res = await fetch('/api/messages?after=' + lastId);
  render(await res.json());
}, 5000);

Просто и работает везде, но задержка до интервала и куча пустых запросов.

Long polling

Клиент шлёт запрос, сервер держит его открытым, пока не появится событие (или таймаут ~30 с), отвечает, клиент сразу шлёт новый. Задержка маленькая, но на каждое событие новый HTTP-запрос с заголовками.

Server-Sent Events (SSE)

Однонаправленный поток от сервера к клиенту поверх обычного HTTP. Сервер отвечает Content-Type: text/event-stream и не закрывает ответ:

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

id: 17
event: price
data: {"ticker":"SBER","price":301.5}

Каждое событие заканчивается пустой строкой. На клиенте:

const es = new EventSource('/api/stream');
es.addEventListener('price', (e) => console.log(JSON.parse(e.data)));

Сильные стороны SSE:

  • встроенный автореконнект; после переподключения браузер шлёт Last-Event-ID, и сервер может дослать пропущенное;
  • работает через обычные прокси и HTTP/2;
  • куки шлются как в обычном запросе.

Ограничения: только текст, только сервер → клиент. EventSource не умеет ставить свои заголовки (например Authorization), поэтому авторизуют кукой. На HTTP/1.1 браузер держит не больше 6 соединений на домен, и каждая вкладка с SSE съедает одно. На HTTP/2 потоки мультиплексируются, лимит не мешает.

WebSocket

Двунаправленный канал поверх одного TCP-соединения. Начинается с HTTP-запроса на upgrade:

GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Сервер отвечает 101 Switching Protocols, и дальше по соединению ходят фреймы (текст или бинарные) в обе стороны. Схемы ws:// и wss:// (через TLS, в проде только она).

const ws = new WebSocket('wss://app.ru/chat');
ws.onopen = () => ws.send(JSON.stringify({ type: 'join', room: 1 }));
ws.onmessage = (e) => console.log(e.data);
ws.onclose = (e) => scheduleReconnect(e.code);

Что надо сделать руками:

  • реконнект с экспоненциальной задержкой и джиттером;
  • heartbeat (ping/pong), чтобы заметить мёртвое соединение и не дать прокси закрыть его по простою;
  • досылку пропущенного после переподключения;
  • проверку Origin при handshake: CORS на WebSocket не распространяется, и чужой сайт может открыть соединение с куками пользователя (атака CSWSH).

Масштабирование

Соединения долгоживущие и привязаны к конкретному инстансу. Если пользователь A подключён к серверу 1, а B к серверу 2, сообщение надо переслать между ними: обычно через Redis pub/sub или брокер. Балансировщику нужна поддержка upgrade и большие таймауты.

Что выбрать

ЗадачаВыбор
уведомления, лента, прогресс задачи, стриминг ответаSSE
чат, совместное редактирование, игрыWebSocket
редкие обновления, простота важнееpolling

Что спросят на собесе

  • Чем WebSocket отличается от SSE и long polling?
  • Как устанавливается WebSocket-соединение?
  • Что делать при обрыве соединения?
  • Как масштабировать WebSocket на несколько серверов?
  • Почему для уведомлений часто хватает SSE?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.