WebSocket handshake: заголовок Upgrade
WebSocket handshake: заголовок Upgrade
WebSocket — не просто HTTP с другим методом, это отдельный протокол. Но для его установки используется HTTP — хитрый трюк, который позволяет WebSocket'ам работать через те же порты (80/443) и с теми же прокси, что и обычный веб-трафик. Разберём, как HTTP-соединение «повышается» до WebSocket.
От HTTP к WebSocket через Upgrade
Клиент начинает с обычного HTTP-запроса, но добавляет специальные заголовки, означающие «я хочу переключиться на WebSocket»:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Это не REST-запрос и не запрос страницы. Это запрос на смену протокола. Метод всегда GET, URL — эндпоинт WebSocket-сервера.
Ключевые заголовки:
Upgrade: websocket— «хочу переключиться с HTTP на WebSocket».Connection: Upgrade— обязательный спутникUpgrade, по правилам HTTP.Sec-WebSocket-Key— случайная строка в base64 (16 байт). Нужна для доказательства, что сервер понимает WebSocket, а не случайный HTTP-сервер.Sec-WebSocket-Version— версия протокола (всегда 13 в современных браузерах).
Ответ сервера: 101 Switching Protocols
Если сервер поддерживает WebSocket по этому URL, он отвечает не 200 OK, а специальным статусом:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
101 Switching Protocols означает: «понял, переключаемся, дальше говорим на WebSocket». Важно: этот ответ не может быть сгенерирован обычным веб-приложением на Node.js/Python — его формирует сам WebSocket-сервер.
Заголовок Sec-WebSocket-Accept — доказательство того, что сервер действительно понял запрос. Он вычисляется по алгоритму:
Sec-WebSocket-Accept = base64(SHA1(Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
Магическая строка 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 — константа из спецификации RFC 6455. Она гарантирует, что сервер понимает именно WebSocket, а не какой-то другой Upgrade-протокол. Браузер проверяет этот хэш — и если не совпадает, соединение разрывается.
После handshake: WebSocket-фреймы
Как только сервер отправил 101, TCP-соединение больше не использует HTTP. Теперь данные передаются в виде WebSocket-фреймов — компактных бинарных пакетов:
WebSocket-фрейм:
┌─────┬─────┬──────────┬───────────┬──────────┐
│ FIN │opcode│ Masking │ Payload │ Masking │
│ (1b)│ (4b) │ Key (4B)│ Length (7b)│ Key │
└─────┴─────┴──────────┴───────────┴──────────┘
+ Payload Data (сами данные)
Фрейм содержит:
- FIN — последний ли фрейм сообщения (сообщение может быть разбито на несколько фреймов).
- Opcode — тип данных: text (UTF-8), binary (бинарные), ping, pong, close.
- Masking key — ключ для XOR-маскировки. Все фреймы от клиента к серверу ОБЯЗАНЫ быть маскированы — это защита от атак через прокси-кэши.
- Payload — сами данные.
Overhead WebSocket-фрейма — 2–10 байт (против ~400+ байт HTTP-заголовков). Именно это делает WebSocket эффективным для real-time.
Ping/Pong: keepalive для WebSocket
WebSocket-соединение может висеть открытым часами. Чтобы проверить, что клиент ещё жив (или что прокси не разорвал idle-соединение), используется механизм Ping/Pong:
Сервер: Ping-фрейм (opcode 9)
Клиент: Pong-фрейм (opcode 10)
Это встроено в протокол — не нужно слать кастомные сообщения «ты жив?». Большинство WebSocket-библиотек делают это автоматически.
Закрытие соединения
Закрыть WebSocket можно с любой стороны. Отправитель шлёт Close-фрейм (opcode 8) с кодом причины:
Клиент: Close-фрейм (code=1000, "Normal Closure")
Сервер: Close-фрейм (code=1000)
Коды закрытия стандартизованы:
- 1000 — нормальное закрытие.
- 1001 — клиент уходит (закрыл вкладку).
- 1006 — соединение оборвалось без close-фрейма (сеть пропала).
- 1011 — внутренняя ошибка сервера.
Проверь себя
- Какой HTTP-статус отвечает сервер при успешном WebSocket handshake?
- Зачем нужна магическая строка
258EAFA5-...в вычислении Sec-WebSocket-Accept? - Зачем нужны Ping/Pong-фреймы?
- 101 Switching Protocols — не 200 OK. Это означает «переключаемся на другой протокол».
- Это гарантия, что сервер понимает именно WebSocket (RFC 6455), а не какой-то другой Upgrade-протокол. Без этой строки обычный HTTP-сервер мог бы случайно вычислить правильный хэш.
- Чтобы проверять, что соединение всё ещё активно. Прокси могут разрывать idle TCP-соединения, а ping/pong поддерживают активность на уровне протокола.
Что унести с урока
- WebSocket начинается как HTTP GET с
Upgrade: websocket→ 101 Switching Protocols. - После handshake TCP-соединение переключается на WebSocket-фреймы (2–10 байт overhead).
- Все фреймы от клиента маскируются XOR-ключом — защита от атак через прокси.
- Ping/Pong поддерживают соединение активным, Close-фреймы корректно закрывают.
В следующем уроке разберём протоколы ws:// и wss:// — как WebSocket работает поверх TCP и TLS, и в чём отличие от HTTP.