Статьи

Начало работы с Atmosphere CPR: написание приложения для чата

В настоящее время написание портативного приложения Comet невозможно: JBossWeb имеет AIO, Tomcat имеет другой API-интерфейс AIO, Jetty имеет свой API-интерфейс Continuation и поддержку API до Servlet 3.0, Grizzly имеет свои Comet Framework, Grizzlet API и т. Д. Итак, такая инфраструктура, как DWR , ICEFaces и Bindows все добавили встроенную поддержку и абстрагировали слой для поддержки различных API Comet. Хуже того, если ваше приложение использует эти API напрямую, то вы застряли на одном веб-сервере. Неплохо, если вы используете Grizzly Comet, но если вы используете конкурента, вы не можете встретить Grizzly!

Текущий EG Servlet работает над предложением добавить поддержку Comet в будущей спецификации Servlet 3.0, но прежде чем планета полностью поддержит эту спецификацию, может потребоваться много времени. И предложение будет содержать небольшое подмножество текущего набора функций, которые некоторые контейнеры уже поддерживают, такие как асинхронный ввод / вывод (Tomcat, Grizzly), управляемый контейнером пул потоков для одновременной обработки операций push, фильтры для операций push. и т.д. Не сказать, что при использовании Atmosphere фреймворку больше не придется заботиться о нативной реализации, а вместо этого строить поверх Atmosphere. Такой протокол, как Bayeux, будет бесплатным и будет работать на всех WebServer, используя их собственный API.

Атмосфера — это основанная на POJO структура, использующая Inversion of Control (IoC), чтобы принести толчок / комету в массы! Наконец, инфраструктура, которая может работать на любом веб-сервере на основе Java, включая Tomcat, Jetty, GlassFish, Resin, Jersey, RESTlet и т. Д. ….. без необходимости ждать Servlet 3.0 или без необходимости изучать, как поддержка push / Comet имеет были по-разному реализованы всеми этими контейнерами.

Время начать с Atmosphere CPR ( Comet Portable Runtime )! В этой первой части я опишу, как написать приложение для чата и развернуть его на Tomcat, Jetty и GlassFish.

Давайте сначала сделаем основное. Давайте используем Maven 2 и создадим структуру файла:

 %  mvn archetype:create -DgroupId=org.atmosphere.samples
-DartifactId=chat -DarchetypeArtifactId=maven-archetype-webapp

Который создаст следующую структуру:

./chat
./chat/pom.xml
./chat/src
./chat/src/main
./chat/src/main/resources
./chat/src/main/webapp
./chat/src/main/webapp/index.jsp
./chat/src/main/webapp/WEB-INF
./chat/src/main/webapp/WEB-INF/web.xml

Следующим шагом является добавление необходимого context.xml и сохранение его в META-INF /

.
./chat
./chat/pom.xml
./chat/src
./chat/src/main
./chat/src/main/resources
./chat/src/main/webapp
./chat/src/main/webapp/index.jsp
./chat/src/main/webapp/WEB-INF
./chat/src/main/webapp/WEB-INF/lib
./chat/src/main/webapp/WEB-INF/lib/atmosphere-portable-runtime-0.1-ALPHA1.jar
./chat/src/main/webapp/WEB-INF/web.xml
./chat/src/main/webapp/META-INF
./chat/src/main/webapp/META-INF/context.xml

Наконец, давайте добавим библиотеку Atmosphere CPR в pom.xml, чтобы она добавлялась в наш WEB-INF / lib

         <dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-portable-runtime</artifactId>
<version>0.1-ALPHA1</version>
</dependency>

<repositories>
<repository>
<id>maven2.java.net</id>
<name>Java.net Repository for Maven 2</name>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>

Теперь мы готовы написать наш первый AtmosphereHandler , который является центральным элементом любого приложения Atmosphere CPR. Давайте просто реализуем этот интерфейс

 

package org.atmosphere.samples.chat;


import java.io.IOException;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.atmosphere.cpr.AtmosphereEvent;
import org.atmosphere.cpr.AtmosphereHandler;
import org.atmosphere.cpr.Broadcaster;

