Friday Dec 04, 2009

An Example of Porting Grizzly Comet to Servlet 3.0

Grizzly Comet is a very powerful feature in GlassFish v2 and GlassFish v3. It provides a framework for writing many interesting applications. However, the same application will not run in other servlet containers (without Grizzly). One can resolve this by porting the code to use asynchronous API in Servlet 3.0 as in the glassfish sample, asyn-request-war, code AjaxCometServlet.java. In this case, the application would be able to run in all Servlet 3.0 containers.

In this blog, I will discuss the former by using AjaxCometServlet.java. Even though, some details are specified to the chat example, the principle is general. There are two main steps:

Mark the servlet as asynchronous

One can achieve this by using one of the following:
  • specifying the asyncSupported in @WebServlet,
    for instance, @WebServlet(urlPatterns = {"/chat"}, asyncSupported = true)
  • adding <async-supported>true</async-supported> under corresponding <servlet> in web.xml

Modify the servlet code

ServletUsing Grizzly CometUsing Asynchronous API in Servlet 3.0
instance variablesString contextPath
  • Thread, notifierThread
  • Queue of AsyncContext, queueBlockingQueue of message, messageQueue

    Queue<AsyncContext> queue = new ConcurrentLinkedQueue<AsynContext>();
    BlockingQueue messageQueue = new LinkedBlockingQueue();

init method
  • register contextPath in CometEngine
  • set expiration time

    contextPath = config.getServletContext().getContextPath() + "/chat";
    CometContext context = CometEngine.getEngine().register(contextPath);
    context.setExpirationDelay(5 \* 60 \* 1000);

create and start a notifierThread to consume message from messageQueue

    Runnable notifierRunnable = new Runnable() {
    public void run() {
        ...
        cMessage = messageQueue.take();
        for (AsyncContext ac : queue) {
            try {
                PrintWriter acWriter = ac.getResponse().getWriter();
                acWriter.println(cMessage);
                acWriter.flush();
            } catch(IOException ex) {
                queue.remove(ac);
        ...
    };
    notifierThread = new Thread(notifierRunnable);
    notifierThread.start();

doGet method
  • create a CometHandler<PrintWriter>
  • attach the response writer to the CometHandler
  • register contextPath in CometEngine
  • add the CometHandlerto CometContext

    ... // code for ChatListenerHandler is omitted
    ChatListnerHandler handler = new ChatListnerHandler();
    handler.attach(writer);
    CometContext context = CometEngine.getEngine().register(contextPath);
    context.addCometHandler(handler)

  • create a AsyncContext
  • set timeout
  • create an AsyncListener and add to AsyncContext
  • add the AsyncContext to queue

    final AsyncContext ac = req.startAsync();
    ac.setTimeout(10 \* 60 \* 1000);
    ac.addListener(new AsyncListener() {
        ...
    };
    queue.add(ac);

doPost methodcall context.notify(message);put the message into the messageQueue
destroy method 
  • clean up the queue
  • interrupt the notifierThread

    queue.clear();
    notifierThread.interrupt();

Note that asynchronous API in Servlet 3.0 are, in fact, quite easy to use. In this scenario, some coding is need for the notification thread. If you don't want to manage the thread that notify, you can take look at Atmosphere.

Wednesday Apr 23, 2008

Follow up on A Simple Comet Example: Long Polling vs Http Streaming

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

A Simple Comet Example: Hidden Frame and Long Polling

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:

  1. 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.

  2. 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);

  3. 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);

  4. 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.

  5. 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:

  1. 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.
  2. 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,

Note: There is a threading issue in the above sample code. I have posted the fix and other additional comments in another blog: Follow up on A Simple Comet Example: Long Polling vs Http Streaming.

About

Shing Wai Chan

Search

Categories
Archives
« July 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today