Статьи

Асинхронный Ajax для революционных веб-приложений

Что, если небольшое изменение в том, как мы используем Интернет, может позволить нам превратить каждое приложение в новый инструмент коммуникации? Что если бы пользователи могли общаться друг с другом напрямую через наши приложения и получать более своевременную информацию, позволяющую людям принимать лучшие бизнес-решения. Эй, разве Аякс уже не «асинхронный»? Не совсем; нам нужен Ajax Push.

Чтобы понять Ajax Push (по-разному называемый «Комета» или «Обратный Ajax»), необходимо охватить множество тем. После установки этапа с практическим определением Web 2.0 мы продолжим объяснение Ajax Push на проводе, продолжим, чтобы увидеть влияние на сервер и какие серверные API-интерфейсы использовать, а затем закончим с методами разработки приложений.

По

Web 2.0 и асинхронная революция

Имеет ли «Web 2.0» полезную интерпретацию, которая позволит нам создавать лучшие приложения, или это просто торговая марка O’Reilly? Чтобы извлечь полезное определение, нам нужно взглянуть на некоторые приложения, которые обычно считаются относящимися к категории «Web 2.0», такие приложения, как eBay, Blogger, YouTube и Flickr. У этих приложений есть ряд общих характеристик, но мы хотим сосредоточиться на том, что они «созданы» их пользователями.

Теперь давайте посмотрим более внимательно на eBay. Интересен ли eBay, потому что у eBay есть куча вещей, которые можно продать нам, или eBay интересен, потому что его пользователи выставляют предметы на аукцион и выставляют на них торги? Понятно, что приложение eBay создается совместно его пользователями. eBay — это многопользовательская система совместной работы, в которой приложение eBay создается при участии его пользователей. Как сказал Джонатан Шварц на JavaOne в 2005 году, мы выходим из «эпохи информации» и в «эпоху участия». Он намеревался, что программное обеспечение в настоящее время разрабатывается совместно с открытым исходным кодом, но комментарий в равной степени относится и к сети в целом.

Веб больше не является тем, что какое-то учреждение размещает свои документы на общедоступном файловом сервере и позволяет другим людям получать их. Теперь в Интернете все о пользователях, создающих приложения вместе с их вкладом. Мы публикуем аукционы. Мы публикуем статьи в Википедии. Мы делаем записи в блогах, публикуем фотографии и фильмы. Все эти вещи, предоставленные пользователями, создают веб. Другими словами, сеть в целом превращается в среду сотрудничества.

Но если мы перейдем к отдельной странице eBay, где мы рассмотрим интересный предмет аукциона, у нас действительно будет чуть больше распечатки на экране. Другие пользователи могут делать ставки на этот товар, но мы не знаем об этом, пока не нажмем «обновить». Сама веб-страница не является совместной или многопользовательской.

Ajax помогает улучшить взаимодействие с пользователем, но он по-прежнему не позволяет нам видеть, что в это время делают другие пользователи; сетевые операции по-прежнему синхронизируются с потоком пользовательских событий. Чтобы создать приложение для совместной работы, нам нужна полная реализация Ajax или «Ajax Push», где у нас есть асинхронный канал от сервера к браузеру, который позволяет нам обновлять страницу в любое время.

Как мы можем использовать такой асинхронный канал связи для создания более интересного приложения, которое позволяет пользователям сотрудничать?

Представьте, что у нас есть два пользователя приложения для создания слайд-шоу: модератор Yoda и зритель Luke. Йода нажимает кнопку, чтобы продвинуть слайд, тем самым отправляя пользовательское событие на сервер. Сервер обрабатывает это пользовательское событие и, используя свою функцию push-уведомлений, выдает смену слайда Люку на другом компьютере. Другими словами, возможность push позволила нам превратить веб-приложение в инструмент коммуникации нового типа. В этом случае новой формой общения является многопользовательское слайд-шоу.


[Img_assist | NID = 5916 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 442 | Высота = 244]

