Exercise 1: Build a Comet Chat Application (20 minutes)
In this lesson we are going to build a chat
applicaton using comet.
Background Information
The term Ajax was invented
to describe the technologies that provide background request-response
between a web server and client browser. With Ajax, web applications
can retrieve data from the server in the background, essentially
preloading information that is revealed in response to a user gesture
and giving the impression of a more responsive web page. Although the
data appears to be downloaded asynchronously, the server responds to
client requests — typically through the use of the XMLHttpRequest
object.
Web protocols require that servers respond to browser
requests. Ajax
falls short for multi-user, interactive pages because one user won't
see changes other users make until the browser sends a request. In
order for a server to push data to a browser asynchronously, some
design workarounds are required. These design strategies are broadly
described by the term Reverse Ajax.
One strategy is to use browser polling, in which the browser
makes a
request of the server every few seconds. Another strategy, known as
piggybacking, delays a server's page update until the next request is
received from the browser, at which time the server sends the pending
update along with the response.
A third strategy, and the one used in the example application
presented in this article, is to design with long-lived HTTP
connections between the
browser and server. With this strategy, a browser does not poll the
server for updates; instead, the server has an open line of
communication it can use to send data to the browser asynchronously.
Such connections reduce the latency with which messages are passed to
the server. This technique is known as Comet —
a play on words that can be appreciated by users of household cleansers.
The example application in this exercise provides a chat
feature that enables users to publishmessages and let subscribed users
see
the messages updated on their browser.
The example application
incorporates the
following technologies. All are available for download.
- Ajax –
Techniques
that provide background updating of web pages using a request-response
model.
- Comet
–
Techniques that enable a server to push data to client browsers through
an HTTP open line of communication.
- Dojo
– An
open-source DHTML toolkit written in JavaScript. The Dojo Toolkit
includes many utilities that go beyond Ajax. For example, the
dojox.comet
module simplifies programming Comet applications.
- Bayeux – A protocol for routing JSON-encoded
events between clients and servers in a publish-subscribe model.
- GlassFish
– Open-source Java EE-compliant application server.
- Grizzly
– Open-source framework designed to help developers take advantage of
the Java New I/O API (Java NIO API). Grizzly comes bundled with
GlassFish application server, but it can be used separately.
More About the Technologies
Comet
is a term coined by Alex Russell
to describe applications where the server pushes data to the client
over a long-lived HTTP connection. Comet uses the persistent connection
feature in HTTP/1.1. This feature keeps alive the TCP connection
between the server and the browser until an explicit 'close connection'
message is sent by one or the other, or until a timeout or network
error occurs. The technique enables the server to send a message to the
client when an event occurs without waiting for the client to send a
request. Cometd is a scalable HTTP-based event routing bus that
uses a push technology pattern known as Comet.
Grizzly
is an HTTP framework that uses the Java NIO API to provide fast HTTP
processing. Grizzly provides long-lived streaming HTTP connections that
can be used by Comet, with support built on top of Grizzly's Asynchronous Request Processing (ARP). With
Grizzly ARP, each Comet request doesn't monopolize a thread, and the
availability of threads permits scalability.
Bayeux
is a protocol for routing JSON-encoded events between clients and
servers
in a publish-subscribe model. Grizzly provides an implementation of
Bayeux, which makes it easy to build Comet applications with dojox.comet.
With the support for Cometd and Bayeux, an application can be developed
using only Dojo (or Ajax component), and the application can be
deployed in any server that supports the Bayeux protocol.
To build your application, configure GlassFish for Comet and configure
your web application's web.xml file for the Grizzly
Bayeux servlet. You can then use the dojox cometd
publish and subscribe methods to send and receive Comet events. These
techniques are described in more detail in the
description of the application.
Figure 1:
Grizzly Architecture
Steps to Follow
- If NetBeans is not already running, start it.
-
Choose File > New Project (Ctrl-Shift-N) from the main
menu. Under Categories, select Java Web. Under Projects, select Web
Application and click Next.
-
Type chat
in the Project Name field. Note that the
Context Path becomes /chat.
-
Specify the Project Location to the exercise1 sub
directory of this lab on your computer.
-
Under Server, select GlassFish v3. GlassFish is a Java
EE5-certified application server and is bundled with the Web and Java
EE insallation of NetBeans IDE.
-
Leave the Set as Main Project option selected and click
Finish. The IDE creates the chat project folder. The project
folder contains all of your sources and project metadata, such as the
project's Ant build script. The chat project opens in the IDE. The
welcome page, index.jsp, opens in the Source Editor in the main window.
You can view the project's file structure in the Files window (Ctrl-2),
and its logical structure in the Projects window (Ctrl-1).)

