← Назад к вопросам
Как используешь Server-Side Events?
2.0 Middle🔥 201 комментариев
#Браузер и сетевые технологии
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Server-Sent Events (SSE) в веб-приложениях
Что такое SSE
Server-Sent Events (SSE) — это веб-технология, которая позволяет серверу отправлять сообщения клиенту в реальном времени через обычное HTTP соединение. Это альтернатива WebSocket'ам для uni-directional коммуникации (только сервер -> клиент).
Когда использовать SSE
SSE лучше, чем WebSocket'ы когда:
- Нужны только push-сообщения от сервера
- Требуется автоматическое переподключение
- Нужна поддержка старых браузеров
- Простая структура сообщений
WebSocket'ы лучше, когда:
- Нужна двусторонняя коммуникация
- Требуется очень низкая задержка
- Большой объем данных
1. Базовое использование SSE
// Клиент: подключение к SSE
const eventSource = new EventSource('/api/stream');
// Слушание сообщений
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('Получено сообщение:', data);
});
// Закрытие соединения
eventSource.close();
2. Серверная часть (Node.js/Express)
app.get('/api/stream', (req, res) => {
// Устанавливаем правильные заголовки
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Отправляем сообщение сразу
res.write('data: Вы подключились к stream\n\n');
// Отправляем сообщение каждые 5 секунд
const intervalId = setInterval(() => {
const data = {
timestamp: new Date(),
message: 'Текущее сообщение'
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 5000);
// Очистка при отключении
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
3. Использование именованных событий
// Сервер: отправка разных типов событий
app.get('/api/notifications', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Событие "online_users"
res.write(`event: online_users\n`);
res.write(`data: {"count": 5}\n\n`);
// Событие "new_message"
res.write(`event: new_message\n`);
res.write(`data: {"from": "John", "text": "Hello"}\n\n`);
// Событие "status_update" с id для восстановления
res.write(`id: msg-123\n`);
res.write(`event: status_update\n`);
res.write(`data: {"status": "connected"}\n\n`);
});
// Клиент: слушание разных событий
const eventSource = new EventSource('/api/notifications');
eventSource.addEventListener('online_users', (event) => {
const data = JSON.parse(event.data);
console.log(`Онлайн пользователей: ${data.count}`);
updateUserCount(data.count);
});
eventSource.addEventListener('new_message', (event) => {
const data = JSON.parse(event.data);
console.log(`Новое сообщение от ${data.from}: ${data.text}`);
showNotification(data);
});
eventSource.addEventListener('status_update', (event) => {
const data = JSON.parse(event.data);
console.log(`Статус: ${data.status}`);
});
4. Обработка ошибок и переподключение
// Клиент
const eventSource = new EventSource('/api/stream');
// Обработка ошибок
eventSource.addEventListener('error', (event) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('Соединение закрыто');
// Не будет автоматического переподключения
} else if (eventSource.readyState === EventSource.CONNECTING) {
console.log('Попытка переподключиться...');
// Автоматическое переподключение (через 5 сек по умолчанию)
}
});
// Явное управление переподключением
let reconnectInterval = 1000;
let maxReconnectInterval = 30000;
function connect() {
const eventSource = new EventSource('/api/stream');
eventSource.addEventListener('open', () => {
console.log('Соединение установлено');
reconnectInterval = 1000; // Сброс интервала при успехе
});
eventSource.addEventListener('message', (event) => {
console.log('Сообщение:', event.data);
});
eventSource.addEventListener('error', () => {
eventSource.close();
// Экспоненциальная задержка для переподключения
setTimeout(() => {
reconnectInterval = Math.min(reconnectInterval * 2, maxReconnectInterval);
connect();
}, reconnectInterval);
});
}
connect();
5. SSE для live-обновлений данных
// Сервер: отправка обновлений цены акции
app.get('/api/stock-price/:ticker', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const ticker = req.params.ticker;
const intervalId = setInterval(() => {
// Симуляция цены
const price = (Math.random() * 200 + 50).toFixed(2);
const data = {
ticker: ticker,
price: parseFloat(price),
timestamp: new Date().toISOString()
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
// Клиент: отображение live-цены
function trackStockPrice(ticker) {
const eventSource = new EventSource(`/api/stock-price/${ticker}`);
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
document.getElementById('price').textContent = `$${data.price}`;
document.getElementById('timestamp').textContent = new Date(data.timestamp).toLocaleTimeString();
});
return eventSource;
}
const aapl = trackStockPrice('AAPL');
6. SSE с параметрами запроса
// Клиент: отправка параметров в URL
const userId = 123;
const eventSource = new EventSource(`/api/user-notifications?userId=${userId}`);
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
});
// Сервер
app.get('/api/user-notifications', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const userId = req.query.userId;
// Подписываем пользователя на уведомления
notificationService.subscribe(userId, (notification) => {
res.write(`event: notification\n`);
res.write(`data: ${JSON.stringify(notification)}\n\n`);
});
req.on('close', () => {
notificationService.unsubscribe(userId);
res.end();
});
});
7. SSE для прогресса длительной операции
// Сервер: отправка прогресса
app.get('/api/long-operation/:operationId', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const operationId = req.params.operationId;
// Симуляция длительной операции
let progress = 0;
const intervalId = setInterval(() => {
progress += Math.random() * 20;
if (progress >= 100) {
res.write(`event: complete\n`);
res.write(`data: {"progress": 100, "status": "done"}\n\n`);
clearInterval(intervalId);
res.end();
} else {
res.write(`event: progress\n`);
res.write(`data: {"progress": ${Math.floor(progress)}, "status": "processing"}\n\n`);
}
}, 500);
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
// Клиент: отображение прогресса
function trackProgress(operationId) {
const eventSource = new EventSource(`/api/long-operation/${operationId}`);
const progressBar = document.getElementById('progress');
eventSource.addEventListener('progress', (event) => {
const data = JSON.parse(event.data);
progressBar.style.width = `${data.progress}%`;
progressBar.textContent = `${data.progress}%`;
});
eventSource.addEventListener('complete', (event) => {
const data = JSON.parse(event.data);
progressBar.style.width = '100%';
progressBar.textContent = 'Готово!';
eventSource.close();
});
eventSource.addEventListener('error', () => {
console.error('Ошибка при отслеживании прогресса');
eventSource.close();
});
}
trackProgress('op-456');
8. React компонент для SSE
import { useEffect, useState } from 'react';
function NotificationCenter() {
const [notifications, setNotifications] = useState<any[]>([]);
const [connected, setConnected] = useState(false);
useEffect(() => {
const eventSource = new EventSource('/api/notifications');
eventSource.addEventListener('open', () => {
setConnected(true);
});
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse(event.data);
setNotifications(prev => [notification, ...prev]);
});
eventSource.addEventListener('error', () => {
setConnected(false);
eventSource.close();
});
return () => {
eventSource.close();
};
}, []);
return (
<div>
<div className={`status ${connected ? 'online' : 'offline'}`}>
{connected ? 'Подключено' : 'Отключено'}
</div>
<ul>
{notifications.map((notif) => (
<li key={notif.id}>{notif.message}</li>
))}
</ul>
</div>
);
}
export default NotificationCenter;
Формат SSE сообщений
field: value\n
Поля:
- data: содержимое сообщения
- event: тип события
- id: идентификатор для восстановления (Last-Event-ID)
- retry: интервал переподключения в миллисекундах
Примеры:
Простое сообщение:
data: Hello world\n\n
Сообщение с типом события:
event: custom-event\ndata: Some data\n\n
Сообщение с ID для восстановления:
id: msg-1\ndata: Important message\n\n
Установка интервала переподключения (в сек):
retry: 10000\n\n
Итоги
SSE используется для:
- Live-уведомлений
- Обновления статуса операций
- Live-данных (курсы, погода)
- Chat-сообщений (в одном направлении)
- Отслеживания прогресса
Преимущества:
- Простой API
- Автоматическое переподключение
- Встроенная поддержка восстановления
- Низкие накладные расходы
Ограничения:
- Только uni-directional (сервер -> клиент)
- Максимум соединений от браузера (6-8)
- Не подходит для игр и real-time приложений