Wednesday Apr 23, 2008
In my previous comet blog,
A Simple Comet Example: Hidden Frame and Long Polling",
I illustrate comet by using a simple example of two frames.
While it is good for illustration, there is a limitation.
If you try to use two different browsers to access the counter and
click really fast, then you may notice that one of the counter may
be updated and then immediately changes to blank.
This is because the comet response may come before the response of
the http post. This is more significant in the case of Http
Streaming.
In this blog, we will explain how to resolve "blank problem" and
change the example to use Http Streaming instead of Long Polling.
One More Frame
The "counter blank" problem can be solved easily by extracting the
post action and put it in a different frame
(button.html). In other words, we only keep the
display related stuff in count.html.
In this case, post request is sent from button.html,
not from count.html. And hence, count.html
will only be updated by JavaScript only. (In contrast
with my previous blog, the count.html can also be
updated by Http Response.)
Now, in index.html, there will three frames as follows:
<iframe name="hidden" src="hidden_comet"
frameborder="0" height="0" width="100%"><iframe>
<iframe name="counter"
src="count.html" frameborder="0" height="70%"
width="100%"><iframe>
<iframe name="button"
src="button.html" frameborder="0" height="30%"
width="100%"><iframe>
The next thing we need to do is to update one line of
Java code in the doPost of the servlet to
redirect back to button.html rather than
count.html.
req.getRequestDispatcher("button.html").forward(req, res);
You can download the updated sample
here.
Http Streaming
Http Streaming is different from Long Polling by keeping the
connection (until expiration) between client and server even
after it delivers the data.
In general, this will perform better.
With the fix in the previous section, we can modify our example
easily to Http Streaming by commenting out the following
event.getCometContext().resumeCometHandler(this); in HiddenCometHandle.java
In this case, the server will not resume the connection.
parent.hidden.location.href = "hidden_comet" in updateCount JavaScript
In this case, the browser will not reload the hidden frame again.
I have make a comment in the source codes. One can locate the above easily.
Thursday Apr 03, 2008
Recently, there is a great interest in Comet technology.
One can find many interesting articles in Comet Daily.
Comet allows server and client to keep a live connection for communication.
This provides a mechanism for server to update clients, instead of
having classical polling.
In this blog, I am going to share my experience about using Comet
with hidden frame and long polling in
GlassFish v3 Technology
Preview 2 builds.
I try to make example as simple as possible to illustrate the basic
interactions there. If you want to learn more about Comet, then I recommend
Jean-Francois' blogs.
Set up GlassFish v3
Download GlassFish v3 Technology Preview 2, unzip the file and start the server with jvm option v3.grizzlySupport=true to enable comet.
java -Dv3.grizzlySupport=true -jar glassfish-10.0-SNAPSHOT.jar
We need the above jvm-option in today's build. This will not be
needed when comet is enabled by default.
Comet Servlet Code
The comet servlet code is adapted from grizzly sample comet-counter, which uses Ajax client. The details of our serlvet is as follows:
- In
init(ServletConfig), one registers a context path to CometEngine,
ServletContext context = config.getServletContext();
contextPath = context.getContextPath() + "/hidden_comet";
CometEngine engine = CometEngine.getEngine();
CometContext cometContext = engine.register(contextPath);
cometContext.setExpirationDelay(30 * 1000);
where "/hidden_comet" is url-pattern of the comet servlet in web.xml. For testing purpose, one keeps the connection for 30 sec.
- In
doGet(HttpServletRequest, HttpServletResponse), one looks up the CometContext
and adds our CometHandler.
CounterHandler handler = new CounterHandler();
handler.attach(res);
CometEngine engine = CometEngine.getEngine();
CometContext context = engine.getCometContext(contextPath);
context.addCometHandler(handler);
- In
doPost(HttpServletRequest, HttpServletResponse), one increments the counter and then invokes the
CometContext.notify, which will trigger the CometHandler.onEvent above.
counter.incrementAndGet();
CometEngine engine = CometEngine.getEngine();
CometContext<?> context = engine.getCometContext(contextPath);
notify(null);
In addition, it forwards to count.html page for displaying the count.
req.getRequestDispatcher("count.html").forward(req, res);
- Next, one need to have a class implementing
CometHandler interface. Among methods in CometHandler,
the most interesting one is onEvent(CometEvent).
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);
}
}
In our case, it writes a Javascript back to client side. This will invoke the Javascript function updateCount in count.html. The onEvent also invokes resumeCometHandler.
This is necessary as the polling connection will be dropped once it is used.
- To compile the above Java code, one needs to include javax.javaee*.jar and grizzly-comet*.jar in classpath.
Client Code
On client side, I will illustrate the technique of hidden frame.
Basically, the main page will have at least two frames. One of them does the long polling and is hidden from user.
In our case, the index.html consists two frames as follows:
<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>
The first frame, which is hidden, is pointed to our Comet Servlet above through GET method.
The second frame is to display the counter and submit button
for incrementing the counter.
The Javascript in count.html is very simple as follows:
<script type='text/javascript'>
function updateCount(c) {
document.getElementById('count').innerHTML = c;
parent.hidden.location.href = "hidden_comet";
};
</script>
How does it work?
One can download the sources and war file from
here,
and deploy the war file.
Using two browsers to access http://localhost:8080/grizzly-comet-hidden/index.html
and click on "Click" button on each browser separately.
Then one sees that counts in both browsers will be updated whenever one
clicks on one of them. The mechanism is outlined as follows:
- When the user accesses
index.html, a browser will load two frames:
- The "hidden" frame accesses our Comet Servlet through
GET method. This allows the client to start long polling with server.
- The "counter" frame loads a static
count.html.
- When the user clicks on the button in
count.html,
it submits a POST request to our Comet Servlet.
This triggers the CometHandler onEvent method
and redirects back to count.html to display
the count. The onEvent triggers the
updateCount() JavaScript in "counter" frame, which will
- update the count and
- invoke the Comet Servlet
doGet for long polling in "hidden" frame,