Home > Uncategorized > New Adventures in Comet: Writing a Comet enabled client using Prototype and Behaviour libraries

New Adventures in Comet: Writing a Comet enabled client using Prototype and Behaviour libraries

Continuing my Comet exploration, this time I will demonstrate how to write a client side Comet application using Prototype and Behaviour JavaScript libraries.

comet-bis.jpg

Recently I got a lot of questions about how to write the client side of an AJAX application. So far all the blogs I’ve written on Comet were mostly concentrating on the server side. My first blog on the topic will use the Prototype JavaScript framework and Behaviour. I’m really not an expert with those libraries so I recommend you first take a look at their tutorial before continuing.

The Grizzly Comet application I will describe here is a simple Counter that gets updated every time the user click on a simple html component. The layout page looks like (yes this is basic):

sc.png

Let’s assume the following layout for our web resources


|- index.html
|- javascripts
    |---- behaviour.js
    |---- prototype.js
    |---- counter.js

The only file we need to define is the counter.js:


      1 var counter = {
      2       'poll' : function() {
      3          new Ajax.Request('/demo/long_polling', {
      4             method : 'GET',
      5             onSuccess : counter.update
      6          });
      7       },
      8       'increment' : function() {
      9          new Ajax.Request('/demo/long_polling', {
     10             method : 'POST'
     11          });
     12       },
     13       'update' : function(req, json) {
     14          $('count').innerHTML = json.counter;
     15          counter.poll();
     16       }
     17 }
     18
     19 var rules = {
     20       '#increment': function(element) {
     21          element.onclick = function() {
     22             counter.increment();
     23          };
     24       }
     25 };
     26
     27 Behaviour.register(rules);
     28 Behaviour.addLoadEvent(counter.poll);

The counter variable does two Prototype calls. The first one:

      2       'poll' : function() {
      3          new Ajax.Request('/demo/long_polling', {
      4             method : 'GET',
      5             onSuccess : counter.update
      6          });
      7       },

is what I've described as a long polling connection in my previous blog. The Ajax.Request(..) will execute an http GET and wait for the server for a push. As soon as the server pushes data, the counter variable will be updated and the connection re-established, waiting for the next push from the server. The server pushes occurs when the second Prototype request is executed:

      8       'increment' : function() {
      9          new Ajax.Request('/demo/long_polling', {
     10             method : 'POST'
     11          });
     12       },

On the server side, this http POST will initiate the server push. The last two line are Behaviour specific:

     27 Behaviour.register(rules);
     28 Behaviour.addLoadEvent(counter.poll);

Line 27 just register our rules, e.g. all it means is when the user will click on a the html component called 'increment', our JavaScript function will be invoked. Finally, line 28 tells Behaviour to invoke our function when the window.onLoad event happen, which means as soon as the page is loaded (OK use google for a more technical discussion of what that function does :-)), a long polling connection will be established to the server. Now the only thing we need to do is write a simple HTML page:

      1 <?xml version="1.0" encoding="UTF-8" ?>
      2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      3     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      4 <html xmlns="http://www.w3.org/1999/xhtml">
      5 <head>
      6  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      7  <link rel="stylesheet" href="stylesheets/styles-site.css" type="text/css" />
      8  <title>Comet Example : Counter</title>
      9  <script type="text/javascript" src="javascripts/prototype.js"></script>
     10  <script type="text/javascript" src="javascripts/behaviour.js"></script>
     11  <script type="text/javascript" src="javascripts/counter.js"></script>
     12 </head>
     13 <body>
     14  <div id="container">
     15   <div id="container-inner">
     16    <h1>Comet Example : Counter</h1>
     17
     18    <div id="counter">
     19     <div id="count"></div>
     20     <div><span id="increment">click to increment</span></div>
     21    </div>
     22
     23   </div>
     24  </div>
     25 </body>
     26 </html>

Wow, that looks way to easy to be true...but it is!! Event myself, who is a JavaScript nullity, understand the code! The important things here to note is line 20:

     20     <div><span id="increment">click to increment</span></div>

When clicked, an http POST will be executed and the server will starts pushing back the data to all the clients that are subscribed to the Comet application (clients that have loaded the page).