/**
* Simple AtmosphereHandler that implement the logic to build a Chat application.
*
*/
public class ChatAtmosphereHandler implements AtmosphereHandler {

Как описано здесь, реализация AtmosphereHandler требует двух методов:


/**
* When a client send a request to its associated {@link AtmosphereHandler}, it can decide
* if the underlying connection can be suspended (creating a Continuation)
* or handle the connection synchronously.
*
* It is recommended to only suspend request for which HTTP method is a GET
* and use the POST method to send data to the server, without marking the
* connection as asynchronous.
*
* @param event an {@link AtmosphereEvent}
* @return the modified {@link AtmosphereEvent}
*/
public AtmosphereEvent onEvent(AtmosphereEvent event) throws IOException;


/**
* This method is invoked when the {@link Broadcaster} execute a broadcast
* operations. When this method is invoked its associated {@link Broadcaster}, any
* suspended connection will be allowed to write the data back to its
* associated clients.
*
* @param event an {@link AtmosphereEvent}
* @return the modified {@link AtmosphereEvent}
*/
public AtmosphereEvent onMessage(AtmosphereEvent event) throws IOException;

Метод onEvent будет вызываться каждый раз, когда запрос сопоставляется с ассоциированным с ним AtmosphereHandler. Существует два способа сопоставить запрос с AtmosphereHandler. По умолчанию будет использоваться имя AtmosphereHandler, например, если мы назовем наше веб-приложение chat.war, запрос к http: // localhost: 8080 / chat / ChatAtmosphereHandler вызовет AtmophereHandler.onEvent. Для чата давайте предположим, что мы приостановим ответ, когда браузер отправит нам запрос GET.