There are three main ways to install Dojo, which you can read
about
in the book of Dojo. Netbeans 6.5 has JavaScript
libraries bundled with it, which makes it easy to add the dojo
libraries to your project.

In this lab we are not using the current bundled version
because it
does not contain dojox, which we need for the dojox.grid widget and
dojox.cometd. If you want to use a different version of dojo than the
bundled one (like in this HOL), another easy way to use Dojo
with the NetBeans IDE is to download and extract the dojo JavaScript
library, and then copy it into your Netbeans project web directory: .../web.
The dojo JavaScript library has already been downloaded and extracted
for
this HOL and is in the js
folder in the hol\resources\dojo directory.
-
In your file system copy the js
folder from the <lab_root>\exercises\exercise1\dojo
directory to
your chat project's
web
directory \exercises\exercise1\chat\web. The js
folder contains the dojo javascript libraries.
-
You should
now see the js folder in your chat project as below:

-
In the Projects window right click the chat
node (from exercise 1),
and select New > Other....
-
In the New File window select Web HTML and click Next

-
In the New HTML File Window, enter the HTML file name index, and
click Finish.

-
If the index.html file is not open , then Double click on
the index.html file to open it
in the
editor.

-
Copy all of the html contents below and paste them to
replace the
contents of the index.html
file in the editor.
<html>
<head>
<title>chat</title>
<style type="text/css">
@import
"js/dijit/themes/tundra/tundra.css";
@import
"js/dojo/resources/dojo.css";
</style>
<script type="text/javascript"
src="js/dojo/dojo.js"
djConfig="parseOnLoad: true"></script>
<script type="text/javascript"
src="chat.js"></script>
<link rel="stylesheet" type="text/css"
href="chat.css">
</head>
<body class="tundra">
<h1>Chat</h1>
<br>
<div id="chatroom">
<div id="join" >
<label for="sendName" style="float: left; width: 100px; padding:
3px;">Name:</label>
<input id="sendName" type="text" dojoType="dijit.form.TextBox">
<button id="joinB"
dojoType="dijit.form.Button">Join</button>
</div>
<div id="joined"
class="hidden">
<label for="sendText" style="float: left; width: 100px; padding:
3px;">Message:</label>
<input id="sendText" type="text" dojoType="dijit.form.TextBox">
<button id="sendB"
dojoType="dijit.form.Button">Send</button>
<button id="leaveB"
dojoType="dijit.form.Button">Leave</button>
</div> <br>
<div id="chat">
<div id="messageLog">Messages:</div>
</div>
</div>
</body>
</html>
|
-
In the Projects window right click the chat
node (from exercise 1),
and select New > Other....
-
In the New File window select Web Cascading Style Sheet
and click Next

-
In the New CSS File Window, enter the CSS file name chat, and
click Finish.

-
Double click on thechat.css file to open it in the
editor.

-
Copy all of the contents below and paste them to
replace the
contents of the chat.css
file in the editor.
div
{
border: 0px solid black;
}
div#chatroom
{
background-color: #e0e0e0;
border: 1px solid black;
width: 45em;
}
div#chat
{
background-color: #f0f0f0;
padding: 4px;
border: 0px solid black;
}
div#input
{
clear: both;
padding: 4px;
border: 0px solid black;
border-top: 1px solid black;
}
input#sendText
{
width:28em;
background-color: #e0f0f0;
}
input#sendName
{
width:14em;
background-color: #e0f0f0;
}
div.hidden
{
display: none;
}
span.alert
{
font-style: italic;
}
|
-
In the Projects window right click the chat
node (from exercise 1),
and select New > Other....
-
In the New File window select Web JavaScriptFile and click
Next

