Real-Time Backend

Server-Sent Events

Цели урока

  • Понимать как SSE работает поверх HTTP (chunked encoding, text/event-stream)
  • Использовать четыре поля формата: data, event, id, retry
  • Строить SSE-сервер на Node.js с авто-реконнектом и Last-Event-ID
  • Выбирать между SSE, WebSocket и polling исходя из требований

Предварительные знания

  • HTTP and Its Limitations

2013 год. Twitter переводит live-ленту с polling на SSE - нагрузка на серверы падает в 10 раз. Один HTTP-запрос вместо тысяч повторных, и новые твиты появляются мгновенно. 2022 год: ChatGPT использует SSE для эффекта печатания - каждый токен летит отдельным событием. SSE - первая настоящая push-технология, встроенная прямо в браузер.

  • Facebook/Twitter - live-ленты и уведомления через SSE
  • GitHub - стриминг логов CI/CD в реальном времени
  • Финансовые платформы - обновления котировок и цен
  • ChatGPT - стриминг ответов модели (эффект печатания через SSE)

Ian Hickson и HTML5 EventSource

Ян Хиксон, главный редактор спецификации HTML5 в WHATWG, добавил Server-Sent Events в черновик HTML5 в 2006 году. До этого единственным способом получать события от сервера был polling или long polling - обходные решения поверх HTTP. Хиксон хотел дать браузеру нативный механизм push без WebSocket (который был отдельной спецификацией). В 2014 году SSE вошёл в финальный стандарт W3C как самостоятельная спецификация.

SSE: Push поверх обычного HTTP

2013 год. Twitter переводит live-ленту с polling на SSE - нагрузка на серверы падает в 10 раз. Один HTTP-запрос вместо тысяч повторных. ChatGPT в 2022 использует SSE для эффекта печатания - каждый токен прилетает отдельным событием. Это и есть SSE: сервер отправляет события клиенту, а не наоборот.

**Server-Sent Events (SSE)** - стандартный механизм W3C, при котором сервер отправляет события клиенту через обычное HTTP-соединение. Клиент делает один GET-запрос, сервер держит соединение открытым и шлёт данные по мере появления.

SSE работает поверх HTTP/1.1 chunked transfer encoding. Никакого специального протокола - обычный HTTP. Поэтому SSE проходит через любые прокси и файрволы без дополнительной настройки.

  • **Однонаправленный:** только сервер -> клиент (unidirectional)
  • **Content-Type:** text/event-stream
  • **Транспорт:** обычный HTTP/1.1, chunked encoding
  • **Автореконнект:** браузер сам переподключается при обрыве
  • **Встроен в браузер:** EventSource API, никаких библиотек

В отличие от polling, где клиент забрасывает сервер запросами, SSE - это **настоящий push**. Сервер решает, когда отправить данные. Соединение устанавливается один раз и живёт до тех пор, пока одна из сторон не закроет его.

SSE - это двусторонний канал связи, как WebSocket

SSE - строго однонаправленный: только сервер может отправлять данные клиенту. Для отправки данных от клиента нужен отдельный HTTP-запрос

Какой Content-Type используется для SSE?

Формат text/event-stream

SSE-ответ под капотом - обычный текст с простыми правилами форматирования. Никакого бинарного протокола, никаких handshake-ов. Это один из главных плюсов: SSE легко отлаживать в браузере прямо в Network tab.

Каждое событие - набор полей, разделённых двойным переводом строки (`\n\n`). Четыре поля формата:

ПолеНазначениеПример
data:Данные события (обязательное)data: {"price": 100}
event:Имя события (по умолчанию "message")event: notification
id:Уникальный ID для восстановления после обрываid: 42
retry:Время переподключения в мсretry: 3000

Каждое событие **обязательно** заканчивается двойным `\n\n`. Одинарный `\n` разделяет поля внутри одного события. Без двойного перевода строки браузер не распознает границу события.

Что произойдёт, если не указать поле `event:` в SSE-событии?

EventSource API в браузере

Сервер готов. Подключаемся из браузера через встроенный **EventSource** API - без npm install, без webpack, без ничего. Это один из редких случаев когда платформа даёт готовый инструмент.

readyStateЗначениеОписание
CONNECTING0Подключение или переподключение
OPEN1Соединение активно, данные поступают
CLOSED2Соединение закрыто, переподключение не будет