 HttpServletRequest req = event.getRequest();
HttpServletResponse res = event.getResponse();

res.setContentType("text/html");
res.addHeader("Cache-Control", "private");
res.addHeader("Pragma", "no-cache");
if (req.getMethod().equalsIgnoreCase("GET")) {
res.getWriter().write("<!-- Comet is a programming technique that enables web " +
"servers to send data to the client without having any need " +
"for the client to request it. -->\n");
res.getWriter().flush();
event.suspend();

Центральным элементом является AtmosphereEvent , из которого мы можем получить объект запроса и ответа. Затем мы делаем некоторые настройки, а затем, как только мы будем готовы, нам просто нужно вызвать AtmosphereEvent.suspend () , который автоматически скажет Atmosphere CPR не фиксировать ответ. Отсутствие ответа означает, что мы можем использовать его позже для записи. В текущем упражнении мы будем использовать приостановленный ответ, когда кто-то вводит присоединение или вводит предложение в чате. Теперь давайте предположим, что когда пользователь входит в систему или вводит предложения, браузер устанавливает POST (публикуя некоторые данные). Поэтому, когда пользователь входит в систему

              res.setCharacterEncoding("UTF-8");
String action = req.getParameterValues("action")[0];
String name = req.getParameterValues("name")[0];

if ("login".equals(action)) {
event.getBroadcaster().broadcast(
"System Message from "
+ <a href="https://atmosphere.dev.java.net/nonav/apidocs/org/atmosphere/cpr/AtmosphereEvent.html#getWebServerName%28%29">event.getWebServerName()</a>, name + " has joined.");
res.getWriter().write("success");
res.getWriter().flush();

Важной частью здесь является строка 94. Роль вещателя — публиковать данные в приостановленных ответах. Как только вы передадите данные, все приостановленные ответы получат возможность записать содержание трансляции. Выше мы просто передаем имя, а также на каком веб-сервере мы работаем (для демонстрационных целей). Вызов Broadcaster.broadcast () , в свою очередь, вызовет ваш AtmosphereHandler.onMessage, и все будут приостановлены. Здесь давайте предположим, что мы просто отражаем (пишем) то, что получаем:

     public AtmosphereEvent
onMessage(AtmosphereEvent event) throws IOException {
HttpServletRequest req = event.getRequest();
HttpServletResponse res = event.getResponse();
res.getWriter().write(event.getMessage().toString());
res.getWriter().flush();
return event;
}

Далее, когда пользователь вводит некоторые сообщения чата:

             } else if ("post".equals(action)) {
String message = req.getParameterValues("message")[0];
event.getBroadcaster().broadcast(BEGIN_SCRIPT_TAG + toJsonp(name, message) + END_SCRIPT_TAG);
res.getWriter().write("success");
res.getWriter().flush();

Здесь мы кодируем сообщение, используя формат JSON, чтобы клиентский javascript мог легко обновить страницу. Это для АтмосферыХандлер. Вы можете увидеть полный исходный код здесь.

Теперь давайте предположим, что мы хотим иметь более точный способ сопоставить наш AtmosphereHandler с запросом. Чтобы добиться этого, создайте файл с именем атмосферный файл в каталоге src / main / webapp / META-INF / и определите необходимое отображение:

   <atmosphere-handlers>
<atmosphere-handler context-root="/chat" class-name="org.atmosphere.samples.chat.ChatAtmosphereHandler">
<property name="name" value="Chat"/>
</atmosphere-handler>
</atmosphere-handlers>

С этим файлом все запросы к / chat будут сопоставлены с нашим ChatAtmosphereHandler.

Теперь давайте рассмотрим клиентскую часть. Сначала напишем очень простой файл index.html:

   <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Atmosphere Chat</title>
<link rel="stylesheet" href="stylesheets/default.css" type="text/css" />
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/behaviour.js"></script>
<script type="text/javascript" src="javascripts/moo.fx.js"></script>
<script type="text/javascript" src="javascripts/moo.fx.pack.js"></script>
<script type="text/javascript" src="javascripts/application.js"></script>
</head>
<body>
<div id="container">
<div id="container-inner">
<div id="header">
<h1>Atmosphere Chat</h1>
</div>
<div id="main">
<div id="display">
</div>
<div id="form">
<div id="system-message">Please input your name:</div>
<div id="login-form">
<input id="login-name" type="text" />
<br />
<input id="login-button" type="button" value="Login" />
</div>
<div id="message-form" style="display: none;">
<div>
<textarea id="message" name="message" rows="2" cols="40"></textarea>
<br />
<input id="post-button" type="button" value="Post Message" />
</div>
</div>
</div>
</div>
</div>
</div>
<iframe id="comet-frame" style="display: none;"></iframe>
</body>
</html>

Простая форма, которая отправит обратно на сервер имя пользователя и введенное сообщение чата. Чтобы обновить интерфейс «на лету», как только наш ChatAtmosphereHandler.onMessage напишет / отправит нам данные, давайте использовать javascript для прототипа и поведения. Я предполагаю, что вы либо знакомы с этими основами, либо имеете общее представление о том, как они работают. Это будет определено в application.js. Как только пользователь введет свой логин, давайте сделаем

      login: function() {
var name = $F('login-name');
if(! name.length > 0) {
$('system-message').style.color = 'red';
$('login-name').focus();
return;
}
$('system-message').style.color = '#2d2b3d';
$('system-message').innerHTML = name + ':';

$('login-button').disabled = true;
$('login-form').style.display = 'none';
$('message-form').style.display = '';

var query =
'action=login' +
'&name=' + encodeURI($F('login-name'));
new Ajax.Request(app.url, {
postBody: query,
onSuccess: function() {
$('message').focus();
}
});
},

Когда пользователь напишет новое сообщение в чате, давайте нажмем

      post: function() {
var message = $F('message');
if(!message > 0) {
return;
}
$('message').disabled = true;
$('post-button').disabled = true;

var query =
'action=post' +
'&name=' + encodeURI($F('login-name')) +
'&message=' + encodeURI(message);
new Ajax.Request(app.url, {
postBody: query,
requestHeaders: ['Content-Type',
'application/x-www-form-urlencoded; charset=UTF-8'],
onComplete: function() {
$('message').disabled = false;
$('post-button').disabled = false;
$('message').focus();
$('message').value = '';
}
});
},

Теперь, когда мы получаем ответ, мы просто обновляем страницу, используя

      update: function(data) {    
var p = document.createElement('p');
p.innerHTML = data.name + ':
' + data.message;

$('display').appendChild(p);

new Fx.Scroll('display').down();
}

То, как index.html и application.js взаимодействуют, определяется просто:

  var rules = {
'#login-name': function(elem) {
Event.observe(elem, 'keydown', function(e) {
if(e.keyCode == 13) {
$('login-button').focus();
}
});
},
'#login-button': function(elem) {
elem.onclick = app.login;
},
'#message': function(elem) {
Event.observe(elem, 'keydown', function(e) {
if(e.shiftKey && e.keyCode == 13) {
$('post-button').focus();
}
});
},
'#post-button': function(elem) {
elem.onclick = app.post;
}
};
Behaviour.addLoadEvent(app.initialize);
Behaviour.register(rules);

Смотрите полный исходный код здесь . Пока все хорошо, теперь мы готовы развернуть наше приложение на нашем любимом WebServer.

Вот простые картинки с разных веб-серверов:

Glassfish v3

GFv3.jpg

пристань

jetty.jpg

Кот

tomcat.jpg

Гризли

Grizzly.jpg

Вау, это было легко! Загрузите war или src, чтобы начать. Следите за нами в Твиттере для ежедневного обновления о статусе проекта и задавайте свои вопросы, используя [email protected]

Атмосфера облегчит задачу любому, кто хочет писать приложения в режиме реального времени, не дожидаясь официальной поддержки Servlet 3.0. Конечно, Atmosphere будет поддерживать Servlet 3.0 async API, но Atmosphere предлагает больше, чем 3.0. Например, вещательная компания Atmosphere Broadcaster очень полезна, когда пришло время проталкивать / агрегировать / фильтровать данные между приостановленными соединениями. То, что вы не можете получить бесплатно с помощью Servlet 3.0 или встроенной реализации Comet прямо сейчас.