The Source for Java Technology Collaboration
User: Password:



Jean-Francois Arcand

Jean-Francois Arcand's Blog

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

Posted by jfarcand on June 27, 2007 at 03:12 PM | Comments (14)

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     }

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

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

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!!!

technorati:


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • I tried this example, with two browsers point to "http://localhost:8080/demo/" , and using java http client to send post request. the result turns out that these two browsers update one by one instead of simultaneously. is that supposed to update at the same time? any help?

    Posted by: fudanzz on June 28, 2007 at 02:30 AM

  • Hi, have you enable Comet in GlassFish? Which version are you using?

    Posted by: jfarcand on June 28, 2007 at 07:12 AM

  • I do add " ". and glassfish version is v2-b41d

    Posted by: fudanzz on June 28, 2007 at 08:36 PM

  • Hi, edit glassfish.home/domains/domain1/config/domain.xml and remove line <property ="proxiedProtocols" value=:ws/tcp"/> as b41d seems to be broken. If you can, use the latest build (b52) to use the latest implementation. with full non blocking read/write support. Thanks!

    Posted by: jfarcand on June 29, 2007 at 07:14 AM

  • Salut, Jean-Francois! I experience the same problem as 'fudanzz' (round-robin updates). I just realized that the code in onEvent() in your demo.jar is not the same as printed on this page. Maybe this is the reason? TIA, Stefan

    Posted by: stefanz on July 06, 2007 at 02:52 PM

  • Hhm, no. Changing onEvent() to the printed code seems to make things even worse. Now I am really at a dead end. What is right resp. what goes wrong? I'm using b54 and I have removed the proxiedProtocols line. Thanks, Stefan

    Posted by: stefanz on July 06, 2007 at 04:59 PM

  • Hi, I'm really confused about what's happening :-( I've tried deploying the jar and it seems to work fine. Can we have the discussion on users@grizzly.dev.java.net so I can trace what's happening exactly. Mainly, adding the property and starting the server should make it works. Are you seeing any strange exception in the server.log? Thanks!!

    Posted by: jfarcand on July 09, 2007 at 07:56 AM

  • hi,let me ask a simple question,would grizzly cometd server embed tomcat 6.0? How to do this if can? and would you like to leave you contact infomation? thanks very much!
    look forward to your reply!

    Posted by: pollux0505 on July 10, 2007 at 08:18 PM

  • You cannot use gCometd with Tomcat because the implementation is Grizzly specific. The Tomcat 6 community have recently started working on the Bayeux protocol, so I suspect Tomcat 6 will soon have an implementation. But you can always use the one from Jetty, which can be used with Tomcat (but performance wise, better to use with Jetty). Thanks.

    Posted by: jfarcand on July 12, 2007 at 07:16 AM

  • Jean-Francois,

    Your example does not illustrate the essence of Comet which is server push because of the presence of line 20:

    click to increment

    (yes, you can argue it's piggy-back style comet, but I was looking for one more one the line of long connection technique). If you require the client (web users) to click on that link, then we're already doing with just Ajax. Under the hood, you might have CometEngine, CometContext, etc, but if you 're going to require the client to initiate, then your app still does NOT do server push. I challenge you to come up with an example just like what you have now, but without line 20.

    Thanks

    Posted by: kevinhle on September 17, 2007 at 01:23 PM

  • Kevin, I'm not sure I follow :-) When you click, the interface is not only locally updated, but all browsers that have loaded that page will be updated. So one click will cause the server to push the update to all other browser. Can you elaborate more? Thanks

    Posted by: jfarcand on September 18, 2007 at 07:49 AM

  • Jean-Francois, Thanks for clarifying my question. In your sample, I did not realize that all browsers will be updated, that's why I posted my question. Now that I understand it, yes, you're doing server push. I have another question, however.

    In Comet, the server does not close the connection, and 'pushes' the data to the client browser when it has some. But in your example, you continuously do the following Ajax request:

    new Ajax.Request('/demo/long_polling', {...

    So it seems like the client makes the first Ajax request. The server sends the data to the client, but does NOT close the connection. With the first connection still opened, the client now opens the second connection (by making another new Ajax.Request()). Therefore, you will end up with infinitely many number of connections between one single and the server.

    What am I still misunderstand? Thanks

    Kevin

    Posted by: kevinhle on September 20, 2007 at 11:06 AM

  • Hi Kevin, there is different type of Comet request. See here for more info. In this example, we are using long polling (not http streaming). If you want to see an http streaming demo, take a look at this blog. Hope that help.-- Jeanfrancois

    Posted by: jfarcand on September 20, 2007 at 11:14 AM

  • I wouldn't bother with the Euro spec turbo's, you won't gain a lot for the hassle. However a hybridised pair of stock turbo's might be worth it. They are the same externally as the stock turbo's but feature larger compressor and turbine wheels.

    The 550cc injectors will allow you to get close to 550HP with the right turbo's but in reality a small single turbo is required to achieve the power as even the hybrid OEM turbos arn't capable of flowing that much air.how to play roulette
    roulette how to win
    roulette tips
    how to win at roulette
    Run your car on water
    Run Car on Water
    Water Powered Car
    Water Car
    horse racing
    horse racing tips
    horse racing betting
    horse racing software
    horse racing systems
    downloadable movies
    download full version movies
    download movies
    movie downloads
    forex trading systems
    forex currency trading
    forex software
    learn forex
    world of warcraft guide
    wow gold guide
    wow guide
    world of warcraft cheats
    how to play poker
    learn poker
    poker strategy
    poker sites
    poker calculator
    free blackjack
    online blackjack
    blackjack strategy
    how to play blackjack
    iphone downloads
    iphone games download
    iphone download site review
    iphone download sites
    how to make money on ebay
    make money on ebay
    warren buffett
    warren buffet

    Posted by: j_doe1 on May 25, 2008 at 04:48 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds