...

четверг, 9 апреля 2015 г.

[Перевод] Почему одного 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="http://ift.tt/1sue1aC"></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 – введение в использования универсального роутера для приложений


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий