Розробка Server-Sent Events (SSE) для веб-додатку
Server-Sent Events (SSE) — технологія для відправки подій від сервера до клієнта через HTTP-з'єднання. Односпрямована: сервер → клієнт. Простіша за WebSocket для задач, де не потрібна двонаправлена зв'язок: сповіщення, прогрес довгих операцій, real-time стрічки.
Переваги SSE перед WebSocket
- Сумісність HTTP/1.1 — SSE працює через простий HTTP без upgrade
-
Автоматичне переподключення — браузер сам переподключається при обриві (через поле
retry) - Стандартні заголовки — Authorization, cookies працюють без зусиль
- Proxy-friendly — звичайний HTTP, менше проблем із корпоративними проксі
- Немає CORS preflight для GET-запитів
Формат SSE
Відповідь сервера — text/event-stream з полями data:, event:, id:, retry::
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"type":"notification","message":"Нове замовлення"}
event: order_update
data: {"orderId":"123","status":"shipped"}
id: msg_456
retry: 3000
: comment (ігнорується клієнтом)
Реалізація сервера (Node.js + Express)
app.get('/api/events', (req, res) => {
const userId = req.user.id;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // для Nginx: відключити буферизацію
});
// Відправляємо перший пакет одразу (обхід nginx буферизації)
res.write(':ok\n\n');
// Додаємо клієнта до реєстру
const clientId = nanoid();
clients.set(clientId, { res, userId });
// Heartbeat кожні 15 секунд
const heartbeat = setInterval(() => {
res.write(': ping\n\n');
}, 15000);
req.on('close', () => {
clearInterval(heartbeat);
clients.delete(clientId);
});
});
// Відправка события конкретному користувачу
function sendToUser(userId: string, event: string, data: object) {
clients.forEach(({ res, userId: uid }) => {
if (uid === userId) {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
});
}
// Broadcast всім
function broadcast(event: string, data: object) {
clients.forEach(({ res }) => {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
}
Клієнт (браузер)
const eventSource = new EventSource('/api/events', { withCredentials: true });
// Дефолтні события (event: без імені)
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log('Message:', data);
};
// Іменовані события
eventSource.addEventListener('order_update', (e) => {
const order = JSON.parse(e.data);
updateOrderStatus(order.orderId, order.status);
});
eventSource.addEventListener('notification', (e) => {
showNotification(JSON.parse(e.data).message);
});
// Обробка помилок
eventSource.onerror = (e) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('З\'єднання закрито, автоматичне переподключення...');
}
};
Масштабування з Redis Pub/Sub
Кілька серверів — Redis Pub/Sub для розподіленої доставки подій (як WebSocket):
sub.subscribe('user:events', (message) => {
const { userId, event, data } = JSON.parse(message);
sendToUser(userId, event, data);
});
// З будь-якого сервісу
await pub.publish('user:events', JSON.stringify({
userId: 'user_123',
event: 'payment_completed',
data: { amount: 5000 }
}));
Обмеження SSE
- Лише сервер → клієнт — клієнт не може відправити дані через SSE (лише нові EventSource-запити або окремий API-запит)
- Ліміт з'єднань у HTTP/1.1 — браузер обмежує 6 з'єднань на домен. SSE займає одне. Рішення: HTTP/2 (один мультиплексований потік).
-
IE не підтримує — полібіл
eventsourceдля старих браузерів
Практичні кейси
- Прогрес довгих операцій (імпорт файлу, генерація звіту)
- Real-time сповіщення у ЛК
- Оновлення лічильників (нові повідомлення, замовлення)
- Live-стрічка новин або спортивних результатів
Терміни
SSE-ендпоінт з аутентифікацією, heartbeat, Redis-масштабування: 3–5 днів. З типізованими событиями, client-side хуком (useSSE), інтеграцією сповіщень: 1–2 тижні.







