Skip to main content

Dead Simple Comet Example on Glassfish v3 / Grizzly

Posted by driscoll on May 1, 2008 at 3:02 PM PDT

I was looking at a recent blog by Shing Wai Chan and going through the Comet example, when I noticed that the example wasn't working correctly. Although he updated his example to get around that problem, I was still a bit unsatisfied, and decided to sit down, using his basic example, and see if I could make it even simpler.

I've whittled it down to about 100 lines, and only 2 files, and I thought I'd go over it here. The full example (both files) are
index.html and CometCount.java. So this will be a little long, but if you're tired of reading my rambling, just look at the files, and the code should speak for itself.

First, about the app: It's a simple counter, which is updated every time you hit a button on the page. Pretty basic, except - every other web browser viewing that page will have the counter updated as well, through the magic of Comet.


About setting it up: make sure that the url mapping points to /CometCount, the value is hardcoded in a few places. Also, to compile you'll need access to the Grizzly Comet APIs - you can either get them from Grizzly, or Glassfish v3 tp2. You'll need to also add the jar in the modules directory named grizzly-optionals to your classpath in order to build, along with the standard Servlet API. You'll also need to update the domain.xml of the v3 instance to add the property cometSupport=true, as you see below:

        <http-listener acceptor-threads="1" address="0.0.0.0" 
           blocking-enabled="false" default-virtual-server="server"
           enabled="true" family="inet" id="http-listener-1" port="8080"
           security-enabled="false" server-name="" xpowered-by="true">
                <property name="cometSupport" value="true"/>
        </http-listener>


Now on to the description of the program flow. On startup, the servet is initialized, and registers itself with with the Comet Engine (make sure the servlet is installed with a url CometCount, or it won't work). We set a timeout of 2 minutes.

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        ServletContext context = config.getServletContext();
        contextPath = context.getContextPath() + "/MyComet";
        
        CometEngine engine = CometEngine.getEngine();
        CometContext cometContext = engine.register(contextPath);
        cometContext.setExpirationDelay(120 * 1000);
    }


Then on the first load of index.html in the browser, the hidden iframe makes a call to the doGet of the servlet - this call suspends, awaiting further action. It does this by attaching the response to a handler (of type CounterHandler), and attaching the handler to the servlet's CometContext. This also gives rise to the first bug of the program - there's no display of initial result.

So this:

calls this:
   protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        
        CounterHandler handler = new CounterHandler();
        handler.attach(res);
        
        CometEngine engine = CometEngine.getEngine();
        CometContext context = engine.getCometContext(contextPath);
        
        context.addCometHandler(handler);
    }


Next, someone, somewhere, who's also using the program, hits the button marked "click". This calls the onclick method, postMe(). postMe sends an empty POST to the servlet, triggering the doPost method.

So this:

Calls this:
            var url = "CometCount";
            function postMe() {
                function createXMLHttpRequest() {
                    try { return new XMLHttpRequest(); } catch(e) {}
                    try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
                    try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
                    alert("Sorry, you're not running a supported browser - XMLHttpRequest not supported");
                    return null;
                };
                var xhReq = new createXMLHttpRequest();
                xhReq.open("POST", url, false);
                xhReq.send(null);
                hidden.location = url;
            };


The doPost method increments the counter, and then sends a notify event to the servlet's CometHandler.

    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {

        counter.incrementAndGet();
        
        CometEngine engine = CometEngine.getEngine();
        CometContext context = engine.getCometContext(contextPath);
        context.notify(null);
    }


This event now wakes up that initial GET request, and sends a bit of javascript down the line. Lastly, we call resumeCometHandler, which marks the current event as completed, removing it from the active queue.

        public void onEvent(CometEvent event) throws IOException {
            if (CometEvent.NOTIFY == event.getType()) {
                int count = counter.get();
                PrintWriter writer = response.getWriter();
                writer.write("\n");
                writer.flush();
                event.getCometContext().resumeCometHandler(this);
            }
        }