Теперь революционная природа этого должна быть ясна; Если мы посмотрим в историю на технологии, меняющие мир, то увидим, что многие из них были изобретениями, которые позволяли людям общаться друг с другом по-разному: подвижный тип, радио, телевидение, и это лишь некоторые из них. Если у нас есть способ превратить каждое веб-приложение в новый, уникальный инструмент коммуникации, мы стоим на пороге новой революции. Если вы сделаете шаг назад и подумаете о приложении, над которым вы работаете сегодня, вы поймете, что есть несколько очень интересных способов добавить push-возможности и многопользовательское взаимодействие.

Например, в будущем мы увидим, что чат, форумы и блоги объединяются в одно гибридное приложение. Рассмотрите возможность предоставления блога с ответами, обновляемыми в режиме реального времени. Это превратит систему блогов в систему чатов и улучшит способность людей общаться друг с другом через нее.

Давайте посмотрим, что нам нужно, чтобы сделать революцию реальностью.

Ajax Push на проводе

Существует три распространенных метода реализации протокола Push поверх HTTP: опрос, длинный опрос и потоковая передача. Давайте рассмотрим каждый по очереди.


[Img_assist | NID = 5917 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 487 | высота = 221]

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

Длительный опрос (или «блокирование HTTP») использует задержанный ответ на сервере, чтобы отправить сообщение в нужное время. Браузер выполняет HTTP-запрос для сообщения, и сервер просто задерживает до тех пор, пока сообщение не станет доступным, после чего процесс повторяется, и браузер снова делает запрос на сообщение. Это эффективно инвертирует поток сообщений HTTP. Поскольку медленный сервер не может быть запрещен спецификацией HTTP, этот метод хорошо работает через прокси и брандмауэры. Единственный недостаток — это сетевые издержки, требуемые запросами сообщений, но это можно уменьшить, накапливая сообщения на сервере и отправляя их партиями.

Потоковая передача HTTP является наиболее эффективной на уровне TCP, но, к сожалению, сталкивается с некоторыми сложностями с прокси и JavaScript API. Потенциальная проблема с HTTP-прокси заключается в том, что (в зависимости от конфигурации) он может выбрать буферизацию HTTP-ответа до его завершения. В случае потоковой передачи ответ не завершается до тех пор, пока приложение не завершится, в результате чего сообщения в браузер не доставляются. Другая проблема заключается в том, что JavaScript не предоставляет API read (), поэтому для чтения из потока необходимо собрать весь поток в виде буфера в памяти, что по существу становится утечкой памяти в браузере.

Для справки, ICEfaces использует метод блокировки HTTP. Операцию можно описать очень конкретно, поскольку сообщения в ICEfaces содержат только обновления страницы: когда браузер загружает страницу ICEfaces с сервера, он выполняет некоторый JavaScript, первая операция которого заключается в запросе любых обновлений страницы. Если в это время обновления страницы отсутствуют, сервер просто ждет. Когда обновление страницы готово, например, когда пользователь ввел сообщение чата, сервер отвечает связанными изменениями страницы. Затем браузер снова спрашивает, и цикл продолжается.

Ajax Push на сервере

Имея в наличии проводной протокол, мы готовы перейти к серверу, но две предпочитаемые технологии push не подходят для текущего API Servlet 2.5. Давайте рассмотрим проблему более подробно и рассмотрим некоторые из различных серверных API, которые предлагают решения.


[Img_assist | NID = 5918 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 437 | Высота = 254]

В Servlet 2.5 сохранение HTTP-ответа означает, что он никогда не выйдет из метода service (), тем самым потребляя поток. Так как нам нужно push-соединение для каждого пользователя, это заставляет нас использовать поток на сервере для каждого отдельного пользователя. Это не очень хорошо для масштабируемости.

Вместо этого нам нужно обработать несколько запросов и ответов с помощью небольшого пула потоков. Давайте рассмотрим некоторые из различных вариантов этого, доступных на Jetty, Tomcat, Resin, WebLogic и GlassFish, и немного заглянем в будущее с Servlet 3.0.

пристань

