Reverse Ajax: Часть 1. Знакомство с концепцией Comet

Использование потоков и ждущего опроса для организации быстрореагирующего соединения между сервером и клиентом

В этом цикле статей говорится о том, как разработать событийно-управляемое Web-приложение с использованием методов Reverse Ajax, чтобы сделать интерфейс более удобным для пользователя. В примерах клиентской части используется библиотека JavaScript JQuery. В этой первой статье мы рассмотрим различные методы Reverse Ajax. Загружаемые примеры иллюстрируют концепцию Comet с использованием методов потоков и ждущего опроса.

Матье Карбу, Java Web-архитектор, Ovea

Фото Матье КарбуМатье Карбу (Mathieu Carbou), Java Web-архитектор и консультант компании Ovea, предоставляет услуги и разрабатывает решения. Он коммиттер и руководитель нескольких проектов по разработке ПО с открытым исходным кодом, докладчик и глава монреальской группы пользователей Java. Матье обладает богатым практическим опытом программирования и специализируется на разработке событийно-управляемых Web-программ для клиентских и серверных систем, а также на решениях для высокомасштабируемых Web-приложений, управляемых событиями и сообщениями. Познакомьтесь с его блогом.



02.05.2012

Введение

За последние несколько лет Web-разработка претерпела значительные изменения. Мы ушли от связанных друг с другом статических Web-страниц, которые вызывали обновление окна браузера, заставляя дожидаться окончания процесса загрузки. Сегодня востребованы полностью динамические приложения, доступные через Web. Эти приложения должны реагировать как можно быстрее и содержать компоненты, работающие практически в режиме реального времени. В этом цикле из пяти статей говорится о том, как разработать событийно-управляемое Web-приложение с использованием методов Reverse Ajax.

В первой статье мы остановимся на методах Reverse Ajax, опросах, потоках, Comet и ждущем опросе. Мы покажем, как реализовать различные соединения Reverse Ajax, а также рассмотрим преимущества и недостатки каждого метода. Загрузив исходный код, можно будет следить за примерами по ходу статьи.


Ajax, Reverse Ajax и WebSockets

Доступная в JavaScript функция браузера Asynchronous JavaScript и XML (Ajax) позволяет сценариям незаметно обращаться к Web-сайту с HTTP-запросами без перезагрузки страницы. Ajax применяется уже более десятилетия. Хотя в название технологии фигурирует XML, Ajax позволяет делать почти любые запросы. Чаще всего используются данные JSON, которые по синтаксису близки к JavaScript и создают меньше трафика. Листинг 1 содержит пример Ajax-запроса для определения географического названия по почтовому индексу.

Листинг 1. Пример Ajax-запроса
var url = 'http://www.geonames.org/postalCodeLookupJSON?postalcode=' 
    + $('#postalCode').val() + '&country=' 
    + $('#country').val() + '&callback=?'; 
$.getJSON(url, function(data) { 
    $('#placeName').val(data.postalcodes[0].placeName); 
});

Этот пример есть в файле listing1.html загружаемого исходного кода для этой статьи.

Reverse Ajax ― это, по существу, концепция: способ передачи данных от сервера к клиенту. В стандартном Ajax-запросе HTTP данные передаются на сервер. Reverse Ajax можно представить как способ передачи специальных Ajax-запросов, варианты которых описаны в этой статье, в ответ на которые сервер как можно быстрее (с малой задержкой связи) доставляет события клиенту.

WebSockets ― это совсем новый подход, который входит в HTML5. Его уже поддерживают многие браузеры (Firefox, Google Chrome, Safari и другие). WebSockets допускает создание двунаправленных, дуплексных каналов связи. Соединение открывается посредством определенного HTTP-запроса со специальными заголовками, который называется рукопожатием WebSockets. Установленное соединение сохраняется постоянно, и в него можно записывать и получать данные посредством JavaScript, как при использовании сокета TCP. WebSockets будет подробно рассматриваться во второй статье этого цикла.


Методы Reverse Ajax

Задачей Reverse Ajax является предоставление серверу возможности передавать информацию клиенту. По умолчанию Ajax-запросы не хранят информацию о соединении и могут открываться только от клиента к серверу. Это ограничение можно обойти с помощью методов имитации реакции на соединение между сервером и клиентом.

HTTP-опрос и JSONP-опрос

Опрос (polling) предусматривает обращение клиента за информацией к серверу. Очевидно, что это просто Ajax-запрос HTTP. Чтобы узнавать о событиях сервера как можно скорее, интервал опроса (время между запросами) должен быть как можно меньше. Но если этот интервал слишком мал, браузер клиента будет выдавать множество запросов, большинство из которых не возвращает никаких полезных данных, и ресурсы процессора и сети будут расходоваться впустую.

