Apr 27, 2009

Getting Started With Cometd, Jetty, and jQuery

I had difficulty getting started with these tools, so I thought I'd blog my minimal setup. This is all derived from the cometd demo who's war you can find inside the Jetty 6 webapp directory, and who's code you can find inside the contrib/cometd directory of the Jetty 6 source download.

First of all you need to be able to run Jetty (and debug it). I recommend the Run Jetty Run plugin for Eclipse.

Next you need to populate your web.xml for your webapp: (it's quite possible that you don't need all these init params)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>comet2</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

<context-param>
<param-name>org.mortbay.jetty.servlet.ManagedAttributes</param-name>
<param-value>org.cometd.bayeux,dojox.cometd.bayeux</param-value>
</context-param>

<servlet>
<servlet-name>game</servlet-name>
<servlet-class>
org.mortbay.cometd.continuation.ContinuationCometdServlet</servlet-class>
<init-param>
<param-name>timeout</param-name>
<param-value>120000</param-value>
</init-param>
<init-param>
<param-name>interval</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>maxInterval</param-name>
<param-value>10000</param-value>
</init-param>
<init-param>
<param-name>multiFrameInterval</param-name>
<param-value>2000</param-value>
</init-param>
<init-param>
<param-name>logLevel</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>directDeliver</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>refsThreshold</param-name>
<param-value>10</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>game</servlet-name>
<url-pattern>/game/*</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>gameServlet</servlet-name>
<servlet-class>server.GameServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

</web-app>
The GameServlet is pretty much boiler plate:
package server;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.cometd.Bayeux;
import org.mortbay.cometd.ext.AcknowledgedMessagesExtension;
import org.mortbay.cometd.ext.TimesyncExtension;

public class GameServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
((HttpServletResponse)res).sendError(503);
}

@Override
public void init() throws ServletException {
super.init();
Bayeux bayeux=(Bayeux)getServletContext().getAttribute(Bayeux.DOJOX_COMETD_BAYEUX);
new GameService(bayeux);
bayeux.addExtension(new TimesyncExtension());
bayeux.addExtension(new AcknowledgedMessagesExtension());
}
}
The GameService pairs paths to method names. You can call the send() method to send messages to clients. Mine looks like this:
package server;

import java.util.Map;

import org.cometd.Bayeux;
import org.cometd.Client;
import org.mortbay.cometd.BayeuxService;

public class GameService extends BayeuxService {
private GameState state = new GameState();

public GameService(Bayeux bayeux) {
super(bayeux, "game");
subscribe("/game/join", "join");
subscribe("/game/event", "event");
}

public void join(final Client joiner, final String channelName, Map<String, Object> data, final String messageId) {
send(joiner, "/game/state", state.getData(), null)
}

public void event(final Client joiner, final String channelName, Map<String, Object> data, final String messageId) {
// TODO
}
}
Finally, and perhaps most importantly (as this was the part that confused me the most), here's the JavaScript to talk to the comet service:
var _connected = false;

function _metaConnect(message)
{
var wasConnected = _connected;
_connected = message.successful;
if (_connected && !wasConnected)
{
$.cometd.startBatch();
$.cometd.subscribe('/game/state', this, receive);
$.cometd.publish('/game/join', {});
$.cometd.endBatch();
}
}

function join()
{
$.cometd.addListener('/meta/connect', this, _metaConnect);

var cometURL = document.location.protocol + '//' + document.location.hostname + ':' + document.location.port + '/c2/game';
$.cometd.init(cometURL);
}

function send(data) {
$.cometd.publish('/game/event', data);
}

function receive(message)
{
// handle message.data content
}

join();
Note that /c2 is the context name of my webapp in Jetty.

One last point, I used the same includes in my index.html as the cometd chat example did:
<script type="text/javascript" src="jquery/json2.js"></script>
<script type="text/javascript" src="jquery/jquery.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd.js"></script>
Let me know if you get up to something with comet.

9 comments:

pv said...

Hi,
This is a great start for me I have been looking for jquery implementation of cometd. But was not able to run the example. I tried the steps that you have mentioned but not able to run the example successfully. Can you please elaborate.
Thanks,
-P

Curious Attempt Bunny said...

Of course. Can you provide any diagnostic information?

pv said...

I was able to make it work. Thanks a lot for the great info that you have provided.

markww said...

Hi,

How are you handling multiple tabs or browser window instances? Also, if you just force-kill a connected browser, do you get notified of the dropped player? I'm having problems with getting either of those two issues to work!

Curious Attempt Bunny said...

Hi Mark,

I've not put time into this since so I don't have the answers to your questions. I'd be interested in hearing what your up to or what solutions to come up with.

Cheers,
Merlyn

markww said...

Hey yeah so I wanted to basically make a game as well. I've looked at so many (comet) ways of doing it. Seems like Bayeux with Jetty is pretty robust. Problems with many implementations are that multiple tabs in the browser screw things up, force kills of the browsers, stuff like that. The Cometd project is probably the best I've found. The only other promising project is Ajax Push Engine, but it's brand new and we'll see how quickly it matures.

ss said...

Hi .I have a code to run. To run this program I need some libraries that you used in your own code. I want to know if you can mail these libraries to me. The list is hear:
org.cometd.Client;
org.cometd.Message;
org.cometd.MessageListener;

ss said...

Hi again!. my email : nakhaie.1365@gmail.com
Thanks

Curious Attempt Bunny said...

Hi ss,

I'm pretty sure you can download all the code you need to run cometd from their website. I certainly didn't author any of the org.cometd classes.