Using Comet
This section explains the Comet programming technique and how
to create and deploy a Comet-enabled application with the Sun GlassFish Enterprise Server.
Introduction to Comet
Comet is a programming technique that allows a web server to
send updates to clients without requiring the clients to explicitly
request them.
This kind of programming technique is called server
push, which means that the server pushes data to the client.
The opposite style is client pull, which means
that the client must pull the data from the server, usually through
a user-initiated event, such as a button click.
Web applications that use the Comet technique can deliver updates
to clients in a more timely manner than those that use the client-pull
style while avoiding the latency that results from clients frequently
polling the server.
One of the many use cases for Comet is a chat room application.
When the server receives a message from one of the chat clients, it
needs to send the message to the other clients without requiring them
to ask for it. With Comet, the server can deliver messages to the
clients as they are posted rather than expecting the clients to poll
the server for new messages.
To accomplish this scenario, a Comet application establishes
a long-lived HTTP connection. This connection is suspended on the
server side, waiting for an event to happens before being resumed.
This kind of connection remains open, allowing an application that
uses the Comet technique to send updates to clients when they are
available rather than expecting clients to reopen the connection to
poll the server for updates.
The Grizzly Implementation of Comet
One limitation of the Comet technique is that you must use it
with a web server that supports non-blocking connections in order
to avoid poor performance. Non-blocking connections are those that
do not need to allocate one thread for each request. If the web server
were to use blocking connections then it might end up holding many
thousands of threads, thereby hindering its scalability.
The GlassFish server includes the Grizzly HTTP Engine, which
enables asynchronous request processing (ARP) by avoiding blocking
connections. Grizzly's ARP implementation accomplishes this by using
the Java NIO API.
With Java NIO, Grizzly enables greater performance and scalability
by avoiding the limitations experienced by traditional web servers
that must run a thread for each request. Instead, Grizzly's ARP mechanism
makes efficient use of a thread pool system and also keeps the state
of requests so that it can keep requests alive without holding a single
thread for each of them.
Grizzly supports two different implementations of Comet:
-
Grizzly Comet —
Based on ARP, this includes a set of APIs that you use from a web
component to enable Comet functionality in your web application. Grizzly
Comet is specific to the Sun GlassFish Enterprise Server.
-
Bayeux Protocol —
Often referred to as Cometd. This consists of the JSON-based Bayeux
message protocol, a set of Dojo or Ajax libraries, and an event handler.
The Bayeux protocol uses a publish/subscribe model for server/client
communication. The Bayeux protocol is portable, but it is container
dependent if you want to invoke it from an EJB component. The Grizzly
implementation of Cometd consists of a servlet that you reference
from your web application.
Client Technologies to Use With Comet
In addition to creating a web component that uses the Comet
APIs, you need to enable your client to accept asynchronous updates
from the web component. To accomplish this, you can use JavaScript,
IFrames, or a framework, such as Dojo.
An IFrame is an HTML element that allows you to include other
content in an HTML page. As a result, the client can embed updated
content in the IFrame without having to reload the page.
The example explained in this tutorial employs a combination
of JavaScript and IFrames to allow the client to accept asynchronous
updates. A servlet included in the example writes out JavaScript code
to one of the IFrames. The JavaScript code contains the updated content
and invokes a function in the page that updates the appropriate elements
in the page with the new content.
The next section explains the two kinds of connections that
you can make to the server. While you can use any of the client technologies
listed in this section with either kind of connection, it is more
difficult to use JavaScript with an HTTP-streaming connection.
Kinds of Comet Connections
When working with Comet, as implemented in Grizzly, you have
two different ways to handle client connections to the server:
-
HTTP Streaming
-
long-polling
HTTP Streaming
The HTTP Streaming technique keeps a connection open indefinitely.
It never closes, even after the server pushes data to the client.
In the case of HTTP streaming, the application sends a single
request and receives responses as they come, reusing the same connection
forever. This technique significantly reduces the network latency
because the client and the server don't need to open and close the
connection.
The basic life cycle of an application using HTTP-streaming
is:
request --> suspend --> data available --> write response -->
data available --> write response
The client makes an initial request and then suspends the request,
meaning that it waits for a response. Whenever data is available,
the server writes it to the response.
Long Polling
The long-polling technique is a combination of server-push and
client-pull because the client needs to resume the connection after
a certain amount of time or after the server pushes an update to the
client.
The basic life cycle of an application using long-polling is:
request -> suspend --> data available --> write response -->
resume
The client makes an initial request and then suspends the request.
When an update is available, the server writes it to the response.
The connection closes, and the client optionally resumes the connection.
How to Choose the Kind of Connection
If you anticipate that your web application will need to send
frequent updates to the client, you should use the HTTP-streaming
connection so that the client does not have to frequently reestablish
a connection. If you anticipate less frequent updates, you should
use the long-polling connection so that the web server does not need
to keep a connection open when no updates are occurring. One caveat
to using the HTTP-streaming connection is that if you are streaming
through a proxy, the proxy can buffer the response from the server.
So, be sure to test your application if you plan to use HTTP-streaming
behind a proxy.
Grizzly Comet
The following sections describe how to use Grizzly Comet.
The Grizzly Comet API
Grizzly's support for Comet includes a small set of APIs that
make it easy to add Comet functionality to your web applications.
The Grizzly Comet APIs that developers will use most often are the
following:
-
CometContext: A Comet context,
which is a shareable space to which applications subscribe in order
to receive updates.
-
CometEngine: The entry point to
any component using Comet. Components can be servlets, JavaServer
PagesTM (JSPTM), JavaServer Faces
components, or pure Java classes.
-
CometEvent: Contains the state
of the CometContext object
-
CometHandler: The interface an
application implements to be part of one or more Comet contexts.
The way a developer would use this API in a web component is
to perform the following tasks:
-
Register the context path of the application with
the CometContext object:
CometEngine cometEngine =
CometEngine.getEngine();
CometContext cometContext =
cometEngine.register(contextPath)
-
Register the CometHandler implementation
with the CometContext object:
cometContext.addCometHandler(handler)
-
Notify one or more CometHandler implementations
when an event happens:
cometContext.notify((Object)(handler))
The Hidden Frame Example
This rest of this tutorial uses the Hidden Frame example to
explain how to develop Comet-enabled web applications. You can download
the example from grizzly.dev.java.net at Hidden example download. From there, you can download
a prebuilt WAR file as well as a JAR file containing the servlet code.
The Hidden Frame example is so called because it uses hidden
IFrames. What the example does is it allows multiple clients to increment
a counter on the server. When a client increments the counter, the
server broadcasts the new count to the clients using the Comet technique.
The Hidden Frame example uses the long-polling technique, but
you can easily modify it to use HTTP-streaming by removing two lines.
See Notifying the Comet Handler of an Event and Creating the HTML Page That Updates and Displays the Content for more information on converting the example
to use the HTTP-streaming technique.
The client side of the example uses hidden IFrames with embedded
JavaScript tags to connect to the server and to asynchronously post
content to and accept updates from the server.
The server side of the example consists of a single servlet
that listens for updates from clients, updates the counter, and writes
JavaScript code to the client that allows it to update the counter
on its page.
See Deploying and Running a Comet-Enabled Application for instructions on how to deploy and run the
example.
When you run the example, the following happens:
-
The index.html page opens.
-
The browser loads three frames: the first one accesses
the servlet using an HTTP GET; the second one loads the count.html page, which displays the current count; and the third one
loads the button.html page, which is used to send
the POST request.
-
After clicking the button on the button.html page,
the page submits a POST request to the servlet.
-
The doPost method calls the onEvent method of the Comet handler and redirects the incremented
count along with some JavaScript to the count.html page
on the client.
-
The updateCount JavaScript function
on the count.html page updates the counter on the
page.
-
Because this example uses long-polling, the JavaScript
code on count.html calls doGet again
to resume the connection after the servlet pushes the update.
Creating a Comet-Enabled Application
This section uses the Hidden Frame example application to demonstrate
how to develop a Comet application. The main tasks for creating a
simple Comet-enabled application are the following:
Developing the Web Component
This section shows you how to create a Comet-enabled web component
by giving you instructions for creating the servlet in the Hidden
Frame example.
Developing the web component involves performing the following
steps:
-
Create a web component to support Comet requests.
-
Register the component with the Comet engine.
-
Define a Comet handler that sends updates to the client.
-
Add the Comet handler to the Comet context.
-
Notify the Comet handler of an event using the Comet
context.
Creating a Web Component to Support Comet
-
Create an empty servlet class, like the following:
import javax.servlet.*;
public class HiddenCometServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private String contextPath = null;
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {}
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {);
}
-
Import the following Comet packages into the servlet class:
import com.sun.grizzly.comet.CometContext;
import com.sun.grizzly.comet.CometEngine;
import com.sun.grizzly.comet.CometEvent;
import com.sun.grizzly.comet.CometHandler;
-
Import these additional classes that you need for incrementing
a counter and writing output to the clients:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
-
Add a private variable for the counter:
private final AtomicInteger counter = new AtomicInteger();
Registering the Servlet with the Comet Engine
-
In the servlet's init method, add the
following code to get the component's context path:
ServletContext context = config.getServletContext();
contextPath = context.getContextPath() + "/hidden_comet";
-
Get an instance of the Comet engine by adding this line
after the lines from step 1:
CometEngine engine = CometEngine.getEngine();
-
Register the component with the Comet engine by adding
the following lines after those from step 2:
CometContext cometContext = engine.register(contextPath);
cometContext.setExpirationDelay(30 * 1000);
Defining a Comet Handler to Send Updates to
the Client
-
Create a private class that implements CometHandler and
add it to the servlet class:
private class CounterHandler
implements CometHandler<HttpServletResponse> {
private HttpServletResponse response;
}
-
Add the following methods to the class:
public void onInitialize(CometEvent event)
throws IOException {}
public void onInterrupt(CometEvent event)
throws IOException {
removeThisFromContext();
}
public void onTerminate(CometEvent event)
throws IOException {
removeThisFromContext();
}
public void attach(HttpServletResponse attachment) {
this.response = attachment;
}
private void removeThisFromContext() throws IOException {
response.getWriter().close();
CometContext context =
CometEngine.getEngine().getCometContext(contextPath);
context.removeCometHandler(this);
}
You need to provide implementations of these methods when implementing CometHandler. The onInterrupt and onTerminate methods execute when certain changes occur in the status
of the underlying TCP communication. The onInterrupt method
executes when communication is resumed. The onTerminate method
executes when communication is closed. Both methods call removeThisFromContext, which removes the CometHandler object
from the CometContext object.
Adding the Comet Handler to the Comet Context
-
Get an instance of the Comet handler and attach the response
to it by adding the following lines to the doGet method:
CounterHandler handler = new CounterHandler();
handler.attach(res);
-
Get the Comet context by adding the following lines to doGet:
CometEngine engine = CometEngine.getEngine();
CometContext context = engine.getCometContext(contextPath);
-
Add the Comet handler to the Comet context by adding this
line to doGet:
context.addCometHandler(handler);
Notifying the Comet Handler of an Event
-
Add an onEvent method to the CometHandler class to define what happens when an event occurs:
public void onEvent(CometEvent event)
throws IOException {
if (CometEvent.NOTIFY == event.getType()) {
int count = counter.get();
PrintWriter writer = response.getWriter();
writer.write("<script type='text/javascript'>" +
"parent.counter.updateCount('" + count + "')" +
"</script>\n");
writer.flush();
event.getCometContext().resumeCometHandler(this);
}
}
This method first checks if the event type is NOTIFY,
which means that the web component is notifying the CometHandler object that a client has incremented the count. If the
event type is NOTIFY, the onEvent method
gets the updated count, and writes out JavaScript to the client. The
JavaScript includes a call to the updateCount function,
which will update the count on the clients' pages.
The last line resumes the Comet request and removes it from
the list of active CometHandler objects. By this
line, you can tell that this application uses the long-polling technique.
If you were to delete this line, the application would use the HTTP-Streaming
technique.
-
For HTTP-Streaming:
Add the same code as for
long-polling, except do not include the following line:
event.getCometContext().resumeCometHandler(this);
You don't include this line because you do not want to resume
the request. Instead, you want the connection to remain open.
-
Increment the counter and forward the response by adding
the following lines to the doPost method:
counter.incrementAndGet();
CometEngine engine = CometEngine.getEngine();
CometContext<?> context =
engine.getCometContext(contextPath);
context.notify(null);
req.getRequestDispatcher("count.html").forward(req, res);
When a user clicks the button, the doPost method
is called. The doPost method increments the counter.
It then obtains the current CometContext object
and calls its notify method. By calling context.notify, the doPost method triggers the onEvent method you created in the previous step. After onEvent executes, doPost forwards the response
to the clients.
Creating the Client Pages
Developing the HTML pages for the client involves performing
these steps:
-
Create a welcome HTML page, called index.html,
that contains: one hidden frame for connecting to the servlet through
an HTTP GET; one IFrame that embeds the count.html page,
which contains the updated content; and one IFrame that embeds the button.html page, which is used for posting updates using
HTTP POST.
-
Create the count.html page that
contains an HTML element that displays the current count and the JavaScript
for updating the HTML element with the new count.
-
Create the button.html page that
contains a button for the users to submit updates.
Creating a Welcome HTML Page That Contains
IFrames for Receiving and Sending Updates
-
Create an HTML page called index.html.
-
Add the following content to the page:
<html>
<head>
<title>Comet Example: Counter with Hidden Frame</title>
</head>
<body>
</body>
</html>
-
Add IFrames for connecting to the server and receiving
and sending updates to index.html in between the body tags:
<frameset>
<iframe name="hidden" src="hidden_comet"
frameborder="0" height="0" width="100%"></iframe>
<iframe name="counter" src="count.html"
frameborder="0" height="100%" width="100%"></iframe>
<iframe name="button" src="button.html" frameborder="0" height="30%" widget="100%"></iframe>
</frameset>
The first frame, which is hidden, points to the servlet by referencing
its context path. The second frame displays the content from count.html, which displays the current count. The second frame displays
the content from button.html, which contains the
submit button for incrementing the counter.
Creating the HTML Page That Updates and Displays
the Content
-
Create an HTML page called count.html and
add the following content to it:
<html>
<head>
</head>
<body>
<center>
<h3>Comet Example: Counter with Hidden Frame</h3>
<p>
<b id="count"> </b>
<p>
</center>
</body>
</html>
This page displays the current count.
-
Add JavaScript code that updates the count in the page
. Add the following lines in between the head tags
of count.html:
<script type='text/javascript'>
function updateCount(c) {
document.getElementById('count').innerHTML = c;
parent.hidden.location.href = "hidden_comet";
};
</script>
The JavaScript takes the updated count it receives from the
servlet and updates the count element in the page. The last line in
the updateCount function invokes the servlet's doGet method again to reestablish the connection.
-
For HTTP-Streaming:
Add the same code as for
long-polling, except for the following line:
parent.hidden.location.href = “hidden_comet”
This line invokes the doGet method of CometServlet again, which would reestablish the connection. In the case
of HTTP-Streaming, you want the connection to remain open. Therefore,
you don't include this line of code.
Creating the HTML Page That Allows Submitting
Updates
-
Create an HTML page called button.html and
add the following content to it:
<html>
<head>
</head>
<body>
<center>
<form method="post" action="hidden_comet">
<input type="submit" value="Click">
</form>
</center>
</body>
</html>
This page displays a form with a button that allows a user to
update the count on the server. The servlet will then broadcast the
updated count to all clients.
Creating the Deployment Descriptor
This section describes how to create a deployment descriptor
to specify how your Comet-enabled web application should be deployed.
Creating the Deployment Descriptor
-
Create a file called web.xml and put
the following contents in it:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd ">
<servlet>
<servlet-name>HiddenCometServlet</servlet-name>
<servlet-class>
com.sun.grizzly.samples.comet.HiddenCometServlet
</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HiddenCometServlet</servlet-name>
<url-pattern>/hidden_comet</url-pattern>
</servlet-mapping>
</web-app>
This deployment descriptor contains a servlet declaration and
mapping for HiddenCometServlet. The load-on-startup attribute must be set to 0 so that the Comet-enabled servlet
will not load until the client makes a request to it.
Deploying and Running a Comet-Enabled Application
Before running a Comet-enabled application in the Enterprise Server,
you need to enable Comet in the server. Then you can deploy the application
just as you would any other web application.
When running the application, you need to connect to it from
at least two different browsers to experience the effect of the servlet
updating all clients in response to one client posting an update to
the server.
Enabling Comet in the Enterprise Server
Before running a Comet-enabled application, you need to enable
Comet in your application server by adding a special property to the http-listener element of the domain.xml file.
The following steps tell you how to add this property.
-
Open domain-dir/config/domain.xml in
a text editor.
-
Add the following property in between the http-listener start and end tags:
<property name="cometSupport" value="true"/>
-
Save domain.xml and restart the server.
Deploying the Example
These instructions tell you how to deploy the Hidden Frame example.
-
Download grizzly-comet-hidden-1.7.3.1.war.
-
Run the following command to deploy the example:
as-install/bin/asadmin deploy grizzly-comet-hidden-1.7.3.1.war
Running the Example
These instructions tell you how to run the Hidden Frame example.
-
Open two web browsers, preferably two different brands
of web browser.
-
Enter the following URL in both browsers:
http://localhost:8080/grizzly-comet-hidden/index.html
-
When the first page loads in both browsers, click the
button in one of the browsers and watch the count change in the other
browser window.
Bayeux Protocol
The Bayeux protocol, often referred to as Cometd, greatly simplifies
the use of Comet. No server-side coding is needed for servers such
as Enterprise Server that support the Bayeux protocol. Just enable Comet
and the Bayeux protocol, then write and deploy the client as described
in the following tasks:
Enabling Comet
Before running a Comet-enabled application, you need to enable
Comet in your application server by adding a special property to the http-listener element of the domain.xml file.
The following steps tell you how to add this property.
-
Open domain-dir/config/domain.xml in
a text editor.
-
Add the following property in between the http-listener start and end tags:
<property name="cometSupport" value="true"/>
-
Save domain.xml and restart the server.
Configuring the web.xml File
To enable the Bayeux protocol on the Enterprise Server, you must
reference the CometdServlet in your web application's web.xml file. In addition, if your web application includes
a servlet, set the load-on-startup value for your
servlet to 0 (zero) so that it will not load until
the client makes a request to it.
-
Open the web.xml file for your web
application in a text editor.
-
Add the following XML code to the web.xml file:
<servlet>
<servlet-name>Grizzly Cometd Servlet</servlet-name>
<servlet-class>
com.sun.grizzly.cometd.servlet.CometdServlet
</servlet-class>
<init-param>
<description>
expirationDelay is the long delay before a request is
resumed. -1 means never.
</description>
<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>
Note that the load-on-startup value for the CometdServlet is 1.
-
If your web application includes a servlet, set the load-on-startup value to 0 for your servlet
(not the CometdServlet) as follows:
<servlet>
...
<load-on-startup>0</load-on-startup>
</servlet>
-
Save the web.xml file.
Writing, Deploying, and Running the Client
The examples in this task are taken from the example chat application
posted and discussed at http://weblogs.java.net/blog/jfarcand/archive/2007/02/gcometd_introdu_1.html.
-
Add script tags to the HTML page. For example:
<script type="text/javascript" src="chat.js"></script>
-
In the script, call the needed libraries. For example:
dojo.require("dojo.io.cometd");
-
In the script, use publish and subscribe methods to send
and receive messages. For example:
cometd.subscribe("/chat/demo", false, room, "_chat");
cometd.publish("/chat/demo", { user: room._username, chat: text});
-
Deploy the web application as you would any other web
application. For example:
asadmin deploy cometd-example.war
|
-
Run the application as you would any other web application.
The context root for the example chat application is /cometd and the HTML page is index.html. So
the URL might look like this:
http://localhost:8080/cometd/index.html
|
See Also
For more information about deployment in the Enterprise Server,
see the Sun GlassFish Enterprise Server v3 Prelude Application Deployment Guide.
For more information about the Bayeux protocol, see Bayeux Protocol.
For more information about the Dojo toolkit, see http://dojotoolkit.org/.
For information about pushing data from an external component
such as an EJB module, see the example at http://blogs.sun.com/swchan/entry/java_api_for_cometd.
Using this Grizzly Java API for Cometd makes your web application
non-portable. Running your application on a server that doesn't support
Grizzly Comet will not work.
For information about RESTful (REpresentational State Transfer)
web services and Comet, see RESTful Web Services and Comet.