Реальное время: polling, SSE, WebSocket, chunked
Базовый HTTP это один запрос, один ответ: клиент спросил, сервер ответил, соединение свободно. Но половина современного веба хочет наоборот, чтобы сервер слал данные сам: новое сообщение в чате, тик котировки, строчка лога, прогресс загрузки. Этот урок про четыре способа продавить одностороннюю и двустороннюю доставку поверх HTTP, от костыля до честного дуплекса.
Эволюция тут историческая, и пройти её по порядку полезно: каждый следующий приём чинит боль предыдущего.
Проблема: сервер хочет говорить первым
В классической модели сервер нем, пока его не спросили. Чтобы узнать про новое событие, клиент вынужден спрашивать снова и снова. Отсюда первый, наивный приём.
Short-polling: спрашиваем по таймеру
// каждые 3 секунды дёргаем сервер: есть новое?
setInterval(async () => {
const res = await fetch('/api/messages?since=' + lastId);
const items = await res.json();
render(items);
}, 3000);
Просто и работает везде, но цена высокая: пустые запросы жгут трафик и батарею, а задержка до события в среднем половина интервала. Это отправная точка: проще и переносимее уже не сделать, и дальше каждый способ будет бороться именно с этими двумя минусами, лишним трафиком и задержкой.
Long-polling: держим запрос открытым
Long-polling переворачивает идею: клиент спрашивает, а сервер молчит, пока не появится событие (или не истечёт таймаут). Получил ответ, сразу спросил снова.
async function poll() {
try {
const res = await fetch('/api/updates'); // сервер держит до события
const data = await res.json();
handle(data);
} catch {}
poll(); // сразу новый цикл
}
poll();
Задержка почти нулевая, лишних запросов нет. Но каждое событие это полный HTTP-цикл с новыми заголовками, а сервер держит кучу открытых соединений. Исторически этот класс приёмов звали Comet. В 2026 long-polling это fallback (например, в socket.io, когда WebSocket не проходит через прокси), а не первый выбор.
Server-Sent Events: честный односторонний стрим
SSE это первый честный push: одно долгое HTTP-соединение, по которому сервер бесконечно дописывает события. Формат текстовый и до смешного простой.
data: привет
data: ещё строка
event: price
id: 42
retry: 5000
data: {"ticker":"AAPL","px":231.5}
Поля: data: полезная нагрузка, event: имя события, id: идентификатор
(браузер вернёт его в Last-Event-ID при переподключении), retry: интервал
reconnect в миллисекундах. Пустая строка завершает событие.
Клиент это одна строчка, и переподключение встроено:
const es = new EventSource('/api/stream');
es.addEventListener('price', (e) => render(JSON.parse(e.data)));
es.onmessage = (e) => console.log('default event:', e.data);
// при разрыве браузер сам переподключится и пришлёт Last-Event-ID
Сервер на Node держит соединение открытым и пишет в него:
function stream(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
const timer = setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 1000);
req.on('close', () => clearInterval(timer));
}
SSE идеален, когда поток нужен только в одну сторону: лента уведомлений, прогресс задачи, серверные логи, токены LLM. Минусы: только текст, только сервер-клиент, и в HTTP/1.1 действует лимит на число одновременных соединений к домену (в HTTP/2 эта боль уходит, мультиплексирование снимает лимит).
WebSocket: полный дуплекс
Когда обе стороны должны говорить когда угодно (чат, игра, совместный редактор), нужен WebSocket.
Хитрость в том, как он начинается: обычный HTTP-запрос с заголовком
Upgrade,
на который сервер отвечает 101 Switching Protocols. После этого по тому же
TCP-соединению летят
фреймы
в обе стороны, без заголовков HTTP на каждое сообщение.
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
// клиент
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => ws.send(JSON.stringify({ type: 'hello' }));
ws.onmessage = (e) => render(JSON.parse(e.data));
ws.onclose = () => reconnectWithBackoff();
ws:// поверх обычного TCP, wss:// поверх TLS (всегда wss в проде).
Важные нюансы: переподключение и
heartbeat
ты реализуешь сам (в отличие от SSE, где reconnect встроен), а ещё нужно
помнить, что прокси и балансировщики любят рвать долгие соединения.
Библиотеки вроде socket.io добавляют поверх reconnection, rooms и fallback на
long-polling.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.