-
In the NewJavaScript File Window, enter the file
name chat, select the chat web Folder, and
click Finish.

-
Double click on the chat.js file to open it
in the
editor.

-
Copy the JavaScript contents below and paste them into the
chat.js
file in the
editor.
dojo.require("dojox.cometd");
var room = {
username: null,
channel: "/chat/demo",
join:
function(name){
dojox.cometd.init("cometd");
room.username=name;
dojo.byId('join').className='hidden';
dojo.byId('joined').className='';
dojox.cometd.subscribe(room.channel, room, "chatCallback");
dojox.cometd.publish(room.channel, {
user: room.username,
text: " has joined"
});
},
leave: function(){
if (room.username==null)
return;
dojox.cometd.unsubscribe(room.channel, room, "chatCallback");
dojox.cometd.publish(room.channel, {
user: room.username,
text: " has left"
});
dojox.cometd.disconnect();
// switch the input form
dojo.byId('join').className='';
dojo.byId('joined').className='hidden';
room.username=null;
},
chat: function(text){
dojox.cometd.publish(room.channel, {
user: room.username,
text: text
});
},
chatCallback: function(message){
if(!message.data){
alert("bad message format "+message);
return;
}
var
messageLog=dojo.byId('messageLog');
var from=message.data.user;
var text=message.data.text;
from+=":";
// display the chat
text
messageLog.innerHTML +=
"<br/><span class=\"from\">"+from+"
</span><span
class=\"text\">"+text+"</span>";
},
init: function(){
dojo.connect(dojo.byId("joinB"),"onclick",function(){
if
(!dojo.byId("sendName").value.length) {
alert("Please enter Name");
return;
}
room.join(dojo.byId('sendName').value);
});
dojo.connect(dojo.byId("sendB"),"onclick",function(){
if
(!dojo.byId("sendText").value.length) {
alert("Please enter Text");
return;
}
room.chat(dojo.byId("sendText").value);
});
dojo.connect(dojo.byId("leaveB"),"onclick",function(){
room.leave();
});
}
};
dojo.addOnLoad(room, "init");
dojo.addOnUnload(room,"leave");
|
Edit the web.xml file to change the welcome page:
-
double click on the web.xml file in the chat WEB-INF
directory

-
Click on the Pages tab. Change the Welcome File from
index.jsp to index.html .