Back at the client, that javascript that got sent down gets put into the hidden iFrame, and then executed. This calls the updateCount function, which updates the counter with the new value. Then, we set the iframes' location object, which reconnects with GET to the servlet, and we're ready for our next request.

            function updateCount(c) {
                document.getElementById('count').innerHTML = c;
                hidden.location = url;
            }


That is, unless we time out. If we time out (remember, we set the timeout to 2 minutes, not "infinite"), the iframe goes dead, the GET polling loop is broken, and we never again update the counter on the webpage, though the user's increasing frustrated button pushing on the client will happily update counter on the server. So, to get around this, we add a single line at the end of our postMe function, updating the hidden iframe's location again. This is the one really hacky part of the program, and I'd love to know a better way to do it. Also, it gives rise to the second bug of our program, related to the first - if the connection dies, you need to click the button twice to see an update of the counter on the client.


So - ta da! We have a bare bones, long polling comet application in two files and about 100 lines.


Again, here's the programs
Download file index.html
Download file CometCount.java


Jeanfrancois Arcand and I have a BOF on Wednesday night (May 7th, 2008) at JavaOne. If you're at JavaOne and are just getting started with Comet, come on by.

Related Topics >>

Comments

CPU issue with Grizzly Comet On GlassFish 9.1.1.

You need to go to the Grizzly mailing list

Hi - for detailed questions like this, it's really best to go to the Grizzly users mailing list, or even the Glassfish mailing list. There's a bunch of folks there who are better suited to help you out than I.

users@grizzly.dev.java.net

hai can i know where i can able see the count incremented

I have compared this example with my previous blog (http://blogs.sun.com/swchan/entry/follow_up_on_a_simple) on Hidden Frame: Long Polling vs Http Streaming (referenced above). Here are the differences: 1. The only difference in Java code is the last line in doPost method. In my case, I have a redirect back to "button.html". I do this as I try to minimize the use of Javascript in my blog. 2. The main differences are in Javascript and html. I use three frames where one of them is hidden frame. Jim's use Javascript postMe() to make the request. (Note that there is no hidden frame in Jim's example. There is a typo is the index.html above.)

This looks like it could be just the kind of example I need. Only thing is - I can't get it to work . Is there any chance you could post a complete WAR file (for deployment on Glassfish for example) with you web.xml, class and java source? I now you have the html and the java source here, but I think I have a problem with the deployment somewhere. Also, in your article you say the URL mapping should end in /CometCount yet in your code you set the context to: ServletContext context = config.getServletContext(); contextPath = context.getContextPath() + "/MyComet"; Is this going to be a problem? Thanks. (I would really appreciate an answer because I'd like to do a small demo for a client soon with this! :)

<p>[quote=andymiddled]This looks like it could be just the ...

andymiddled wrote:
This looks like it could be just the kind of example I need. Only thing is - I can't get it to work . Is there any chance you could post a complete WAR file (for deployment on Glassfish for example) with you web.xml, class and java source? I now you have the html and the java source here, but I think I have a problem with the deployment somewhere. Also, in your article you say the URL mapping should end in /CometCount yet in your code you set the context to: ServletContext context = config.getServletContext(); contextPath = context.getContextPath() + "/MyComet"; Is this going to be a problem? Thanks. (I would really appreciate an answer because I'd like to do a small demo for a client soon with this! :)

andymiddled wrote:
This looks like it could be just the kind of example I need. Only thing is - I can't get it to work . Is there any chance you could post a complete WAR file (for deployment on Glassfish for example) with you web.xml, class and java source? I now you have the html and the java source here, but I think I have a problem with the deployment somewhere. Also, in your article you say the URL mapping should end in /CometCount yet in your code you set the context to: ServletContext context = config.getServletContext(); contextPath = context.getContextPath() + "/MyComet"; Is this going to be a problem? Thanks. (I would really appreciate an answer because I'd like to do a small demo for a client soon with this! :)

I have the same request as above. I was able to download your java source but the index.html link seems to be broken. I'd really appreciate adding good links to the relevant files. Thanks for your help !