Из временной диаграммы (Рисунок 1) видно, что некоторые запросы клиента не возвращают никакой информации. Чтобы получить информацию о двух событиях, случившихся на сервере, клиент должен дожидаться следующего опроса.

Рисунок 1. Reverse Ajax с HTTP-опросом
На диаграмме изображены HTTP-запросы в рамках Ajax-опроса, которые передаются каждые 5 секунд. Первый ответ не содержит данных. Затем на стороне сервера происходят два события. Чтобы доставить их клиенту, нужно дождаться второго опроса, который начнется через 4 секунды. Затем третий опрос опять ничего не возвращает, поскольку никаких событий не произошло.

JSONP-опрос, по существу, не отличается от HTTP-опроса. Разница лишь в том, что в случае JSONP можно создавать междоменные запросы (запросы к чужому домену). В листинге 1 JSONP используется для определения места по почтовому индексу. JSONP-запрос обычно можно узнать по параметру обратного вызова и возвращаемым данным, которые представляют собой исполняемый код JavaScript.

В JavaScript для реализации опроса можно использовать команду setInterval, чтобы периодически подавать Ajax-запросы, как показано в листинге 2.

Листинг 2. Опрос JavaScript
setInterval(function() { 
    $.getJSON('events', function(events) { 
        console.log(events); 
    }); 
}, 2000);

Пример опроса в исходном коде для этой статьи содержит трафик, создаваемый этим методом. Интервал опроса короткий, но некоторые запросы не возвращают никаких событий. В листинге 3 показан результат выполнения примера опроса.

Листинг 3. Результат выполнения примера опроса
[client] проверка наличия событий... 
[client] событий нет 
[client] проверка наличия событий... 
[client] 2 события 
[event] В ВС 5 июня 15:17:14 EDT 2011 
[event] В ВС 5 июня 15:17:14 EDT 2011 
[client] проверка наличия событий... 
[client] 1 событие 
[event] В ВС 5 июня 15:17:16 EDT 2011

Опрос JavaScript имеет свои преимущества и недостатки.

  • Преимущества: он очень прост в реализации, не требует никаких специальных функций со стороны сервера и работает во всех браузерах.
  • Недостаток: этот метод редко используется, поскольку он немасштабируем. Представьте себе, сколько ресурсов будет потеряно при наличии 100 клиентов, каждый из которых выдает запросы с интервалом 2 секунды, когда 30% запросов не возвращает никаких данных.

Комбинированный опрос

Комбинированный опрос ― гораздо более рациональный метод, поскольку он исключает все ненужные запросы (те, что не возвращают никаких данных). Интервала нет, запросы посылаются тогда, когда клиенту необходимо обратиться с запросом к серверу. Разница заключается в ответе, который делится на две части: отправка запрошенных данных и событий сервера, если таковые имели место. На рисунке 2 приведен пример.

Рисунок 2. Reverse Ajax с комбинированным опросом
Diagram depicts the client sending a post request and getting the mixed response. No events occured on the server, so the mixed response only contains the response of the request. Two events then arrive on the server side, but they need to wait for the next client request so that the mixed response will contain both the events plus the normal request's response. If the client doesn't trigger any action, then the events arrived on server won't be retrieved.

При реализации комбинированного метода все Ajax-запросы, адресованные серверу, как правило, могут возвращать смешанные ответы. Пример реализации приведен в загружаемом коде для этой статьи и в листинге 4.

Листинг 4. Пример комбинированного запроса
$('#submit').click(function() { 
    $.post('ajax', function(data) { 
        var valid = data.formValid; 
        // Обработка результатов проверки  
        // Обработка остальной части ответа (событий) 
        processEvents(data.events); 
    }); 
});

Листинг 5 содержит пример комбинированных выходных данных.

Листинг 5. Пример комбинированных выходных данных
[client] проверка наличия событий... 
[server] проверка формы ? в порядке 
[client] 4 события 
[event] В ВС 5 июня 16:08:32 EDT 2011 
[event] В ВС 5 июня 16:08:34 EDT 2011 
[event] В ВС 5 июня 16:08:34 EDT 2011 
[event] В ВС 5 июня 16:08:37 EDT 2011

Как видите, в ответ добавлены результат проверки формы и события. У этого метода тоже есть свои преимущества и недостатки.

  • Преимущества: при отсутствии запросов никакие данные не возвращаются, так как клиент сам определяет, когда посылать запросы; в результате потребляется меньше ресурсов. К тому же метод работает во всех браузерах и не требует специальных функций на стороне сервера.
  • Недостаток: неизвестно, когда события, накопленные на сервере, будут переданы клиенту, поскольку это делается по запросу клиента.

