Server-Sent Events (EventSource) #
Server-Sent Events (SSE) — способ получать от сервера поток текстовых событий по одному HTTP-соединению. Клиентский API — класс EventSource: браузер сам переподключается и парсит формат text/event-stream. Подходит для ленты активности, счётчиков на дашборде и статусов сборки — когда клиенту нужно только слушать, без частых ответов серверу.
Базовое подключение #
const feed = new EventSource('/api/dashboard/activity')
feed.onmessage = (event) => {
const row = JSON.parse(event.data)
appendActivityRow(row.user, row.action)
}
feed.onerror = () => {
console.warn('Поток активности прерван, EventSource переподключится')
}
Первый аргумент — URL (обычно same-origin). В отличие от fetch, соединение остаётся открытым: сервер периодически дописывает строки в тело ответа.
Формат события на проводе #
Сервер отправляет текстовые блоки, разделённые пустой строкой:
data: {"trackId":"trk-12","listeners":384}
data: {"trackId":"trk-12","listeners":385}
Поле data может повторяться — строки склеиваются через \n. Необязательные поля:
| Поле | Назначение |
|---|---|
event | Имя типа события (не message) |
id | Идентификатор для заголовка Last-Event-ID при reconnect |
retry | Интервал переподключения в миллисекундах |
Именованные события #
Если сервер шлёт event: order-status, слушайте отдельный тип:
const orders = new EventSource('/api/checkout/stream')
orders.addEventListener('order-status', (event) => {
const { orderId, step } = JSON.parse(event.data)
updateCheckoutStep(orderId, step)
})
orders.addEventListener('ping', () => {
// keep-alive без полезной нагрузки
})
События без поля event попадают в обработчик onmessage.
Закрытие и состояние #
const feed = new EventSource('/api/metrics/live')
// readyState: CONNECTING (0), OPEN (1), CLOSED (2)
document.getElementById('pause-feed').addEventListener('click', () => {
feed.close()
})
После close() переподключения не будет. При ошибке сети EventSource сам восстанавливает соединение (если не вызван close()), отправляя Last-Event-ID, если сервер ранее прислал id.
SSE vs WebSocket vs polling #
| EventSource | WebSocket | Polling | |
|---|---|---|---|
| Направление | Сервер → клиент | Двустороннее | Запрос–ответ |
| Протокол | HTTP | Отдельный WS | HTTP |
| Переподключение | Встроено | Вручную | Каждый tick |
| Бинарные данные | Нет (только текст) | Да | Зависит от ответа |
// polling — лишняя нагрузка каждые N секунд
setInterval(async () => {
const res = await fetch('/api/cart/count')
const { count } = await res.json()
setBadge(count)
}, 5000)
// SSE — сервер пушит при изменении
const cart = new EventSource('/api/cart/stream')
cart.onmessage = (e) => setBadge(JSON.parse(e.data).count)
Для чата или совместного редактирования, где клиент часто пишет в канал, выберите WebSocket. Для ленты логов и метрик SSE часто проще в эксплуатации.
Ограничения #
- Только GET — тело запроса не отправить; авторизацию передают cookie (same-origin) или query-токен по политике сервера.
- Текст — бинарные payload нужно кодировать (Base64) или использовать WebSocket.
- Лимит соединений — в HTTP/1.1 браузер ограничивает число параллельных запросов к одному хосту; для многих потоков предпочтителен HTTP/2.
- CORS — cross-origin SSE возможен, если сервер выставляет заголовки доступа; детали пересекаются с темой CORS.
Итог #
EventSourceоткрывает долгоживущий HTTP-потокtext/event-stream.- События по умолчанию —
onmessage; именованные типы — черезaddEventListener('event-name'). - Браузер переподключается автоматически;
close()останавливает поток. - SSE — односторонний push; для двустороннего обмена используйте WebSocket.
- Предпочитайте SSE polling-у, когда обновления редкие и инициатор — сервер.