Почему одного AJAX недостаточно: протокол WAMP

от автора

AJAX-вызовы вывели работу web на новый уровень. Уже не нужно перезагружать страницу в ответ на каждый ввод информации пользователем. Теперь возможно отправлять вызовы на сервер и обновлять страницу на основании полученных ответов. Это ускоряет работу интерактивного интерфейса.

А вот что AJAX не обеспечивает – так это обновления с сервера, которые необходимы для работы приложения в реальном времени. Это могут быть приложения, в которых пользователи одновременно редактируют один документ, или уведомления, рассылаемые миллионам читателей новостей. Необходим ещё один шаблон для рассылки сообщений, в дополнение к запросам AJAX, который бы работал в разных масштабах. Для этого традиционно используется шаблон PubSub («publish and subscribe», «публикация и подписка»).

Какую задачу решил AJAX

До появления AJAX интерактивные взаимодействия со страницей были тяжеловесными. Каждое из них требовало перезагрузки страницы, которая создавалась на сервере. В этой модели основной единицей взаимодействия была страница. Неважно, какой объём информации отправлялся из браузера на сервер – результатом была полностью обновлённая страница. Это была трата как трафика, так и серверных ресурсов. И это было медленно и неудобно для пользователей.

AJAX решил проблему, разбивая всё на части: стало возможным отправить данные, получить конкретный результат и обновить лишь часть страницы, имеющую к этому отношение. От вызова «дай мне новую страницу» мы перешли к конкретным запросам данных. У нас появилась возможность делать вызовы удалённых процедур (RPC).

Рассмотрим простой пример веб-голосования:

image

С использованием 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 обновляет страницы только в ответ на действия пользователя. Он не решает задачи обработки апдейтов, идущих с сервера. Он не предлагает способа делать то, что нам нужно: передавать информацию с сервера в браузер. Для этого необходим шаблон передачи сообщений, который отправляет обновления на клиент без участия пользователя и без необходимости для клиента постоянно опрашивать сервер.

image

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 на сервере.

image

Библиотеки WAMP можно использовать не только на бэкенде, но и для нативных клиентов, позволяя сочетать web и клиентов, работающих на одном протоколе. Библиотека на C++ хорошо приспособлена для запуска WAMP-компонент на устройствах с ограниченными ресурсами.

Соединения происходят не от браузера к бэкенду, а через WAMP-роутер, распространяющий сообщения. Для PubSub он играет роль сервера – ваш сервер публикует сообщение для роутера, а он уже распространяет его. Для RPC фронтенд отправляет запрос на удалённую процедуру на роутер, а он переадресовывает её на бэкенд, и затем возвращает результат.

Посмотрим, как решить нашу задачу с голосовалкой при помощи WAMP.

Живое обновление голосовалки: WebSockets и WAMP

Для простоты наш бэкенд будет также написан на JS и будет работать в другой закладке. Браузерный бэкенд возможнен потому, что браузерные клиенты могут регистрировать процедуры для удалённого вызова так же, как и любой другой WAMP-клиент.

Код для демки лежит на GitHub, вместе с инструкциями по запуску. В качестве роутера используется Crossbar.io.

Подключение библиотеки WAMP

Для начала подключим библиотеку Autobahn|JS.

В целях демонстрации её можно подключить так:

<script src="https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz"></script>; 
Устанавливаем соединение

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 – введение в использования универсального роутера для приложений

ссылка на оригинал статьи http://habrahabr.ru/post/255327/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *