Рукопожатие: HTTP Upgrade

WebSocket начинается как обычный HTTP-запрос — и в один момент превращается в постоянный канал.

Рукопожатие WebSocket — обмен одним HTTP-запросом и ответом, после которого соединение переключается (Upgrade) с HTTP на протокол WebSocket поверх того же TCP-соединения.

Зачем начинать с HTTP

WebSocket мог бы использовать собственный порт и протокол с нуля, но тогда его блокировали бы корпоративные прокси и файрволы, заточенные под HTTP/80 и HTTPS/443. Поэтому WebSocket маскируется: открывается как обычный HTTP-запрос на тот же порт, проходит через инфраструктуру — и только потом «переобувается» в свой протокол. Это и есть рукопожатие.

Запрос на повышение

Браузер шлёт обычный GET, но с особыми заголовками:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

Ключевое — Upgrade: websocket и Connection: Upgrade: «хочу переключить протокол». Sec-WebSocket-Key — случайная строка для проверки, что сервер действительно понимает WebSocket.

Ответ сервера

Если сервер согласен, он отвечает кодом 101 Switching Protocols:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

После этого ответа HTTP заканчивается, и по тому же TCP-соединению начинают ходить WebSocket-фреймы — в обе стороны.

Как работает под капотом

Поле Sec-WebSocket-Accept — не магия: сервер берёт присланный Sec-WebSocket-Key, дописывает к нему фиксированную строку-«соль» (GUID из стандарта), считает SHA-1 и кодирует в base64. Браузер делает то же самое и сверяет результат. Совпало — значит на той стороне настоящий WebSocket-сервер, а не случайный обработчик, машинально вернувший 101. Это защищает от ложных «апгрейдов» через кеширующие прокси.

Браузер                         Сервер
   |  GET /chat (Upgrade)  ---->  |
   |  <---- 101 Switching ------  |
   |======== канал открыт =======|
   |  фрейм "привет"  --------->   |
   |  <--------- фрейм "ок"        |

Частые ошибки

  • Ждать код 200. Успешное рукопожатие — это 101, а не 200.
  • Думать, что рукопожатие повторяется на каждое сообщение. Оно одно — в самом начале; дальше канал просто живёт.
  • Игнорировать прокси. Некоторые старые прокси не пропускают Upgrade; для надёжности используют wss:// (через TLS), который прокси не вскрывают.

Итоги

  • WebSocket-соединение открывается как HTTP-запрос с заголовками Upgrade: websocket и Connection: Upgrade.
  • Сервер соглашается ответом 101 Switching Protocols.
  • Sec-WebSocket-Accept подтверждает, что на той стороне настоящий WebSocket-сервер.
  • После рукопожатия по тому же TCP-каналу идут двусторонние фреймы.
Проверьте себя
1. Каким кодом ответа сервер подтверждает переход на WebSocket?
A200 OK
B101 Switching Protocols
C301 Moved Permanently
D426 Upgrade Required
2. Зачем WebSocket начинает соединение как обычный HTTP-запрос?
AЧтобы пройти через прокси и файрволы, заточенные под HTTP
BЧтобы зашифровать данные
CЧтобы ускорить передачу
DЭто требование браузера для JSON
3. Сколько раз происходит рукопожатие за время жизни соединения?
AНа каждое сообщение
BОдин раз — в начале
CКаждые 30 секунд
DПри каждом реконнекте оно не нужно