The
goal of this part of exercise 1 is to configure Glassfish for
comet and
your web project for the Grizzly Cometd servlet.
- Enabling Bayeux in GlassFish
To enable Bayeux on GlassFish, add the lines shown in red below
to your Web application's web.xml file:
Code
Sample
from: web.xml |
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
...>
<servlet>
<servlet-name>Grizzly Cometd Servlet</servlet-name>
<servlet-class>
com.sun.grizzly.cometd.servlet.CometdServlet
</servlet-class>
<init-param>
<param-name>expirationDelay</param-name>
<param-value>-1</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Grizzly Cometd
Servlet</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
|
Every request sent to your war file's context-path/cometd/
will be serviced by the Grizzly Bayeux runtime.
- Enable Comet support in
Glassfish if you haven't done so in exercise 0.
See exercise 0
for detail on how to enable comet support in Glassfish if you haven't
done so.
-
Right click the chat node in the Projects
window, and
select Run.
- When you run the project, your browser should display the
opening
page of the dojo comet Sample Application (at
http://localhost:8080/chat/).
- Open another browser, preferably with different profile
or different brand of browser, and
type http://localhost:8080/chat/ to access the application. Enter
name to join the chat and then enter some message.
Comet chat page,
which allows the users to chat at the same time.
Different
Browsers
Sharing Photos in Comet chat
Step 6: Use Firebug to see the
JSON Messages
Firebug is Firefox plugin which allows you to debug,
and monitor CSS, HTML, and JavaScript live in any web page. In
this lab you can use it to see the HTTP Request and Response content in
order to see the JSON messages exchanged during the chat session. In
Firefox , Select Tools > Firebug > Open Firebug.
Enabling Firbug Panels
To work on Javascript code or to study the network action on a site,
you need to enable one or more Firebug panels. If you select the
Console, Script, or Net panels, you will see the tab is grey and the
panel says "disabled". Each panel tab has a small menu control for
enabling the panel. All the panels can be enabled or disabled using the
context menu (right click) on the Firebug status bar icon.

See
Enabling
Firebug for more information.
Click on the
Console
panel and select Enabled.

In the Chat Application type in a name and click Join . You
should see the HTTP Posts as shown below. Click to expand a
Post Request , then select the
Post tab , you should see the
Bayeux JSON message sent as shown below. When you join a
the chat session multiple json messages are sent between the browser
client and the glassfish servlet to perform a handshake. This is
explained more in
understanding the
code below. The image below shows the JSON message that user
carol has joined.
Click to expand a
Post Request
, then select the
Response tab ,
you should see the Bayeux JSON message received as shown
below.
Click to expand the last
Post Request
, then select the
Post tab , you
should see the Bayeux JSON message as shown below. This is the
long-polling HTTP "keep alive" Request which remains
blocked until a message in the chat session is sent (or until it times
out).
Using dojox.cometd
Loading Base Dojo and Required Modules Into the
Application
This script element: src="src/dojo/dojo.js" loads the base
dojo
script that gives you access
to the Dojo functionality, other dojo modules are loaded as required
with the the dojo.require
function explained later.
Code
Sample
from: index.html |
<script type="text/javascript"
src="js/dojo/dojo.js"></script>
<script type="text/javascript" src="chat.js"></script>
|
The rest of the JavaScript code for this
application is in the file chat.js. Note that NetBeans
IDE 6.5 has a very capable JavaScript editor, shown below editing
the chat.js file:
In chat.js, the application uses the dojo.require
function (similar to the import directive in Java) to
specify which Dojo modules to load.
Code
Sample
from: chat.js |
dojo.require("dojox.cometd");
|
Dojo is organized into three major layers: Dojo Core, Dijit, and DojoX.
DojoX builds on Dojo Core and provides newer extensions to the Dojo
Toolkit. DojoX
cometd implements a Bayeux protocol client for use
with a Bayeux server.
Initializing a Connection Between the Dojo Client and the
Grizzly Bayeux Servlet
Upon loading the chat application, a user can enter a
username
and join a chat session, as shown in Figure 3.
|
Figure 3:
User Login Page
|
When a user clicks the Join button, the
join
JavaScript function is called. In the join function, the
call to dojox.cometd.init initializes a connection to the
given
Comet server — in this case with the GlassFish Grizzly Bayeux servlet.
Note that "cometd" is
the
URL-pattern for the Grizzly Cometd servlet configured in the web.xml
file for the application.
Code
Sample
from: chat.js |
var room = {
...
join: function(name){
dojox.cometd.init("cometd");
dojox.cometd.subscribe("/chat/demo",
room, "chatCallback");
dojox.cometd.publish("/chat/demo",
{ user:
room.username,
text: " has joined"});
}
|
The dojox.cometd.subscribe line subscribes the chatCallback
callback function to the /chat/demo channel. Whenever a
message is sent to the /chat/demo channel, the chatCallback
function is called. The dojox.cometd.publish line
publishes a message that the user whose name was entered with the Join
button has joined the /chat/demo channel. All subscribers
to the /chat/demo channel receive the message.
Using Firebug you can see that dojox.cometd.init("cometd") sends
the following with a POST to
the server:
message=
[ {
"version":"1.0","minimumVersion":"0.9",
"channel":"/meta /handshake",
"id":"0",
"supportedConnectionTypes":["long-polling",
"callback-polling"]
} ]
|
A Bayeux client initiates a connection negotiation by sending
a message
to the
"/meta/handshake" channel. A Bayeux server must respond to a handshake
request with a handshake response
message in the body content of the response. The response looks
like this:
[ {
"channel":"/meta/handshake",
"version":"1.0",
"supportedConnectionTypes":["long-polling","callback-polling"],
"minimumVersion":"0.9","id":"0",
"clientId":"xtY0HrhJ/YkXetV1CWaRSw==",
"successful":true,
"advice":{"reconnect" :"retry",
"interval":0,"multiple-clients":false},
"authSuccessful":true
}]
|
When you join a
the chat session multiple json messages are sent between the browser
client and the glassfish servlet to perform a handshake, which is
specified in the
Bayeux protocol
Using Firebug you can see that
dojox.cometd.publish("/chat/demo",{
user:
room.username,text: " has joined"})
sends the following with a POST to
the server:
message=[
{
"channel":"/meta/subscribe",
"subscription":"/chat/demo",
"clientId":"xtY0HrhJ/YkXetV1CWaRSw==",
"id":"2" },
{ "data":{"user":"carol","text":"
has joined"},
"channel":"/chat/demo",
"clientId":"xtY0HrhJ/YkXetV1CWaRSw==",
"id":"3"}
]
|
The response looks like this:
[
{"channel":"/meta/subscribe",
"successful":true,
"clientId":"xtY0HrhJ/YkXetV1CWaRSw==",
"subscription":"/chat/demo",
"id":"2"},
{"channel":"/chat/demo",
"successful":true,
"clientId":"xtY0HrhJ/YkXetV1CWaRSw==",
"id":"3"},
{ "channel":"/chat/demo",
"data":{"text":" has
joined","user":"carol"},
"id":"3",
"clientId":"xtY0HrhJ/YkXetV1CWaRSw=="}
]
|
Publishing the Next Slide for the Comet chat
When the user clicks the Send button, a JavaScript
function is
called that publishes the message, as shown in Figure 4.
|
Figure 4:
Viewing the Next
Slide
|
Code
Sample
from: index.html |
<button id="sendB" dojoType="dijit.form.Button">Send</button>
|
The JavaScript function that is called when the user clicks
the Send button is shown in the following code sample. This function
calls room.chat, which publishes the message.
Code
Sample
from: chat.js |
var room = {
.. dojo.connect(dojo.byId("sendB"),"onclick",function(){ if (!dojo.byId("sendText").value.length) { alert("Please enter Text"); return; } room.chat(dojo.byId("sendText").value); });
|
The function room.chat, shown in the following
code
sample, calls dojox.cometd.publish to publish the message
text(which it receives as an input argument) to the /chat/demo
channel. All subscribers to the /chat/demo channel
receive this message.
Code
Sample
from: chat.js |
var room = { channel: "/chat/demo", ...
chat: function(text){ dojox.cometd.publish(room.channel, { user: room.username, text: text }); }, ... }
|
When a message is published to a Bayeux channel on the server,
it is
delivered to all clients subscribed to that channel — in this case, to
the /chat/demo channel.
In the room.join function described earlier,
dojox.cometd.subscribe("/chat/demo", room, "chatCallback")
was called to subscribe the chatCallback callback
function to the /chat/demo channel. The chatCallback
function, shown in the following code sample, is called with the
published message as an input argument. The chatCallback
function updates the browser page by setting the messageLog dom element
innerHTML to the text from the published message .
This updates the browser page with the message text that was
published.
Code
Sample
from: chat.js |
var room = { ... chatCallback: function(message){ if(!message.data){ alert("bad message format "+message); return; } var messageLog=dojo.byId('messageLog'); var from=message.data.user; var text=message.data.text; from+=":"; // display the chat text messageLog.innerHTML += from+" "+text; }, ... }
|
Summary
The example application demonstrates the use of Dojo, Comet,
Bayeux, running on the GlassFish
application server.
See Also
For more information see the following resources:
Back to top
Next
exercise