Веб-сокеты (WebSocket) — набор правил для двусторонней связи между браузером и сервером в реальном времени. Если HTTP работает по принципу "спросил — ответил", то веб-сокеты предусматривают постоянный канал: сервер может сам отправлять данные браузеру, не дожидаясь запроса. Именно по этому протоколу работают чаты, онлайн-редакторы, многопользовательские игры.
Я готовил статью об устройстве самого протокола, но когда начал изучать историю появления стандарта, понял: она заслуживает отдельного рассказа. Вот он.
Часть 1. Ян Хиксон и его поезда
Есть старая легенда о том, что веб-сокеты, которым пользуются сегодня миллионы сайтов, были придуманы, чтобы управлять игрушечной железной дорогой через браузер. История красивая. И почти правда.
Начнем с человека.
Ян Хиксон (Ian Hickson), известный в сети как Hixie, начал карьеру стажером в Netscape (для тех, кто помоложе: Netscape — разработчик одного из двух главных браузеров 90-х, проигравший битву Microsoft), затем работал в Opera Software, а в 2005 году перешел в Гугл, где трудился 18 лет. За это время он успел стать соредактором CSS 2.1, создать браузерные тесты Acid2 и Acid3 (об этом тесте был большой материал на Хабре), по которым сверяли совместимость браузеров в нулевых, и главным редактором сп��цификации HTML5 в WHATWG, организации, которую сам же помогал основывать в 2004 году, когда W3C, по мнению разработчиков браузеров, повел стандартизацию в тупик. В общем, перед нами человек, который буквально "строил веб".
В качестве хобби Хиксон собирал масштабный макет железной дороги цифровыми декодерами в каждом локомотиве и стрелке. В апреле 2005 года он написал в своем блоге:
Все стрелки и локомотивы оснащены встроенными цифровыми декодерами, я управляю всем макетом через компьютерный интерфейс. Я настроил небольшое веб-приложение для управления поездами, это довольно круто.
В том же посте фотографии макета.
Уже тогда, за три года до выхода на сцену веб-сокетов, у Хиксона был работающий интерфейс управления поездами через браузер. Но как он это реализовал?
2 запроса вместо одного
В другой записи в апреле 2005 года Хиксон описал архитектуру.
На ноутбуке крутится маленький Perl-скрипт — TCP-сервер с простым текстовым протоколом собственного изобретения. Он принимает команды из сокетов и отправляет их в последовательный порт к поездам, а сигналы от датчиков на рельсах рассылает всем подключенным клиентам. Веб-интерфейс представлен обычной страницей с двумя CGI-скриптами: один держит постоянное соединение с сервером и превращает сообщения в JS-команды для обновления UI, второй открывает соединение, отправляет одну команду и закрывается.
Это ранний пример того, что чуть позже стали называть Comet: не одна технология, а целый класс приемов, которые имитировали пуш от сервера и квази-двустороннюю связь поверх обычного HTTP.
Comet объединял несколько техник:
классический поллинг (браузер долбит сервер запросами "есть новости?")
лонг-поллинг (браузер отправляет запрос и ждет, пока сервер соизволит ответить)
"вечный фрейм" (скрытый
<iframe>, в который сервер бесконечно пишет JavaScript-блоки)HTTP-стриминг (сервер отвечает на запрос, но намеренно не закрывает соединение и продолжает досылать данные порциями).
Все они создавали иллюзию двустороннего канала, но каждый с постоянными компромиссами: либо лишние HTTP-запросы, либо задержки, либо проблемы с прокси и корпоративными фаерволами. Именно от таких схем веб-сокеты в дальнейшем и позволили избавиться.
Параллельно, в апреле 2004 года, Хиксон опубликовал черновик <event-source>, элемент, который позволял серверу посылать браузеру события в реальном времени. Полная BNF-грамматика, новый MIME-тип application/x-dom-event-stream, интерфейс RemoteEvent. Этот элемент стал прямым предком современного Server-Sent Events (SSE). Opera реализовала SSE в 2006 году.
Но у SSE было принципиальное ограничение: поток данных идет только в одну сторону, от сервера к браузеру. Чтобы послать команду "стрелка налево", браузер все равно должен был делать отдельный HTTP-запрос. Для управления железной дорогой это неудобно: нужен полноценный двусторонний канал.
TCPConnection: первое имя веб-сокетов
Задача создания полноценного двустороннего канала обмена сообщениями был�� на тот момент общей для веба. В спецификации HTML5 появилась заглушка под названием TCPConnection. Без протокола, без имплементации, просто обозначение того, что такая возможность нужна.
Веб-сокеты не начинались с идеи "хочу управлять поездами из браузера" (как гласит красивая легенда). Они начинались как стандартное решение задачи, которая требовалась всей индустрии. Сам Хиксон впоследствии уточнял:
Думаю, работа над Web Sockets (тогда называвшимися TCPConnection) началась раньше, чем я задумался об управлении макетом через Web Sockets, хотя Web Sockets, безусловно, сделали бы это значительно проще. Проблема была классической для веб-приложений, управляющих физической системой: есть один компьютер с последовательным портом, подключенным к интерфейсному блоку Märklin, и хочется написать веб-страницу, которая общается с этим компьютером, отправляет команды и получает обновления, не прибегая к поллингу, множественным соединениям, потокам
<script>внутри<iframe>и т.п.
То есть поезда шли параллельно, как живая, наглядная иллюстрация именно той проблемы, которую TCPConnection должен был решить. Тот факт, что они шли параллельно, лишь создавал иллюзию того, что одно породило другое.
Часть 2. От обсуждений в рассылке до мирового стандарта
Рождение термина WebSocket
17 июня 2008 года в рассылке WHATWG появилось письмо от Майкла Картера. Он предложил добавить в спецификацию механизм TCP-соединений, использующий HTTP-заголовок Upgrade: браузер начинает разговор как обычный HTTP-запрос, а затем просит сервер переключиться на другой протокол.
Я считаю, что метод OPTIONS из HTTP/1.1, заголовок Upgrade и ответ "101 Switching Protocols" — наилучший путь, поскольку именно эти части HTTP были специально разработаны для того, чтобы (1) узнать, поддерживает ли сервер новый протокол, и (2) переключить протокол прямо в середине соединения.
В письме Картер процитировал 4 требования к новому механизму, которые сформулировал Хиксон:
данные идут одновременно в обе стороны (полнодуплексный канал)
никакой сложной инфраструктуры (сервер реализуем в нескольких строках Perl)
нельзя использовать протокол для подключения к SMTP или другим сервисам.
соединение работает через корпоративные фаерволы
Картер предложил назвать новый объект SocketConnection. На что Хиксон 18 июня 2008 года ответил:
I was thinking WebSocket (with the protocol itself called the Web Socket Protocol or Web Socket Communication Protocol or some such).
Так родилось имя, которое сегодня знает каждый веб-разработчик.
В декабре 2009 года Google Chrome стал первым, кто включил WebSocket по умолчанию в dev-ветке Chrome 4; затем появились реализации в Safari и Firefox.
Веб-сокеты на паузе
Летом 2010 года исследователи обнаружили уязвимость в черновике протокола, который к тому моменту дошел до версии hixie-76. Уязвимость была неочевидной, но серьезной.
Из-за архитектурной ошибки в рукопожатии hixie-76 стала возможна атака "отравление кэша". Сотрудник на служебном компьютере заходит на зараженный сайт, и вредоносный JS-скрипт открывает WebSocket-соединение через корпоративный прокси. Прокси не понимает протокол веб-сокетов и путается: он принимает часть трафика веб-сокетов за обычные HTTP-запросы и послушно кэширует ответы на них. Атакующий заранее сформировал этот трафик так, чтобы прокси запомнил нужный ему URL с нужным содержимым. Теперь все сотрудники компании, открывая этот URL, получают из кэша подмененную страницу. Ирония в том, что уязвимое место добавили намеренно, как защитный механизм: сервер должен был подтвердить, что прочитал тело запроса целиком.
Firefox и Opera немедленно отключили WebSocket по умолчанию. В 2010 работа над протоколом переехала из WHATWG в IETF, организацию, отвечающую за интернет-стандарты уровня TCP/IP и HTTP, в рабочую группу HyBi (Hypertext Bidirectional).
17 черновиков
Авторство перешло от Хиксона к Яну Фетте (тоже из Гугл). К работе присоединился Алексей Мельников — автор более шестидесяти RFC по IMAP, SMTP и безопасности.
За полтора года рабочая группа выпустила 17 черновиков. Главным техническим решением стала обязательная маскировка фреймов: каждый фрейм данных, отправляемый от клиента к серверу, побитово перемешивается со случайным числом. Для прокси такой поток выглядит как случайный шум, он не может принять его за HTTP-запрос и не станет кэшировать.
Заодно переработали и рукопожатие. Три запутанных ключа из hixie-76 заменили одним Sec-WebSocket-Key: браузер отправляет случайную строку, сервер склеивает ее с фиксированным магическим значением 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 , берет SHA-1 и отвечает результатом. Этот GUID намертво зашит в RFC 6455 и с тех пор кочует по всем реализациям WebSocket в мире.
Для заголовка
Sec-WebSocket-Acceptсервер должен взять значение изSec-WebSocket-Key(в кодировке base64, без ведущих и завершающих пробелов) и соединить его со строковым представлением GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 — строкой, которая вряд ли будет использоваться сетевыми узлами, не поддерживающими протокол WebSocket.RFC 6455, раздел 1.3 "Opening handshake"
Браузер проверяет ответ: если сервер вернул правильный хэш, значит, перед нами настоящий WebSocket-сервер, а не прокси, который случайно ответил согласием.
В декабре 2011 года RFC 6455 "The WebSocket Protocol" был опубликован со статусом Standards Track. Протокол, который начинался как TCPConnection в HTML5-спецификации, получил окончательное имя и номер.
Эпилог
RFC 6455 с тех пор почти не менялся. В 2015 году вышел RFC 7692 с поддержкой сжатия через DEFLATE, в 2018-м RFC 8441 добавил работу поверх HTTP/2. Сам стандарт остался нетронутым.
В ноябре 2023 года Ян Хиксон написал пост, приуроченный к уходу из Гугла, "Reflecting on 18 years at Google". Он описал, как менялась компания на протяжении тех 18 лет, что он в ней работал.
О своей роли в первые 9 лет он написал так:
Моим мандатом было делать то, что лучше для веба, поскольку все, что хорошо для веба, хорошо и для Google. Мне явно было сказано игнорировать интересы Google.
Именно в этой среде, с полной свободой и единственным ориентиром "делай правильно для веба", и появились веб-сокеты. Про ранний Гугл Хиксон написал с нескрываемой теплотой:
Мне очень повезло застать ранний Google после IPO. В отличие от большинства компаний, и вопреки расхожему нарративу, сотрудники Google, от младшего инженера до совета директоров, были поистине хорошими людьми, которые по-настоящему заботились о том, чтобы поступать правильно. Часто высмеиваемый принцип "не будь злом" действительно был путеводным принципом компании в то время.
А вот как он описывает то, во что Гугл, по его мнению, превратился к моменту ухода из компании:
Культура Google разрушилась. Решения перестали приниматься ради пользователей. Сначала решения стали принимать ради Google, а потом и вовсе ради тех, кто их принимает. Прозрачность испарилась. Там, где раньше я с нетерпением ждал каждого общекорпоративного собрания, я теперь мог слово в слово предсказать, что скажут руководители. Сегодня я не знаю никого в Google, кто мог бы объяснить, в чем заключается видение компании
Финал письма совсем грустный:
Деградация корпоративной культуры Google в конечном счете станет необратимой, потому что люди, которые нужны вам в роли морального компаса, это те самые люди, которые не пойдут в организацию, у которой нет морального компаса.
Полный перевод письма Хиксона ранее публиковался на Хабре.
RFC 6455 пережил все это как незыблемый фундамент. Сегодня веб-сокеты работают везде, где есть живые данные: в чатах, совместных редакторах, торговых терминалах, геотрекинге, многопользовательских играх. Каждый раз, когда курсор вашего кол��еги двигается по общему документу и вы видите это в реальном времени, работает протокол, придуманный человеком, которому нужно было перевести стрелку на модели железной дороги.
Последняя запись про железную дорогу в блоге Яна Хиксона датирована февралем 2007 года. Возможно, он наконец получил удобный канал связи, который искал.
