WebSocket — это сетевой протокол поверх TCP, созданный для двунаправленного обмена сообщениями между браузером и сервером через постоянное соединение. В отличие от классического HTTP, где каждый запрос открывает и закрывает соединение, WebSocket поддерживает канал, по которому клиент и сервер могут обмениваться данными без повторных рукопожатий. Сегодня стандартизация API и спецификации протокола активно ведётся в организациях стандартизации — таких как W3C и IETF — а итоговые документы отражают практики, подтверждённые экспертами и разработчиками отрасли.
Открытие канала WebSocket
Установка WebSocket-соединения выполняется через процедуру, напоминающую HTTP-хандшейк: клиент отправляет специальный запрос, сервер отвечает соответствующими заголовками, после чего TCP-соединение переключается в режим WebSocket. В истории протокола были несколько ключевых редакций — каждая вносила значительные изменения в безопасность, структуру сообщений и совместимость.
Протокол 75
В ранней редакции (архивные материалы конца 2009 — середины 2010) процедура установки была максимально простой. Клиент отправляет запрос, похожий на HTTP GET с заголовками Upgrade и Connection, а сервер возвращает ответ с кодом 101, подтверждающий «переход» на WebSocket. После такого обмена канал считался открытым, и начался двунаправленный обмен по тому же TCP-соединению.
Пример запроса клиента (упрощённо):
GET /demo HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
WebSocket-Protocol: sample
В ответ сервер отправлял:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
WebSocket-Protocol: sample
После подтверждения соединения обмен текстовыми сообщениями (UTF-8) требовал специальных ограничителей: нулевой байт перед сообщением и байт 255 после него — подход, характерный для ранних черновых реализаций.
Протокол 76
Редакция 76, принятая в середине 2010 года, ввела механизмы защиты от подделки запросов и изменила процедуру рукопожатия, утратив обратную совместимость с наиболее ранними реализациями. Теперь в запросе появились случайно сгенерированные заголовки Sec-WebSocket-Key1 и Sec-WebSocket-Key2 и восьмибайтовое тело; сервер в ответ возвращал изменённые заголовки Sec-WebSocket-Origin, Sec-WebSocket-Location, Sec-WebSocket-Protocol и 16-байтное тело, рассчитанное по определённому алгоритму.
Новые заголовки добавлялись клиентом случайным образом. Анализ и проверка этих полей делались на серверной стороне, что повышало устойчивость к простым атакам.
Алгоритм формирования 16-байтного тела ответа (упрощённая схема):
- Убрать из строки значения Sec-WebSocket-Key1 все символы, не являющиеся цифрами.
- Получить 64‑битное целое число из оставшихся цифр.
- Разделить это число целочисленно на количество пробелов в исходной строке.
- Представить результат как 4-байтовое число в формате big-endian.
- Повторить для Sec-WebSocket-Key2.
- Конкатенировать две 4-байтовые строки и добавить 8-байтовое тело запроса.
- Вычислить MD5 от полученной 16-байтовой строки и отправить этот бинарный MD5 в теле ответа «как есть».
Важно: хотя хендшейк выглядит как HTTP, он не полностью соблюдает все соглашения HTTP (например, тело запроса присутствует при отсутствии Content-Length), поэтому серверы должны были различать версии по наличию заголовков Sec-WebSocket-Key1/Sec-WebSocket-Key2 и корректно поддерживать оба типа клиентов.
Протокол 07
Редакция 07 (апрель 2011) изменила формат передаваемых кадров и ввела обязательную маскировку данных от клиента к серверу. В этой версии каждое сообщение клиента маскируется уникальной 4-байтовой маской, которая применяется к каждому байту полезной нагрузки; это решало ряд проблем с промежуточными прокси и корректностью передачи.
Сообщение стало снабжаться заголовком, содержащим управляющие флаги и метаданные:
- флаг фрагментации сообщения (есть ли фрагменты);
- тип полезных данных (текст, бинарные данные и т. д.);
- признак маскировки и сама маска;
- длина данных (включая расширения для больших сообщений);
- служебные опкоды для управления (ping, pong, close и др.).
Пример клиентского запроса для версии 07 (сокращённо):
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 7
Сервер в ответ отправляет:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Заголовок Sec-WebSocket-Accept формируется так:
- Соединить значение Sec-WebSocket-Key с фиксированной строкой 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.
- Вычислить бинарный SHA-1 хеш от этой строки (20 байт).
- Закодировать результат в Base64 — это и будет значение Sec-WebSocket-Accept.
Пример на PHP (упрощённо):
<?php
echo base64_encode(sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
?>
Протокол RFC 6455 (фреймы)
11 декабря 2011 года спецификация окончательно стала RFC 6455. В этой версии были закреплены правила передачи данных фреймами, обязательная маскировка клиентских сообщений, формат заголовков и обработка управляющих опкодов. Также было принято использовать в заголовках стандартное имя Origin вместо прежних вариаций. Начиная с этой редакции, взаимодействие между клиентом и сервером по WebSocket стало предсказуемым и совместимым между основными реализациями.
Схема URI
Протокол определяет две основные URI‑схемы:
- ws: для нешифрованных WebSocket‑соединений;
- wss: для зашифрованных (TLS/SSL) соединений.
Использование wss: рекомендуется для публичных и критичных сервисов, так как шифрование защищает от подмены и просмотра трафика посредниками.
Реализация WebSocket в браузерах
На стороне клиента в браузере создаётся объект WebSocket, в конструктор которого передаётся URI сервера, и подписываются обработчики событий — открытие соединения, получение сообщения, ошибка и закрытие. Типовой пример выглядит просто: создать объект, отправить сообщение при открытии и обработать входящие сообщения.
Минимальный пример (логика, упрощённо):
const webSocket = new WebSocket('ws://localhost/echo');
webSocket.onopen = event => {
alert('onopen');
webSocket.send("Hello Web Socket!");
};
webSocket.onmessage = event => {
alert('onmessage, ' + event.data);
};
webSocket.onclose = event => {
alert('onclose');
};
Поддержка WebSocket реализована во всех современных движках браузеров. На этапе внедрения некоторые версии имели экспериментальную или частично отключённую поддержку в связи с безопасностью, но со стандартом RFC 6455 совместимость и поведение выровнялись.
- Google Chrome — поддержка с ранних версий (серии 4.x в черновых билдах);
- Apple Safari — поддерживает WebSocket начиная с версий 5.x;
- Mozilla Firefox — поддержка стабильна с версий 4 и выше;
- Opera — реализовал поддержку в версиях 10.7x и выше;
- Internet Explorer — поддержка появилась в версиях 10 и выше.
Для оценки поддержки конкретных функций WebSocket в разных браузерах удобно сверяться с ресурсами совместимости, такими как caniuse.com, а также с официальной документацией браузерных вендоров и заметками экспертов.
В ноябре 2010 года исследование, опубликованное Adam Barth, показало возможные проблемы надёжности и безопасности при общем использовании WebSocket в условиях прозрачных прокси — возможна была подмена кэша и вмешательство в трафик. Эти результаты повлияли на решения разработчиков браузеров по умолчанию временно ограничивать или тщательно проверять поддержку WebSocket до устранения уязвимостей. Сегодня с учётом RFC 6455 и применения TLS большинство проблем решены, но рекомендации по безопасности остаются актуальными.
| Версия протокола | Год / Дата | Ключевые изменения | Handshake тело (байт) | Маскировка |
|---|---|---|---|---|
| 75 | 2009–2010 | Простой хендшейк, текстовые ограничители (0x00/0xFF) | нет | нет |
| 76 | июнь 2010 | Sec-WebSocket-Key1/Key2, MD5‑ответ (защита от подделки) | запрос 8, ответ 16 | нет |
| 07 | апрель 2011 | Фреймы, обязательная 4-байтная маска от клиента | нет (используются заголовки и опкоды) | обязательно для клиент → сервер |
| RFC 6455 | 11 дек. 2011 | Стандартизация формата фреймов, опкоды, Origin | нет | обязательно для клиент → сервер |
Интересные факты и советы о WebSocket
- WebSocket экономит сетевой трафик и задержки: постоянное соединение устраняет накладные расходы на установку TCP и TLS при каждом сообщении.
- Для публичных сервисов используйте wss: это обязательный шаг для защиты от MITM и утечки данных — практика, подтверждённая рекомендациями специалистов по безопасности.
- Если требуется масштабирование, применяйте специализированные прокси и шлюзы (например, балансировщики WebSocket‑aware), которые корректно работают с длительными соединениями и поддерживают разрывы/переподключения.
- Тестируйте поведение через реальные прокси и мобильные сети: некоторые прозрачные прокси и сетевые устройства могут закрывать или модифицировать WebSocket-сессии.
- Логи и мониторинг фреймов важнее, чем просто логирование HTTP-запросов: у WebSocket есть управляющие опкоды (ping/pong/close), которые помогают диагностировать состояние соединения.
Часто задаваемые вопросы
Что такое WebSocket и чем он отличается от HTTP?
WebSocket — это протокол для двунаправленного обмена данными поверх одного постоянного TCP‑соединения. В отличие от HTTP, где каждое действие обычно инициируется клиентом и требует отдельного запроса, WebSocket позволяет серверу и клиенту обмениваться сообщениями в любое время по одному открытому каналу.
Нужно ли шифрование для WebSocket?
Для публичных и конфиденциальных сервисов рекомендуется использовать wss: (WebSocket over TLS). Шифрование защищает от перехвата и подмены сообщений, особенно при работе через ненадёжные сети или общие прокси.
Что такое маскировка в WebSocket?
Маскировка — это обязательный для клиентских сообщений механизм, при котором полезная нагрузка XOR'ится с 4‑байтовой маской. Это снижает риск вмешательства со стороны некоторых типов промежуточных устройств и предотвращает определённые категории атак.
Как проверить поддержку WebSocket в браузере?
Поддержка проверяется как свойство объекта WebSocket в скрипте (например, 'WebSocket' in window) и через сведения о поддержке по версиям в справочниках совместимости, таких как caniuse.com; также стоит учитывать поведение конкретной версии и движка браузера.
