WebSocket
| Стандарт | RFC 6455 (декабрь 2011) |
|---|---|
| API браузера | WHATWG WebSockets Standard |
| Схемы URI | ws:// (порт 80), wss:// (порт 443, поверх TLS) |
| Работает поверх | TCP; для HTTP/2 — RFC 8441, для HTTP/3 — RFC 9220 |
| Установка | HTTP Upgrade (код 101) |
| Альтернативы | Server-Sent Events, long polling, WebTransport |
WebSocket — сетевой протокол, который открывает между клиентом и сервером постоянный двусторонний канал поверх одного TCP-соединения. В отличие от обычного HTTP, где клиент спрашивает, а сервер отвечает, по WebSocket любая сторона может отправить сообщение в любой момент. На нём строят чаты, совместное редактирование, биржевые котировки, многопользовательские игры, уведомления и потоковый вывод ответов языковых моделей.
История
До WebSocket «живые» веб-приложения обходились приёмами вроде частого опроса (polling), долгого опроса (long polling) и Comet — все они расходовали лишние запросы и заголовки. Протокол появился в рамках HTML5 в 2008–2010 годах, после нескольких несовместимых черновиков был стандартизирован IETF как RFC 6455 в 2011 году.[1] Браузерный интерфейс WebSocket поддерживает WHATWG.[2] Сегодня его поддерживают все браузеры и серверные платформы.
Установка соединения
Соединение начинается с обычного HTTP/1.1-запроса с просьбой «сменить протокол»:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
Сервер отвечает кодом 101 Switching Protocols и заголовком Sec-WebSocket-Accept — хешем SHA-1 от присланного ключа с фиксированной строкой. Это не защита, а проверка, что сервер действительно понимает WebSocket. После этого то же TCP-соединение используется для кадров WebSocket. Для HTTP/2 и HTTP/3 определены свои способы открыть WebSocket внутри потока (RFC 8441, RFC 9220).[4]
Формат кадров
Данные передаются кадрами (frames) с компактным заголовком от 2 до 14 байт:
| Поле | Назначение |
|---|---|
| FIN | последний ли это фрагмент сообщения |
| Opcode | тип: текст (UTF-8), двоичные данные, продолжение, close, ping, pong |
| Mask | маскирован ли полезный груз (клиент → сервер — всегда) |
| Payload length | длина: 7 бит, 16 или 64 бита |
Маскирование кадров от клиента случайным ключом защищает промежуточные прокси от атак отравления кэша. Управляющие кадры ping/pong проверяют, что соединение живо, а close с кодом причины (например, 1000 — нормальное закрытие, 1001 — уход со страницы) завершает его корректно. Расширение permessage-deflate сжимает сообщения.[5]
API в браузере
const ws = new WebSocket("wss://example.com/chat");
ws.addEventListener("open", () => ws.send(JSON.stringify({type: "hello"})));
ws.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
console.log("получено:", msg);
});
ws.addEventListener("close", (e) => console.log("закрыто", e.code));
Обмен обычно ведут в формате JSON. API не умеет само переподключаться — это делает приложение, с экспоненциальной задержкой между попытками.[3]
Сервер
На сервере WebSocket поддерживают все основные стеки: ws и Socket.IO в Node.js, websockets и Starlette в Python (FastAPI), gorilla/websocket и nhooyr.io/websocket в Go, tokio-tungstenite в Rust. Socket.IO — не сам WebSocket, а надстройка со своим протоколом: комнаты, подтверждения, автоматическое переподключение и откат на long polling.
Каждое соединение долгоживущее, поэтому серверу нужна асинхронная модель ввода-вывода, а при нескольких экземплярах — общая шина сообщений (Redis Pub/Sub, NATS), чтобы событие с одного сервера дошло до клиентов, подключённых к другому.
За прокси и балансировщиком
Обратный прокси должен пробрасывать заголовки Upgrade и Connection и не закрывать неактивные соединения слишком рано. Для nginx:[6]
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 1h;
}
При балансировке важно, что соединение закреплено за одним сервером на всё время жизни; алгоритм least connections распределяет такие соединения лучше, чем round robin.
Безопасность
- Используйте
wss://— WebSocket поверх TLS; незашифрованныйws://легко перехватить и блокируется многими сетями. - Политика same-origin на WebSocket не распространяется: сервер обязан проверять заголовок
Origin, иначе возможна атака Cross-Site WebSocket Hijacking. - Аутентификацию выполняют при установке (cookie, токен в первом сообщении); токен в URL попадает в логи.
- Ограничивайте размер сообщений и частоту — открытое соединение легко превратить в канал перегрузки.
Альтернативы
- Server-Sent Events (SSE) — односторонний поток событий от сервера по обычному HTTP; проще, работает через любые прокси, переподключается сам. Именно SSE используют многие API языковых моделей для потокового вывода.
- Long polling — запасной вариант для очень старых сред.
- WebTransport — новый API поверх HTTP/3 и QUIC с несколькими потоками и ненадёжными датаграммами.