Jetty позволяет нам приостановить обработку запроса с помощью Continuation. Это не полное продолжение языка Java (аналогично тому, чтобы сделать Thread Serializable), но оно специфично для обработки запросов. При вызове продолжения.suspend () будет выдано исключение, которое будет перехвачено Jetty, в результате чего пара запрос / ответ будет связана с этим продолжением. Затем, скажем, когда пришло время обновить страницу с помощью сообщения чата, continueation.resume () вызовет метод service () еще раз с тем же запросом и ответом. Немного необычно повторно вводить метод обслуживания с теми же объектами, но преимущество заключается в том, что Servlet API не требует изменений.


import org.mortbay.util.ajax.Continuation;

service(request, response) {
Continuation continuation = ContinuationSupport
.getContinuation(request, this);
...
continuation.suspend();
response.getWriter().write(message);
}

To cause the request processing to be resumed when a chat message is ready:

message.setValue("Howdy");
continuation.resume();

Кот

API Tomcat 6, возможно, ближе всего к основам NIO асинхронной обработки запросов. Интерфейс Tomcat CometProcessor должен быть реализован сервлетом, обрабатывающим push-взаимодействие, но, как только он будет создан, доступны различные события, и запрос и ответ могут быть обработаны произвольным потоком.

import org.apache.catalina.CometProcessor;

public class Processor implements CometProcessor {

public void event(CometEvent event) {
request = event.getHttpServletRequest();
response = event.getHttpServletResponse();

if (event.getEventType() == EventType.BEGIN) { ...
if (event.getEventType() == EventType.READ) { ...
if (event.getEventType() == EventType.END) { ...
if (event.getEventType() == EventType.ERROR) { ...
}

Позже, когда пришло время обновить страницу с помощью сообщения чата, event.close () вызывает завершение ответа и его сброс в браузер.

message.setValue("Howdy");
response.getWriter().write(message);
event.close();

смола

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

public class CometServlet extends GenericCometServlet {
public boolean service(ServletRequest request,
ServletResponse response,
CometController cometController)
...
return true;
}

public boolean resume(ServletRequest request,
ServletResponse response,
CometController cometController)
PrintWriter out = res.getWriter();
out.write(message);
return false;
}

Позже, когда пришло время обновить страницу с помощью сообщения чата, cometController.wake () вызывает метод resume (), и ответ сбрасывается в браузер.

message.setValue("Howdy");
cometController.wake();

WebLogic

WebLogic похож на смолу, но наоборот. Возвращение false из doRequest () указывает, что обработка запроса не должна продолжаться.

import weblogic.servlet.http.AbstractAsyncServlet;
import weblogic.servlet.http.RequestResponseKey;

class Async extends AbstractAsyncServlet {

public boolean doRequest(RequestResponseKey rrk) {
... = rrk;
return false;
}

public void doResponse(RequestResponseKey rrk, Object message) {
rrk.getResponse().getWriter.write(message);
}

Позже, когда пришло время обновить страницу с помощью сообщения чата, AbstractAsyncServlet.notify () вызывает doResponse (), и ответ сбрасывается в браузер.

message.setValue("Howdy");
AbstractAsyncServlet.notify(rrk, message);

Стеклянная рыба

GlassFish предоставляет наиболее гибкий API, и одной из его сильных сторон является то, что асинхронная возможность может быть обнаружена и использована во время выполнения. Когда addCometHandler () вызывается для cometContext, запрос приостанавливается.

    CometContext context =
CometEngine.getEngine().register(contextPath);
context.setExpirationDelay(20 * 1000);
SuspendableHandler handler = new SuspendableHandler();
handler.attach(response);
cometContext.addCometHandler(handler);

class SuspendableHandler implements CometHandler {
public void onEvent(CometEvent event) {
response.getWriter().println(event.attachment());
cometContext.resumeCometHandler(this);
}

Позже, когда пришло время обновить страницу с помощью сообщения чата, cometContext.notify () можно использовать в качестве механизма событий, чтобы упростить разработку приложения, но ключевой метод — cometContext.resumeCometHandler (), который вызывает сброс ответа на браузер

message.setValue("Howdy");
cometContext.notify(message);

Кроме того, GlassFish предоставляет гарантию доставки и API-интерфейсы качества обслуживания.

атмосфера

 

Atmosphere (mosp.dev.java.net) — это инфраструктура, которая скрывает реализацию Comet / Ajax Push для конкретного контейнера, стремясь упростить разработку и улучшить переносимость асинхронных веб-приложений. Используя API Atmosphere, вы теперь можете писать переносимые приложения, не беспокоясь о контейнере, на котором вы будете развертывать. Что еще более важно, если контейнер не поддерживает Comet / Ajax Push, Atmosphere все равно будет работать, эмулируя его непосредственно в рамках.

Атмосфера призвана упростить разработку и улучшить переносимость асинхронных веб-приложений. Любой объект Java может быть расширен с помощью функций асинхронного сервера с помощью нескольких аннотаций, как показано ниже:

@Grizzlet(Grizzlet.Scope.APPLICATION)
@Path("myGrizzlet")
public class MyGrizzlet{

@Suspend(6000)
@GET
@Push
public String onGet(){
return "Suspending the connection";
}

@POST
@Push
public String onPost(@Context HttpServletRequest req,
@Context HttpServletResponse res) throws IOException{
res.setStatus(200);
res.getWriter().println("OK, info pushed");
return req.getParameter("chatMessage");
}

Сервлет 3.0

 

Servlet 3.0 будет охватывать возможность приостановить и возобновить соединение, но не будет поддерживать возможности асинхронного чтения / записи и гарантии доставки. Как интеграция лучших идей, существующих в существующих API, Servlet EG всегда обращает внимание общественности на обратную связь. Должен ли он иметь метод re-entrant service ()? Должны ли фильтры поддерживаться как для входящего запроса, так и для исходящего возобновленного ответа? Сейчас самое время высказать свое мнение.

 

Предел двух соединений

Наличие отличного серверного API — это только часть истории. Практическая проблема, возникающая при попытке реализовать push, заключается в том, что большинство браузеров обычно имеют ограничение в два соединения с данным сервером. Если пользователь открывает два окна браузера, каждое из которых пытается установить принудительное соединение с сервером, оба разрешенных TCP-соединения используются. Это означает, что любые пользовательские события (такие как нажатие на кнопку или ссылку) не будут отправляться на сервер — отсутствует доступное TCP-соединение для его передачи. Следовательно, нам нужно использовать одно TCP-соединение для нескольких окон браузера.

Сервер Ajax Push ICEfaces был создан для решения этой проблемы с помощью JMS для координации всех push-обновлений по одному каналу. В браузере используется технология опроса файлов cookie JavaScript для обмена уведомлениями об обновлениях через окна браузера (HTML5 postMessage () упростит реализацию здесь). Это обеспечивает надежную доставку push-обновлений из нескольких веб-приложений на сервер в несколько вкладок или окон браузера.

Для GlassFish ICEfaces предоставляет модуль центра обновлений, который позволяет легко добавлять и устанавливать Ajax Push Server, автоматически настраивая темы JMS.

Разработка асинхронных приложений

Мы рассмотрели варианты API Servlet, которые позволят нам разрабатывать масштабируемые push-приложения, но факт заключается в том, что сегодня не так много разработчиков работают непосредственно с Servlet API. Разработчики работают с API-интерфейсами, такими как Cometd, DWR и ICEfaces, в значительной степени скрывая основные возможности сервлетов. Итак, давайте кратко рассмотрим, как использовать эти фреймворки.

Для разработчика, концентрирующегося на JavaScript, отличный выбор — Cometd. Cometd основан на идее обмена сообщениями «публикация / подписка» с помощью рефлектора на стороне сервера. Обратите внимание, что сервер потенциально является просто отражателем и может быть реализован на любом языке, служа только ретранслятором сообщений между конечными точками JavaScript. Конечно, серверные API также возможны, и они доступны для различных серверов. Обратите внимание, что системы, основанные исключительно на обмене сообщениями клиентов, страдают от «проблемы позднего присоединения». Если вы присоединились к приложению поздно, у вас может не быть правильного промежуточного состояния для применения текущих сообщений.

Перейдя больше к сфере Java-разработки, у нас есть DWR, или «Direct Web Remoting». DWR, по сути, предоставляет знакомый Java RMI (или удаленный вызов метода, если он вам не знаком), соединенный с JavaScript. Прежде всего, DWR используется для предоставления JavaBean-компонентов на стороне сервера через объектно-ориентированные интерфейсы JavaScript, но возможен и «обратный» подход: DWR можно использовать для вызова методов JavaScript в клиенте из Java-кода на стороне сервера. Для иллюстрации заслуживают внимания два метода: addScript () позволяет приложению вызывать общий фрагмент JavaScript на группе указанных страниц, а setValue () позволяет приложению равномерно обновлять группу DOM браузера с указанным значением. Это мощные возможности на стороне клиента, доступные на сервере; Однако,они требуют, чтобы приложение сохраняло детальное знание содержимого просматриваемой страницы.

Теперь, когда мы увидели несколько API и фреймворков, мы должны сделать шаг назад и спросить, с какими идеальными разработчиками и дизайнерами приложений будет работать для создания приложения Ajax Push.

Прежде всего, разработчики должны работать в основном с JavaBeans с минимумом нестандартных типов (предпочтительны POJO).

public class PageBean  {
String text;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

}

Дизайнеры должны работать в основном с HTML, но им также потребуется язык естественных выражений для динамического связывания с моделью JavaBean.

<f:view
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html" >
<html>
<body>
<h:form>
<h:inputText value="#{pageBean.text}" />
</h:form>
</body>
</html>
</f:view>

Но создание приложений таким естественным способом не только в нашем воображении. Такое четкое разделение между моделью и представлением является одной из сильных сторон JavaServer Faces (JSF), и разработчики и дизайнеры считают его очень продуктивным (даже если «разработка» и «дизайн» — это роли, выполняемые одним и тем же человеком в разное время) , Когда мы смотрим на предоставленный синтаксис, мы видим, что он ничего не говорит об Ajax (JavaScript не виден); все же, разве этого не должно быть достаточно для определения приложения Ajax? Мы описали объекты в модели (в Java), компоненты, которые видны в представлении (в XHTML), и как представление соотносится с моделью (в языке выражений). Фреймворк должен быть в состоянии перевести это в приложение Ajax для нас;действительно, это именно тот контракт, который ICEfaces предоставляет разработчикам приложений. Больше ничего не нужно для определения Ajax-приложения.

Но как насчет толчка? Когда мы считаем, что ICEfaces постепенно обновляет страницу с текущими значениями модели в ответ на пользовательские события, это маленький шаг, чтобы попросить, чтобы страница постепенно обновлялась с текущими значениями модели в ответ на события на стороне сервера. Другими словами, для того, чтобы приложение на стороне сервера могло вызвать передачу рендеринга страницы JSF, достаточно, чтобы инфраструктура могла вносить в браузер дополнительные изменения. Это делает добавление функций push простым как две строки кода.

Для каждого пользователя, который входит в группу с именем «чат»:

SessionRenderer.addCurrentSession("chat")

Мы добавили сессию текущего пользователя в группу сессий, которая называется «чат». Затем, когда получено сообщение чата и настало время отправить обновление на страницы пользователя:

SessionRenderer.render("chat")

Это отображает все окна всех пользователей в группе «чат» (автоматически определяя пошаговые изменения страницы) и передает эти изменения страницы в браузеры пользователей. Добавление push-функций в существующее приложение JSF часто может быть сделано не более чем в двух строках кода.

Кроме того, доступны более детальные API (например, для направления push-обновлений в определенные окна); пожалуйста, смотрите руководство разработчика ICEfaces для деталей.

Вывод

Теперь должно быть ясно, что асинхронная сеть является революционным шагом вперед для многопользовательских веб-приложений, но такие приложения можно разрабатывать в основном с использованием современных методов. Ключевым компонентом является Ajax Push, и он может масштабироваться для удовлетворения потребностей, при условии тщательного применения правильных серверов приложений и API. Конечно, авторы считают, что вы найдете комбинацию ICEfaces и GlassFish как наиболее эффективную, позволяющую легко добавлять многопользовательские функции в ваше веб-приложение и позволяющую развертывать его и масштабировать в соответствии со спросом.