 |
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.
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):
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: grizzly nio asynchronous request processing comet ajax glassfish
Bookmark blog post: del.icio.us Digg DZone Furl 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
|