Комментарии 11
Websocket как минимум уже 15 лет. И только счас его распробовали.... 1с внедрила, тут инфа появляется...
Это для веба самый вкусный протокол.
Почему так долго внедряется?
Спасибо за основательный разбор! Тема веб-сокетов обычно воспринимается как чёрный ящик: есть connect, есть send, а что там внутри уже не важно, если это работает. Но ровно до первого 1006 или странного поведения за прокси.
Особенно полезно, что вы прошлись по RFC, показали реальную структуру фрейма с битовыми масками и механизм маскирования. Вы хорошо объяснили про маскирование и то, что это не шифрование, а защита от атак на промежуточные прокси. Многие действительно думают, что маскировка нужна для конфиденциальности, хотя на самом деле история именно про то, чтобы вредоносный JS не мог сэмулировать HTTP-запрос и отравить кеш.
И отдельный респект за упоминание backoff при переподключении. Это та деталь, о которой часто забывают даже в коммерческих проектах, а потом сервер ложится от одновременной волны переподключающихся клиентов.
Единственное, что можно добавить для тех, кто хочет копнуть глубже: полезно заглянуть в исходники production-библиотек, например websockets для Python или реализацию в стандартной библиотеке asyncio. Там видно, как те же самые механизмы (фрейминг, контрольные сообщения, обработка закрытия) реализованы с учётом реальных случаев, таймаутов и правильной работы с памятью.
Ещё раз спасибо за качественный материал. Такие статьи помогают лучше понимать инструменты, которыми мы пользуемся.
ии
Вы хорошо объяснили про маскирование и то, что это не шифрование, а защита от атак на промежуточные прокси. Многие действительно думают, что маскировка нужна для конфиденциальности, хотя на самом деле история именно про то, чтобы вредоносный JS не мог сэмулировать HTTP-запрос и отравить кеш.
Вот тут хотелось бы очень подробно (возможно с примером атаки). А то маскирование выглядит как попытка спрятать трафик во времена когда TLS не был популярен.
Хорошо написано, но есть небольшие но:
Я бы написал про Sec-WebSocket-Accept и его проверку.
Если клиент не проверяет что значение в Sec-WebSocket-Accept это и есть именно SHA-1(Base64(seed key + GUID)), то это угрожает безопасности.
«13 единственная актуальная и других нет» это не так.
Сам протокол допускает возможность сообщения сервером других поддерживаемых версий через Sec-WebSocket-Version. Не нужно тут цементировать окошечко.
И не совсем понятно высказывание "любой фрейм можно делить на части".
Фрагментация применима только к фреймам данных, управляющие фреймы нельзя фрагментировать.Окончание сообщения определяется битом FIN. - это требование спецификации.
Спасибо за рецензию!
Я бы написал про Sec-WebSocket-Accept и его проверку.
Из статьи: "Ключ нужен для верификации сервера: клиент убеждается, что сервер действительно понимает протокол WebSocket, а не просто вернул произвольный HTTP-ответ. Проверка происходит через заголовок Sec-WebSocket-Accept — сервер вычисляет значение для него из этого ключа по строго определённой схеме, и клиент может это проверить."
Далее в чек-листе
Сервер вычисляет Sec-WebSocket-Accept из ключа клиента и фиксированного GUID
Клиент валидирует ответ сервера: статус 101, заголовки Upgrade/Connection и Sec-WebSocket-Accept. При успехе обе стороны переходят в состояние OPEN.
По поводу угроз безопасности. Здесь, кажется, важно развести две вещи. Sec-WebSocket-Accept не механизм криптографической защиты и не аутентификация сервера. Его задача в другом: позволить клиенту проверить, что сервер действительно обработал рукопожатие по правилам протокола.
«13 единственная актуальная и других нет» это не так.
Буду признателен, если приведете примеры реально используемых актуальных версий веб-сокетов, кроме 13. Если такие есть, с удовольствием внесу дополнение в статью.
И не совсем понятно высказывание "любой фрейм можно делить на части".
В статье речь не о “любом фрейме”, а о том, что одно логическое сообщение может быть разбито на несколько фреймов. При этом отдельно оговаривается, что управляющие фреймы фрагментировать нельзя.
Из статьи:
"Одно логическое сообщение может быть разбито на несколько фреймов. Это называется фрагментацией."
"Сами управляющие фреймы фрагментировать запрещено."
Спасибо за статью!
Вот так воскресным утром спонтанно решил погрузиться в тему websocket, поднял тестовый серверок, в devtools понаблюдал обмен сообщениями клиент-сервер, собрал и изучил дамп трафика в wireshark!
Определенно, утро удалось! =)
В статье написано что для проверки состояния соединения существуют нативные пинг и понг фреймы, значит ли это что сервер и клиент под капотом уже содержат этот механизм и реализовывать его отдельно при помощи текстовых фреймов как это делают многие разработчики при использовании WebSocket нет необходимости?
Супер вопрос. Ответ тут небыстрый, поэтому лонгрид :)
С одной стороны, протокол веб-сокетов определяет отдельные, служебные пинг-понг фреймы, работу с которыми должна поддерживать конкретная реализация.
Но, с другой стороны, протокол явно требует только одного: если клиент или сервер получил служебный пинг, он должен ответить служебным понгом. Всё.
Кто именно из сторон будет пинговать, будет ли пинговать вообще, с какой частотой и т.п. — всё это остаётся на усмотрение конкретной библиотеки.
Возьмем, к примеру, Python-библиотеку websockets. Там уже внутри либы зашит дефолтный пинг-понг каждые 20 секунд.
Либа сама запускает фоновую задачу, которая периодически шлёт пинги и закрывает соединение, если понг не пришёл вовремя. При необходимости через доступный интерфейс либы мы можем сами подкрутить работу со служебными пингами.
А теперь посмотрим на браузерные веб-сокеты. Здесь механизм служебного пинг-понга не доступен из нашего JS-кода. Т.е. браузер будет сам 100% отвечать понгами на пинги с сервера (потому что жесткое требование RFC). А будет ли браузер сам пинговать, это остается на усмотрение самого браузера (см. вот тут "User agents may send ping and unsolicited pong frames as desired...").
И вот здесь приходится самим внедрять простукивание сервера обычными текстовыми фреймами, назвав их как угодно, например, "пинг", но это уже не служебные фреймы, описанные в RFC. Поэтому на сервере тоже должна быть прописана ответная логика, что делать с этими текстовыми пингами. Это называется "application-level keepalive" и в документации той же Python-библиотеки websockets такой сценарий описан вот тут. Обратите внимание: в нем используется обычный метод send (отправка текстовых фреймов), а не специальный pong (отправка служебного понга). Как раз потому, что от браузерного клиента из JS придет текстовый фрейм.
дополню ответ, из практики:
есть система браузер - сервер на java - ардуинка с модулем интернета
на ардуинке в цикле опрашивается лазерный дальномер, данные отправляются на сервер java по протоколу websocket, сервер делает минимальную обработку для удобства отображения в браузере и отдаёт по websoket в браузер - отображается в виде бегущей кривой - расстояние, отображается в "реальном времени" , т.е. ардуинка как может измеряет и отдаёт. из ардуинки вынимаю коннектор на 15 сек.ардуинка продолжает делать измерения, график в браузере замирает. никаких ошибок ни кто не выдает. вставляю коннектор - модуль ардуинки выплёвывает то, что накопилось у него в буфере, в браузере происходит отображение данных за время отключения (скорость отображения намного превосходит скорость "нормального" отображения), и дальше продолжается "нормальное" отображение. "драйвер" websoket на ардуинке максимально упрощен для экономии кода.никакого пин/понга. да и драйвером назвать трудно, просто метод/функция на си.

Анатомия WebSocket: человечный разбор RFC 6455