**Автореконнект и Last-Event-ID** - главная суперсила SSE. При обрыве соединения браузер автоматически переподключается и отправляет заголовок `Last-Event-ID` с последним полученным `id:`. Сервер может использовать это, чтобы дослать пропущенные события.

Интервал переподключения по умолчанию зависит от браузера (обычно 3-5 секунд). Сервер может переопределить его полем `retry: 5000` (в миллисекундах).

После вызова source.close() можно снова открыть это же соединение

EventSource после close() переходит в состояние CLOSED навсегда. Для нового подключения нужно создать новый объект EventSource

Что произойдёт при обрыве SSE-соединения, если сервер отправлял поле `id:` в каждом событии?

Ограничения SSE и когда его использовать

SSE - производительный инструмент, но у него есть чёткие границы применимости. Понимание этих границ - ключ к правильному выбору технологии.

  • **Однонаправленный:** только сервер -> клиент. Для отправки данных клиентом нужен отдельный POST/PUT
  • **Лимит 6 соединений** на домен в HTTP/1.1 (ограничение браузера, не протокола). В HTTP/2 этот лимит снят через мультиплексирование
  • **Только текст:** бинарные данные (картинки, аудио) передавать нельзя. Можно Base64, но это +33% к размеру
  • **Нет заголовка Authorization** в конструкторе EventSource. Аутентификация - через cookie или query-параметры
  • **Не работает в Service Workers** (ограничение API, не протокола)
КритерийPollingLong PollingSSE
НаправлениеКлиент -> СерверКлиент -> СерверСервер -> Клиент
ЗадержкаИнтервал опросаНизкаяМинимальная (реальный push)
Нагрузка на серверВысокая (много запросов)СредняяНизкая (одно соединение)
АвтореконнектНужно вручнуюНужно вручнуюВстроен в браузер
Бинарные данныеДаДаНет (только текст)
Двусторонний обменДа (новый запрос)Да (новый запрос)Нет

Когда SSE - идеальный выбор

  • SSE подходит — Live-ленты новостей и соцсетей, уведомления (push notifications в браузере), дашборды с метриками в реальном времени, стриминг логов, обновления цен и котировок, прогресс длительных операций (загрузка, обработка), стриминг ответов LLM
  • SSE не подходит — Чаты и мессенджеры (нужен двусторонний обмен), онлайн-игры (нужна низкая задержка в обе стороны), видео/аудио стриминг (бинарные данные), collaborative editing вроде Google Docs

В HTTP/2 лимит 6 соединений на домен **не актуален** - все SSE-потоки мультиплексируются в одном TCP-соединении. Если сервер поддерживает HTTP/2, ограничение соединений снимается.

Для какого сценария SSE подходит лучше всего?

Ключевые идеи SSE

  • SSE - однонаправленный push от сервера к клиенту поверх обычного HTTP
  • Формат text/event-stream: поля data:, event:, id:, retry: разделённые \n\n
  • EventSource API - встроенный в браузер, с авто-реконнектом и Last-Event-ID
  • Лимит 6 соединений на домен в HTTP/1.1 (снят в HTTP/2)
  • Идеален для live-фидов, уведомлений, дашбордов, стриминга LLM-ответов

Связь с другими темами

SSE - первый шаг к настоящему real-time. Следующий - WebSocket, который добавит двусторонний канал.

  • WebSocket — Следующий шаг - полнодуплексный протокол для двустороннего обмена
  • HTTP/2 Server Push — Альтернативный push-механизм на уровне протокола
  • Long Polling — Предшественник SSE - эволюция push через polling

Вопросы для размышления

  • Почему разработчики ChatGPT выбрали SSE для стриминга ответов, а не WebSocket?
  • Как реализовать аутентификацию SSE-соединения в production-приложении?
  • В каких сценариях хранение last-event-id и replay пропущенных событий критичны?

Связанные уроки

  • rt-02-http-limits — Polling и long polling - фон для понимания зачем нужен SSE
  • rt-04 — WebSocket - следующий шаг: двунаправленный канал
  • rt-05 — HTTP/2 Server Push - альтернативный push на уровне протокола
  • rt-11 — Streaming ответов LLM (ChatGPT эффект печатания) через SSE
  • stream-03 — Message brokers как источник событий для SSE endpoint
  • rt-06 — WebRTC для случаев когда SSE недостаточно
  • net-22-http-headers
  • net-21-http-basics
Server-Sent Events

0

1

Войти