[Перевод] Почему одного AJAX недостаточно: протокол WAMP
AJAX-вызовы вывели работу web на новый уровень. Уже не нужно перезагружать страницу в ответ на каждый ввод информации пользователем. Теперь возможно отправлять вызовы на сервер и обновлять страницу на основании полученных ответов. Это ускоряет работу интерактивного интерфейса.А вот что AJAX не обеспечивает — так это обновления с сервера, которые необходимы для работы приложения в реальном времени. Это могут быть приложения, в которых пользователи одновременно редактируют один документ, или уведомления, рассылаемые миллионам читателей новостей. Необходим ещё один шаблон для рассылки сообщений, в дополнение к запросам AJAX, который бы работал в разных масштабах. Для этого традиционно используется шаблон PubSub («publish and subscribe», «публикация и подписка»).
Какую задачу решил AJAXДо появления AJAX интерактивные взаимодействия со страницей были тяжеловесными. Каждое из них требовало перезагрузки страницы, которая создавалась на сервере. В этой модели основной единицей взаимодействия была страница. Неважно, какой объём информации отправлялся из браузера на сервер — результатом была полностью обновлённая страница. Это была трата как трафика, так и серверных ресурсов. И это было медленно и неудобно для пользователей.AJAX решил проблему, разбивая всё на части: стало возможным отправить данные, получить конкретный результат и обновить лишь часть страницы, имеющую к этому отношение. От вызова «дай мне новую страницу» мы перешли к конкретным запросам данных. У нас появилась возможность делать вызовы удалённых процедур (RPC).Рассмотрим простой пример веб-голосования:
С использованием AJAX обработка щелчка по «Vote» сводится примерно к следующему:
var xhr = new XMLHttpRequest (); xhr.open ('get', 'send-vote-data.php');
xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) {
// Обновить голосование на основании результата } else{ alert ('Error: '+xhr.status); // Ошибочка вышла } } } Затем нужно сменить только один счётчик голосования. От перерисовки страницы мы перешли к изменению одного элемента DOM.
У сервера меньше работы, и трафик уменьшился. А главное, интерфейс обновляется быстрее, улучшая привлекательность использования.
Чего не хватает В реальном мире подобное приложение будет получать много голосов параллельно. Количество голосов будет меняться. Поскольку единственной связью клиента с сервером будут AJAX-запросы, пользователь увидит результаты только в тот момент, когда приложение загрузится. Потом изменения пользователю передаваться не будут.AJAX обновляет страницы только в ответ на действия пользователя. Он не решает задачи обработки апдейтов, идущих с сервера. Он не предлагает способа делать то, что нам нужно: передавать информацию с сервера в браузер. Для этого необходим шаблон передачи сообщений, который отправляет обновления на клиент без участия пользователя и без необходимости для клиента постоянно опрашивать сервер.
PubSub: обновления от одного ко многим Устоявшимся шаблоном для таких задач служит PubSub. Клиент заявляет свой интерес в какой-то теме (подписывается) серверу. Когда клиент отправляет событие серверу (публикует), сервер распространяет его по всем подсоединённым клиентам.Одно из преимуществ — публикаторы и подписчики не связаны с сервером. Публикатору не нужно знать о действующих подписчиках, а подписчикам — о публикаторах. Поэтому PubSub легко внедрять как у тех, так и у других, и он хорошо масштабируется.
Реализаций шаблона множество. На Node.js или Ruby можно использовать Faye. Если вам не хочется держать свой сервер, можно использовать веб-сервисы типа Pusher.
Два шаблона отправки сообщений, две технологии? Довольно просто отыскать технологию PubSub, подходящую для нужд определённого приложения. Но даже в таких простых приложениях, как голосование, необходимо реализовывать и RPC и PubSub — и отправку данных, и запросы, и получение обновлений. Используя чистый PubSub, вам придётся использовать две разных технологии: AJAX andУ такого подхода есть минусы: — организация двух разных стеков, возможно, двух серверов— раздельные соединения приложения для двух шаблонов, большая нагрузка на сервер— на сервере нужно интегрировать два стека в одном приложении и координировать их друг с другом— то же на фронтенде
WAMP: RPC и PubSub Web Application Messaging Protocol (WAMP) решает эти проблемы, интегрируя RPC и PubSub в один протокол. Одна библиотека, одно соединение и один API.Протокол открыт, и для него есть открытая реализация на JavaScript (Autobahn|JS), работающая и в браузере и под Node.js. Для других языков также существуют реализации, так что можно использовать PHP, Java, Python или Erlang на сервере.
Библиотеки WAMP можно использовать не только на бэкенде, но и для нативных клиентов, позволяя сочетать web и клиентов, работающих на одном протоколе. Библиотека на C++ хорошо приспособлена для запуска WAMP-компонент на устройствах с ограниченными ресурсами.
Соединения происходят не от браузера к бэкенду, а через WAMP-роутер, распространяющий сообщения. Для PubSub он играет роль сервера — ваш сервер публикует сообщение для роутера, а он уже распространяет его. Для RPC фронтенд отправляет запрос на удалённую процедуру на роутер, а он переадресовывает её на бэкенд, и затем возвращает результат.
Посмотрим, как решить нашу задачу с голосовалкой при помощи WAMP.
Живое обновление голосовалки: WebSockets и WAMP Для простоты наш бэкенд будет также написан на JS и будет работать в другой закладке. Браузерный бэкенд возможнен потому, что браузерные клиенты могут регистрировать процедуры для удалённого вызова так же, как и любой другой WAMP-клиент.Код для демки лежит на GitHub, вместе с инструкциями по запуску. В качестве роутера используется Crossbar.io.
Подключение библиотеки WAMP Для начала подключим библиотеку Autobahn|JS.В целях демонстрации её можно подключить так:
; Устанавливаем соединение var connection = new autobahn.Connection ({ url: «ws://example.com/wamprouter», realm: «votesapp» }); Первый аргумент — URL роутера. Использована схема ws, поскольку WAMP использует WebSockets в качестве транспорта по умолчанию. Кроме того, при общении не передаются HTTP-заголовки, что уменьшает трафик. WebSockets поддерживаются во всех современных браузерах.
Вторым аргументом мы устанавливаем «realm», пространство, к которому присоединяется соединение. Пространства создают отдельные домены для роутинга на сервере — то есть сообщения передаются только внутри одного пространства.
Созданный объект позволяет прикрепить два обратных вызова — один для успешного соединения, а второй для неуспешного, и для момента, когда соединение прервётся.
Хэндлер onopen вызывается по установлению соединения, и получает объект session. Мы передаём это в функцию main, в которой содержится функциональность приложения.
connection.onopen = function (session, details) { main (session); }; Далее необходимо запустить открытие соединения:
connection.open (); Регистрируем и вызываем процедуру Фронтенд отправляет голоса, вызывая процедуру на бэкенде. Определим функцию обработки переданного голоса: var submitVote = function (args) { var flavor = args[0]; votes[flavor] += 1;
return votes[flavor]; }; Она увеличивает количество голосов и возвращает это число.
Затем мы регистрируем её на роутере WAMP:
session.register ('com.example.votedemo.vote', submitVote) При этом мы назначаем ей уникальный идентификатор, использующийся для вызова. Для этого WAMP использует URI в виде пакетов Java.
Теперь функцию submitVote можно вызвать из любого авторизовавшегося клиента из этого же пространства. Выглядит вызов так:
session.call ('com.example.votedemo.vote',[flavor]).then (onVoteSubmitted) То, что возвращает submitVote, передаётся в хэндлер onVoteSubmitted.
Autobahn|JS делает это через обычные обратные вызовы, но с обещаниями: session.call сразу возвращает объект, который оформляется в момент возврата вызова оформляется, а затем выполняется хэндлер- функция.
Для простых случаев использования WAMP и Autobahn|JS вам не надо ничего знать про обещания. Можете считать их другой записью обратных вызовов.
Подписка и отправка обновлений Что насчёт обновления остальных клиентов? Для получения обновлений клиенту надо сообщить роутеру, в какой информации он нуждается. Для этого: session.subscribe ('com.example.votedemo.on_vote', updateVotes); Мы передаём тему и функцию, которая будет вызываться каждый раз по получению информации.
Осталось лишь настроить отправку обновлений с сервера. Создаём объект для отправки и публикации информации по нужной нам теме. Эту функциональность мы добавим в ранее зарегистрированную submitVote:
var submitVote = function (args, kwargs, details) { var flavor = args[0]; votes[flavor] += 1;
var res = { subject: flavor, votes: votes[flavor] };
session.publish ('com.example.votedemo.on_vote', [res]);
return votes[flavor]; }; На этом всё: отправка голосов на бэкенд и обновления голосов для всех подсоединённых браузеров работают на одном протоколе.
Итог WAMP унифицирует передачу сообщений. RPC и PubSub должно хватить для всех задач приложения. Работает протокол через WebSockets, быстрое, одиночное и двунаправленное соединение с сервером. Поскольку протокол WAMP открыт, и уже существуют его реализации для разных языков, вы вольны выбирать технологию для использования на бэкенде и даже писать приложения для нативных клиентов, а не только для web.Примечания «Vote», Crossbar.io — действующая версия голосовалки«Why WAMP?», WAMP — пояснения по разработке протокола
«Free Your Code: Backends in the Browser,» Alexander Gödde, Tavendo — статья на тему того, как симметрия протокола влияет на деплой
«WebSockets: Why, What and Can I Use It?», Alexander Gödde, Tavendo — обзор WebSockets
«WAMP Compared», WAMP — сравнение протокола с другими
Crossbar.io — введение в использования универсального роутера для приложений