Comet

Методы Reverse Ajax с опросом или комбинированным опросом очень ограниченны: они не масштабируются и не обеспечивают быстрой реакции (когда события отражались в браузере, как только они произошли на сервере). Comet - это модель Web-приложения, в которой запрос отправляется на сервер и сохраняется в нем в течение длительного времени, пока не сработает таймер или не произойдет событие на сервере. Когда запрос выполнен, отправляется следующий долгоживущий Ajax-запрос в ожидании других событий на сервере. Comet позволяет Web-серверу отправлять данные клиенту без явного запроса с его стороны.

Большим преимуществом Comet является то, что у каждого клиента всегда имеется действующая линия связи с сервером. Сервер может передавать клиентам события, как только они случаются, или же накапливать и передавать их пакетом. Так как запрос продолжает действовать в течение длительного времени, на стороне сервера требуются специальные средства для обработки всех этих долгоживущих запросов. Пример приведен на рисунке 3. (Во второй статье этого цикла будут подробнее рассмотрены ограничения со стороны сервера.)

Рисунок 3. Reverse Ajax с Comet
Diagram depicts Comet using long-polling (not streaming). An Ajax request is made to the server. The server suspends it (thus this request remains active and no response is completed yet). The client can send other Ajax requests to the server to get/post some data. At some time, an event occurs on the server side; the suspended request is then committed immediately (the response is sent to the client). The client thus receives the response of the suspended Ajax request and can launch another one.

Все реализации метода Comet можно разделить на два типа: с использованием потокового режима и с использованием ждущего опроса.


Comet с использованием HTTP-потока

В потоковом режиме открыто одно постоянное соединение. Существует только один долгоживущий запрос (№1 на рисунке 3), так как каждое событие, происходящее на стороне сервера, передается через одно и то же соединение. Поэтому на стороне клиента нужен способ разделения разных ответов, приходящих через одно соединение. С технической точки зрения существуют два общепринятых потоковых метода ― это Forever Iframes (скрытый Iframes) и составной компонент объекта XMLHttpRequest, используемого для создания Ajax-запросов в JavaScript.

Forever Iframes

При использовании метода Forever Iframes на странице помещается скрытый тег Iframe, атрибут SRC которого указывает путь к сервлету, возвращающему события от сервера. Каждый раз, когда принимается событие, сервлет составляет и выдает новый тег сценария с кодом JavaScript. Этот тег добавляется к содержанию iframe и выполняется.

  • Преимущества: метод прост в реализации и работает во всех браузерах, поддерживающих фреймы iframe.
  • Недостатки: нет способа надежной обработки ошибок и отслеживания состояния соединения, так как всеми соединениями и данными управляет браузер через HTML-теги. Поэтому, если соединение прервется с одной из сторон, вторая сторона об этом не узнает.

Составной компонент XMLHttpRequest

Второй способ, более надежный, заключается в использовании флага multi-part, поддерживаемого некоторыми браузерами (например, Firefox) в объекте XMLHttpRequest. Ajax-запрос передается и остается открытым на сервере. Когда происходит событие, через то же соединение передается ответ из нескольких частей. Пример приведен в листинге 6.

Листинг 6. Пример кода JavaScript для создания составного потокового запроса
var xhr = $.ajaxSettings.xhr(); 
xhr.multipart = true; 
xhr.open('GET', 'ajax', true); 
xhr.onreadystatechange = function() { 
    if (xhr.readyState == 4) { 
        processEvents($.parseJSON(xhr.responseText)); 
    } 
}; 
xhr.send(null);

На стороне сервера все немного сложнее. Сначала нужно настроить составной запрос, а затем задержать соединение. В листинге 7 показано, как задержать потоковый HTTP-запрос. (Подробнее об API рассказывается в третьей статье этого цикла.)

Листинг 7. Приостановка потокового HTTP-запроса в сервлете с использованием API Servlet 3
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
    // начало приостановки запроса
    AsyncContext asyncContext = req.startAsync(); 
    asyncContext.setTimeout(0); 

    //передача клиенту разделителя частей
    resp.setContentType("multipart/x-mixed-replace;boundary=\""
        + boundary + "\""); 
    resp.setHeader("Connection", "keep-alive"); 
    resp.getOutputStream().print("--" + boundary); 
    resp.flushBuffer(); 

    // включение асинхронного контекста в список для последующего использования
    asyncContexts.offer(asyncContext); 
}