On the server side, a very simple Servlet is defined:

     65     @Override
     66     public void init(ServletConfig config) throws ServletException {
     67         ServletContext context = config.getServletContext();
     68         contextPath = context.getContextPath() + "/long_polling";
     69
     70         CometEngine engine = CometEngine.getEngine();
     71         CometContext cometContext = engine.register(contextPath);
     72         cometContext.setExpirationDelay(30 * 1000);
     73     }
     74
     75     @Override
     76     protected void doGet(HttpServletRequest req, HttpServletResponse res)
     77     throws ServletException, IOException {
     78
     79         CounterHandler handler = new CounterHandler();
     80         handler.attach(res);
     81
     82         CometEngine engine = CometEngine.getEngine();
     83         CometContext context = engine.getCometContext(contextPath);
     84
     85         context.addCometHandler(handler);
     86     }
     87
     88     @Override
     89     protected void doPost(HttpServletRequest req, HttpServletResponse res)
     90     throws ServletException, IOException {
     91         counter.incrementAndGet();
     92
     93         CometEngine engine = CometEngine.getEngine();
     94         CometContext context = engine.getCometContext(contextPath);
     95         context.notify(null);
     96
     97         PrintWriter writer = res.getWriter();
     98         writer.write("success");
     99         writer.flush();
    100     }</code

The Servlet.init method just create the required objects needed to enabled Grizzly Comet. What is important to look at here is the doGet and the doPost. Withing the Servlet.doGet method, the important line is:

     85         context.addCometHandler(handler);</code

which enable the long polling connection, sent by the client using the AjaxRequest GET call. When the client issue a AjaxRequest POST, the Servlet.doPost is invoked and the important line is:

     95         context.notify(null);</code

This simple line will tell Grizzly to initiate a server push. What it means is your CometHandler (or your continuation or your parked request (line 85)) will be invoked. The CometHandler looks like:

     30         public void onEvent(CometEvent event) throws IOException {
     31             CometContext cometContext = event.getCometContext();
     32             if (event.getType() == CometEvent.WRITE){
     33                 CometWriter sc = (CometWriter)event.attachment();
     34                 int nWrite = sc.write("success".getBytes());
     35                 if (nWrite != 7){
     36                     cometContext.registerAsyncWrite(this);
     37                 } else {
     38                     event.getCometContext().resumeCometHandler(this);
     39                 }
     40             }
     41
     42             if (CometEvent.NOTIFY == event.getType()) {
     43                 int count = counter.get();
     44                 response.addHeader("X-JSON", "{\"counter\":" + count + " }");
     45
     46                 if (useNonBlocking){
     47                     response.flushBuffer();
     48                     boolean registered = cometContext.registerAsyncWrite(this);
     49                 } else {
     50                     PrintWriter writer = response.getWriter();
     51                     writer.write("success");
     52                     writer.flush();
     53                     event.getCometContext().resumeCometHandler(this);
     54                 }
     55
     56             }
     57         }

Here I'm just showing the important method of the server side, which is the CometHandler.onEvent(). When the CometContext.notify() is invoked in line 95 (from Servlet.doPost), the onEvent method will be called. In the example above, the CometHandler add a special header (X_JSON) that will be retrieved on the client side by the function

     13       'update' : function(req, json) {
     14          $('count').innerHTML = json.counter;
     15          counter.poll();
     16       }

and the html page will be updated, then the connection re-established, because on the server side the CometHandler do:

   53                     event.getCometContext().resumeCometHandler(this);

which release the continuation (or resume the parked request). That's why the client have to re-establish the connection (see this for a more technical description about long polling connection). This is all you need to do....not so complicated, is it?

As a side note, the CometHandler I've described above uses non blocking writes to send the 'success' message. What that means (without going into non blocking socket details) is if Grizzly was unable to flush all the data in one chunk, the onEvent() will be invoked as soon as Grizzly and the underlying OS are ready to write the message. But this is not important here...just pushing some nice Grizzly Comet features!

You can download the complete example here and deploy it using GlassFish v2. A gigantic thanks to Takai Naoto for sending me the original example a couple of months ago!!!

_uacct = "UA-3111670-1";
urchinTracker();

technorati:

Categories: Uncategorized
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s