Теперь каждый раз, когда происходит событие, можно перебрать все задержанные соединения и записать в них данные, как показано в листинге 8.

Листинг 8. Отправка событий в задержанный составной запрос с помощью API Servlet 3
for (AsyncContext asyncContext : asyncContexts) { 
    HttpServletResponse peer = (HttpServletResponse) 
        asyncContext.getResponse(); 
    peer.getOutputStream().println("Content-Type: application/json"); 
    peer.getOutputStream().println(); 
    peer.getOutputStream().println(new JSONArray()
        .put("At " + new Date()).toString()); 
    peer.getOutputStream().println("--" + boundary); 
    peer.flushBuffer(); 
}

Файлы из загрузки для этой статьи в папке Comet-streaming демонстрируют потоковое HTTP-соединение. Если запустить пример и открыть домашнюю страницу, вы увидите, что события отображаются сразу, как только они происходят на сервере. А если открыть консоль Firebug, то можно увидеть, что открыт только один Ajax-запрос. На вкладке Response видны JSON-ответы, как показано на рисунке 4.

Рисунок 4. Отображение потокового HTTP-запроса в консоли FireBug
Screenshot of FireBug view of an HTTP streaming request. The FireBug plug-in in Firefox shows the successive response in the same Ajax streaming request.

Как обычно, у этого метода есть свои преимущества и недостатки.

  • Преимущества: открыто только одно постоянное соединение. Метод Comet меньше всех загружает канал.
  • Недостаток: флаг multi-part поддерживается не всеми браузерами. Некоторые популярные библиотеки, такие как CometD в Java, выдают ошибку буферизации. Например, блоки данных могут запоминаться и передаваться только тогда, когда соединение завершено или буфер заполнен, а это может привести к большей, чем ожидалось, задержке.

Comet с использованием ждущего HTTP-опроса

В режиме ждущего опроса специальный механизм открывает соединение. Сервер оставляет соединение открытым, и как только происходит событие, ответ передается, и соединение закрывается. Клиент немедленно открывает новое соединение ждущего опроса и ждет новых событий.

Ждущий HTTP-опрос можно реализовать с помощью тегов сценария или просто объекта XMLHttpRequest.

Теги сценария

Как и в случае iframes, на страницу нужно добавить тег сценария, который будет выполняется. Сервер приостановит соединение до события, отправит содержание сценария в браузер, а затем откроет другой тег сценария для получения следующих событий.

  • Преимущества: так как этот метод основан на HTML-тегах, он очень прост в реализации и работает между доменами (XMLHttpRequest по умолчанию не допускает запросы к другим доменам или поддоменам).
  • Недостатки: подобно методу iframe, отсутствует обработка ошибок, и нельзя получить информацию о состоянии соединения или прервать его.

Ждущий опрос XMLHttpRequest

Второй и рекомендуемый метод реализации Comet заключается в том, чтобы открыть Ajax-запрос на сервере и ждать ответа. Чтобы запрос можно было задержать, серверу требуются некоторые специальные функции. Как только происходит событие, сервер отправляет ответ на задержанный запрос и закрывает его, точно так же, как закрывается выходной поток ответа сервлета. Клиент получает ответ и открывает новый задержанный Ajax-запрос к серверу, как показано в листинге 9.

Листинг 9. Пример кода JavaScript для организации задержанных запросов
function long_polling() { 
    $.getJSON('ajax', function(events) { 
        processEvents(events); 
        long_polling(); 
    }); 
} 

long_polling();

Как и в случае HTTP-потока, в коде для сервера, чтобы задержать запрос, используется API Servlet 3, но никакой код для управления составным запросом не требуется. См. листинг 10.

Листинг 10. Задержка ждущего Ajax-запроса
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
    AsyncContext asyncContext = req.startAsync(); 
    asyncContext.setTimeout(0); 
    asyncContexts.offer(asyncContext); 
}

Когда событие получено, достаточно просто завершить все задержанные запросы, как показано в листинге 11.

Листинг 11. Завершение ждущего Ajax-запроса по событию
while (!asyncContexts.isEmpty()) { 
    AsyncContext asyncContext = asyncContexts.poll(); 
    HttpServletResponse peer = (HttpServletResponse) 
        asyncContext.getResponse(); 
    peer.getWriter().write(
        new JSONArray().put("At " + new Date()).toString()); 
    peer.setStatus(HttpServletResponse.SC_OK); 
    peer.setContentType("application/json"); 
    asyncContext.complete(); 
}

В файлах для загрузки папка comet-long-polling содержит пример Web-приложения со ждущим запросом, которое можно выполнить с помощью команды mvn jetty:run.

  • Преимущества: легко реализуется на стороне клиента с хорошей системой обработки ошибок и управления тайм-аутом. Этот надежный метод также допускает циклический перебор соединений на стороне сервера, так как соединения не постоянны (это полезно, когда у приложения много клиентов). К тому же он работает во всех браузерах; используется только объект XMLHttpRequest путем подачи простого Ajax-запроса.
  • Недостаток: существенных недостатков по сравнению с другими методами нет. Но, как и все методы, которые мы обсудили, этот метод опирается на HTTP-соединение, для задержки которого требуются специальные функции на стороне сервера.

Рекомендации

Поскольку все современные браузеры поддерживают спецификацию Cross-Origin Resource Sharing (CORS), что позволяет XHR выполнять кросс-доменные запросы, необходимость в методах на основе сценариев и на основе iframe отпадает.

Лучший способ реализовать и использовать концепцию Comet для Reverse Ajax ― это объект XMLHttpRequest, который обеспечивает полноценное управление соединением и обработку ошибок. Учитывая, что не все браузеры поддерживают флаг составных запросов, а составные потоки могут вызвать проблемы буферизации, рекомендуется использовать Comet посредством ждущего HTTP-запроса с объектом XMLHttpRequest (простой запрос Ajax, который задерживается на стороне сервера). Этот метод поддерживают все браузеры, поддерживающие Ajax.


Заключение

Эта статья служит введением в методы Reverse Ajax. В ней исследуются различные способы реализации связи Reverse Ajax и приводятся преимущества и недостатки каждого из них. Какой метод окажется наилучшим - зависит от конкретной ситуации и требований приложения. Однако в общем случае наилучший компромисс обеспечивает Comet с ждущими Ajax-запросами: связь с короткой задержкой, тайм-аут и обнаружение ошибок, простота и хорошая поддержка всеми браузерами и платформами.

Во второй статье этого цикла говорится о третьем механизме Reverse Ajax: WebSockets. Хотя его пока еще поддерживают не все браузеры, WebSockets, безусловно, станет очень хорошим средством связи для Reverse Ajax. WebSockets снимает все ограничения, характерные для HTTP-соединения без запоминания состояния. Во второй части рассматриваются также ограничения со стороны сервера, связанные с механизмами Comet и WebSockets.


Загрузка

ОписаниеИмяРазмер
Исходный код примера для статьиreverse_ajaxpt1_source.zip17 КБ

Ресурсы

Научиться

  • Оригинал статьи
  • Читайте в Википедии об:
  • Exploring Reverse AJAX (блог Google Maps .Net Control, август 2006 г.): введение в некоторые методы Reverse Ajax.
  • Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups (developerWorks, февраль 2009 г.): как сочетать метод скрытого междоменного вызова (JSONP) с гибкой библиотекой JavaScript (JQuery) для создания мощных и на удивление быстрых гибридных приложений.
  • Спецификация Cross-Origin Resource Sharing (CORS) (W3C, июль 2010 г.): подробнее о механизме, который позволяет XHR выполнять кросс-доменные запросы.
  • Build Ajax applications with Ext JS (developerWorks, июль 2008 г.): обзор концепций объектно-ориентированного JavaScript-программирования, стоящих за Ext JS, и способов применения среды Ext JS для элементов пользовательского интерфейса многофункциональных интернет-приложений.
  • Compare JavaScript frameworks (developerWorks, февраль 2010 г.): обзор интегрированных сред разработки, которые значительно упрощают разработку JavaScript.
  • Mastering Ajax, Part 2: Make asynchronous requests with JavaScript and Ajax (developerWorks, январь 2006 г.): как использовать Ajax и объект XMLHttpRequest для создания модели запрос/ответ, которая никогда не заставляет пользователей ждать реакции сервера.
  • Create Ajax applications for the mobile Web (developerWorks, март 2010 г.): как создавать не зависящие от браузера Web-приложения для смартфона с использованием Ajax.
  • Where and when to use Ajax in your applications (developerWorks, февраль 2008 г.): как использовать Ajax для совершенствования Web-сайтов, избегая неудобств для пользователей.
  • Improve the performance of Web 2.0 applications (developerWorks, декабрь 2009 г.): различные кэш-механизмы на стороне браузера.
  • Introducing JSON (JSON.org): введение в синтаксис JSON.
  • Подкасты developerWorks: интересные интервью и дискуссии разработчиков программного обеспечения.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, SOA и web-сервисы
ArticleID=812711
ArticleTitle=Reverse Ajax: Часть 1. Знакомство с концепцией Comet
publish-date=05022012