CometD Documentation

CometD Project Overview

The primary purpose of CometD project is to implement responsive user interactions for web clients using AJAX and WebSocket by means of WebSocket and server-push techniques called Comet.

The CometD project provides the Bayeux specification, that defines the protocol used to exchange information between client and server, and provides (eventually) standardized APIs for servers and clients.
Currently the API bindings for JavaScript (on the client) and Java (for both client and server) have reached a stable status.

Downloads

The CometD distributions are at http://download.cometd.org

Documentation

Reference Manual
Javadocs
Tutorials

Older documentation

CometD 2.4.0 Reference Manual
CometD 2.3.x documentation.
CometD 1.x documentation.

Other Implementations

The CometD project includes implementations of Bayeux specification in several languages, toolkits and servers. Third party implementations of the Bayeux specification also exist, and are not strictly part of the CometD project, however for completeness the third party implementations are linked off this document where appropriate. Other implementations and integrations include:

Installation

Installation

Download the CometD distribution from here.

Then unpack the distribution:

$ tar zxvf cometd-<version>.tgz
$ cd cometd-<version>/

You are done.

If you want to try out the CometD demo, follow these commands:

$ cd cometd-<version>/
$ cd cometd-demo
$ mvn jetty:deploy-war

then point your browser at http://localhost:8080.

The CometD demo contains:

Building

Building the CometD Project

Building the CometD code has 2 minimum requirements:

You can obtain the source code from either the distribution tarball (see here), or by checking out the source from the CometD subversion repository.
Either unpack the distribution:

$ tar zxvf cometd-[version]-distribution.tar.gz
$ cd cometd-[version]

or checkout from the GitHub or Subversion repository:

$ git clone git://github.com/cometd/cometd.git cometd # For CometD greater than 2.2.0
$ cd cometd

$ svn co http://svn.cometd.org/trunk cometd/trunk # For CometD 2.x until 2.2.0 included
$ cd cometd/trunk

$ svn co http://svn.cometd.org/branches/cometd1 cometd/branches/cometd1 # For CometD 1.x
$ cd cometd/branches/cometd1

At this point you need to issue the following command:

$ mvn clean install

If you want to save some time, you can skip the execution of the test suite using the following command:

$ mvn clean install -DskipTests=true

Trying Out Your Build

To try out your build just follow these steps (after having built following the above instructions):

$ cd cometd-demo
$ mvn jetty:run

Then point your browser at http://localhost:8080 and you should see the CometD Demo page.

Committers Release Instructions

The following instructions only apply to the CometD project committers.

1. Creating the Release

Work from a fresh checkout of the trunk/branch to release, for example using trunk:

$ git clone git://github.com/cometd/cometd.git release/cometd
...
$ cd release/cometd
$ mvn clean install
...
$ mvn release:prepare # Specify the SVN tag to be just the version number
...
$ mvn release:perform -Darguments=-Dgpg.passphrase=...
...
$ git push origin master

2. Managing the Repository

Login to http://oss.sonatype.org.

Click on "Build Promotion - Staging Repositories" and you should see the staged project just uploaded by the mvn release:perform command issued above, with status "open".
Tick the checkbox correspondent to the open staged project, choose "Close" from the toolbar, enter a description such as "CometD Release 1.0.0", then click on the "Close" button.

This will make the staged project downloadable for testing, but not yet published to central.

Tick again the checkbox correspondent to the closed staged project, choose "Release" from the toolbar, enter a description such as "CometD Release 1.0.0", then click on the "Release" button. This will publish the project to central.

3. Creating the Distribution Tarball

Work from a fresh checkout of the tag just released (or from the target/checkout directory if you ran the release:perform step):

$ git checkout <version>
...
$ mvn clean install -DskipTests=true
...
$ cd cometd-distribution
$ mvn assembly:assembly
...

This creates the distribution tarball in the cometd-distribution/target directory.

4. Uploading the Distribution Tarball

Upload the tarball:

$ scp cometd-distribution/target/cometd-<version>-distribution.tar.gz <user>@download.cometd.org:/srv/www/vhosts.d/download.cometd.org/

5. Upload the Archetype Catalog

Make sure the archetype catalog $HOME/.m2/archetype-catalog.xml refers to the release just prepared, then upload it:

$ scp $HOME/.m2/archetype-catalog.xml <user>@cometd.org:/srv/www/vhosts.d/cometd.org/

6. Upload Javadocs

Build and upload the javadocs:

$ cd cometd-java
$ mvn javadoc:aggregate-jar
$ scp target/cometd-java-<version>-javadoc.jar <user>@docs.cometd.org:/srv/www/vhosts.d/docs.cometd.org/

then unjar the javadoc jar file into directory "apidocs".

7. Release in JIRA

Perform a release in Jira.

8. Tag Dojo Bindings

$ git clone https://github.com/cometd/cometd-dojo.git
$ cd cometd-dojo
$ git pull

Now edit the package.json file, updating the CometD version and the Dojo dependency version.

$ get push
$ git tag -a "<version>" -m "CometD Release <version>."
$ git push --tags

CometD 1.x Reference

CometD 1.x Reference

CometD JavaScript

CometD JavaScript Implementation

The JavaScript implementation of the Bayeux specification and API has been totally rewritten starting from CometD version 1.0.beta8, and further refactored since 1.0.beta9.
What is available now is a portable JavaScript implementation with bindings for the major JavaScript toolkits, currently Dojo and jQuery.

What this means is that the CometD Bayeux JavaScript implementation is written in pure JavaScript with no dependencies on the toolkits, and that the toolkit bindings add the syntactic sugar that makes the Bayeux APIs feel like they are native to the toolkit.
For example, it is possible to refer to the standard cometd object using the following notation:

// Dojo style
var cometd = dojox.cometd;

// jQuery style
var cometd = $.cometd;

If you followed the Primer, you may have noticed that the skeleton project now requires to reference both the portable implementation, under org/cometd.js, and one binding - for example Dojo's - under dojox/cometd.js. For jQuery, the binding is under jquery/jquery.cometd.js.

The usage of the Bayeux APIs from the JavaScript toolkits is almost identical, and in the following we will not refer to a particular toolkit.
Small differences only surface when passing callback functions to the Bayeux API, where Dojo users may like to use dojo.hitch(), while jQuery users may like an anonymous function approach.

The following sections will go in detail about the JavaScript Bayeux APIs and their implementation secrets.

CometD JavaScript Configuration

JavaScript CometD API: Configuration and Initialization

After you have setup your skeleton project following the Primer, you may want to fully understand how to customize and configure the parameters that govern the behavior of the Cometd implementation.

The whole API is available through a single object prototype named org.cometd.Cometd.
The Dojo toolkit has one instance of this object available under the name dojox.cometd, while for jQuery it is available under the name $.cometd.

This default cometd object has been instantiated and configured with the default values and it has not started any Bayeux communication yet.
Before it can start any Bayeux communication it needs a mandatory parameter: the URL of the Bayeux server.

There are 2 ways of passing this parameter:

// First style: URL string
cometd.configure('http://localhost:8080/cometd');

// Second style: configuration object
cometd.configure({
    url: 'http://localhost:8080/cometd'
});

The first way is a shorthand for the second way.
However, the second way allows to pass other configuration parameters, currently:

Parameter Name Required Default Value Parameter Description
url yes The URL of the Bayeux server this client will connect to
logLevel no info The log level. Possible values are: "warn", "info", "debug". Output to window.console if available
maxConnections no 2 The max number of connections used to connect to the Bayeux server.
Only change this value if you know exactly what is the client's connection limit and what "request queued behind long poll" means
backoffIncrement no 1000 The number of milliseconds of which the backoff time is incremented every time a connection with the Bayeux server fails.
A reconnection will be attempted after the backoff time elapses
maxBackoff no 60000 The max number of milliseconds of the backoff time after which the backoff time is not incremented anymore
reverseIncomingExtensions no true Controls whether the incoming extensions will be called in reverse order with respect to the registration order
maxNetworkDelay no 10000 The max number of milliseconds to wait before considering a request to the Bayeux server failed.
requestHeaders no {} An object containing the request headers to be sent for every bayeux request (for example: {"My-Custom-Header":"MyValue"})
appendMessageTypeToURL no true Whether or not the Bayeux message type (handshake, connect, disconnect) is appended to URL of the Bayeux server (see above).
autoBatch no false Whether multiple publishes that gets queued up will be sent as a batch on the first occasion, without requiring explicit batching.

 
After you have configured the cometd object, it has not started the Bayeux communication yet. To start the Bayeux communication, you need to call handshake(), see the next section.

Previous users of the JavaScript Cometd implementation were used to call a method called init(). This method still exists, and it is a shorthand for calling configure() followed by handshake().
Follow the advices in the next section as they apply as well to init().

CometD JavaScript Handshake

JavaScript CometD API: Handshake

The call to handshake() (or to init()) is the one that initiates the Bayeux communication with the Bayeux server.

The Bayeux handshake performs 2 tasks:

  • the client and the server negotiate the type of transport to use
  • once the transport is negotiated successfully, the server informs the client with the detailed timings of the requests

As with several methods of the JavaScript CometD API, it is an asynchronous method: it returns immediately, well before the Bayeux handshake steps have completed.

Note
Calling handshake() does not mean that you have completed the handshake with the server when handshake() returns.

The handshake may fail for several reasons:

  • you mistyped the server URL
  • the transport could not be negotiated successfully
  • the server denied the handshake (for example, the authentication credentials were wrong)
  • the server crashed
  • there was a network failure

Therefore it is not a good idea to write this code:

// Configure and handshake
cometd.init('http://localhost:8080/cometd');

// Publish to a channel
cometd.publish('/foo', { foo: 'bar' });

It is not a good idea, because there is no guarantee that the call to publish() (which we cover in a later section) can actually succeed in contacting the Bayeux server.
Since the API is asynchronous, you have no way of knowing synchronously (i.e. by having handshake() return an error code or by throwing an exception) that the handshake failed.
Even if the handshake succeeds, you may still be "disconnected" from the Bayeux server, for example because the server crashed just after the successful handshake.

Fortunately there is a way to be notified about the details of the Bayeux protocol message exchange: by adding listeners to special channels (called meta channels).
This is explained in the section about subscriptions.

CometD JavaScript Subscription

JavaScript CometD API: Subscribing and Unsubscribing

Channels

The Bayeux specification defines the concept of a channel: it is like a messaging topic where interested parties can subscribe to receive information published onto the channel.
There are 3 types of channels:

  • meta channels
  • service channels
  • normal channels

A channel looks like a directory path such as /meta/connect (a meta channel; all meta channels starts with the prefix /meta/), or /service/chat (a service channel; all service channels starts with the prefix /service/) or /foo/bar (a normal channel).

Meta Channels

Meta channels are created by the Bayeux protocol itself.
It is not possible to subscribe to meta channels: the server will reply with an error message. However, it is possible to listen to meta channels (see below the difference between subscribing and listening).
It makes no sense to publish messages to meta channels: only the Bayeux protocol implementation creates and sends messages on meta channels.
Meta channels are useful on the client to listen for error messages like handshake errors (for example because the client did not provide the correct credentials) or network errors (for example to know when the connection with the server has broken or when it has been re-established).

Service Channels

Service channels are used in the case of request/response style of communication between client and server (as opposed to the publish/subscribe style of communication or normal channels).
While subscribing to service channels yields no errors, this is a no-operation for the server: the server ignores the subscription request.
It is possible to publish to service channels, with the semantic of a communication between a specific client (the one that's publishing the message on the service channel) and the server.
Service channels are useful to implement, for example, private chat messages: in a chat with userA, userB and userC, userA can publish a private message to userC (without userB knowing about) using service channels.

Normal Channels

Normal channels have the semantic of a messaging topic and are used in the case of publish/subscribe style of communication.
Usually, it is possible to subscribe to normal channels and to publish to normal channels; this can only be forbidden using a security policy on the Bayeux server.
Normal channels are useful to implement broadcasting of messages to all subscribed clients, for example in case of a stock price change.

Subscribers versus Listeners

The JavaScript CometD API has 2 APIs to work with channel subscriptions:

  • addListener() and the correspondent removeListener()
  • subscribe() and the correspondent unsubscribe()

The addListener() method:

  • must be used to listen to meta channel messages
  • may be used to listen to service channel messages (you may also use subscribe())
  • should not be used to listen normal channel messages (use subscribe() instead)
  • does not involve any communication with the Bayeux server, and as such can be called before calling handshake()
  • is synchronous: when it returns, you are guaranteed that the listener has been added

The subscribe() method:

  • must not be used to listen to meta channels messages (otherwise the server will return an error)
  • may be used to listen to service channel messages (you may also use addListener())
  • should be used to listen to normal channel messages
  • involves a communication with the Bayeux server and as such cannot be called before calling handshake()
  • is asynchronous: it returns immediately, well before the Bayeux server has received the subscription request

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Both addListener() and subscribe() return a subscription object that must be passed to, respectively, removeListener() and unsubscribe():

// Some initialization code
var subscription1 = cometd.addListener('/meta/connect', function() { ... });
var subscription2 = cometd.subscribe('/foo/bar/', function() { ... });

// Some de-initialization code
cometd.unsubscribe(subscription2);
cometd.removeListener(subscription1);

A common pattern of utilization is to handle the subscription code in an idempotent method, like this:

var _subscription;

// The idempotent method
function _refresh()
{
    _appUnsubscribe();
    _appSubscribe();
}

function _appUnsubscribe()
{
    if (_subscription) 
        cometd.unsubscribe(_subscription);
    _subscription = null;
}

function _appSubscribe()
{
    _subscription = cometd.subscribe('/foo/bar', function() { ... });
}

The same of course applies also for addListener()/removeListener().

The point is that you have to be careful in your application: you have to remove your subscriptions, in order to avoid to leak functions, or to execute functions more than once (since you could erroneously bind the same callback twice).
Refer also to the Primer for a discussion about using idempotent methods.

How do subscribe() and unsubscribe() behave in case the Bayeux server is not reachable (due to network failures or because the server crashed) ?
In subscribe() the local listener is first added to the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will not know that it has to send messages to this client and therefore on the client the local listener (although present) will never be invoked.
In unsubscribe(), the local listener is first removed from the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will still send the message to the client but there will be no local listener to dispatch to.

Listeners/Subscribers Exception Handling

If a listener or subscriber function throws an exception (for example, calls a method on an undefined object, etc.), then the error message is logged (at level "debug").
However, there is a way to intercept these errors by defining the global listener exception handler, that is invoked every time a listener or subscriber throws an exception:

cometd.onListenerException = function(exception, subscriptionHandle, isListener, message)
{
    // Uh-oh, something went wrong, disable this listener/subscriber
    // Object "this" points to the CometD object
    if (isListener)
        this.removeListener(subscriptionHandle);
    else
        this.unsubscribe(subscriptionHandle);
}

It is be possible to send messages to the server from the listener exception handler.
If the listener exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for extensions, see here.

Wildcard Subscriptions

It is possible to subscribe to several channels at once using wildcards, like this:

cometd.subscribe("/chatrooms/*", function(message) { ... });

A single asterisk has the meaning of matching a single channel segment, so in the example above it will match channels /chatrooms/12 and /chatrooms/15, but not /chatrooms/12/upload.
To match multiple channel segments, use the double asterisk:

cometd.subscribe("/events/**", function(message) { ... });

With the double asterisk, the channels /events/stock/FOO and /events/forex/EUR will match, as well as /events/feed and /events/feed/2009/08/03.

The wildcard mechanism works also for listeners, so it is possible to listen to all meta channels like this:

cometd.addListener("/meta/*", function(message) { ... });

By default, subscriptions to the global wildcards /* and /** result in an error, but this behavior can be changed by specifying a custom security policy on the Bayeux server.

The wildcards can only be specified as last segment of the channel, so these are invalid subscriptions: /**/foo or /foo/*/bar.

Meta Channel List

These are the meta channels available in the JavaScript CometD implementation:

  • /meta/handshake
  • /meta/connect
  • /meta/disconnect
  • /meta/subscribe
  • /meta/unsubscribe
  • /meta/publish
  • /meta/unsuccessful

Each meta channel is notified when the correspondent Bayeux message is handled by the JavaScript Cometd implementation.
The /meta/unsuccessful channel is notified in case of any failure.

By far the most interesting meta channel to subscribe to is /meta/connect, because it gives the status of the current connection with the Bayeux server. In combination with /meta/disconnect, it can be used, for example, to display a green "connected" icon or a red "disconnected" icon on the page, depending on the connection status with the Bayeux server.

This is a common pattern using the /meta/connect and /meta/disconnect channels:

var _connected = false;

cometd.addListener('/meta/connect', function(message)
{
    // if (cometd.getStatus() === 'disconnecting' || cometd.getStatus() === 'disconnected')
    if (cometd.isDisconnected()) // Available since 1.1.2
    {
        return;
    }
    var wasConnected = _connected;
    _connected = message.successful;
    if (!wasConnected && _connected)
    {
        // Reconnected
    }
    else if (wasConnected && !_connected)
    {
        // Disconnected
    }
});

cometd.addListener('/meta/disconnect', function(message)
{
    if (message.successful)
    {
        _connected = false;
    }
}

One small caveat with the /meta/connect channel is that /meta/connect is also used for polling the server.
Therefore, if a disconnect is issued during an active poll, the active poll is returned by the server and this triggers the /meta/connect listener.
The initial check on the status verifies that is not the case before executing the connection logic.

Another interesting usage of meta channels is when there is an authentication step during the handshake.
In this case the registration to the /meta/handshake channel can give details about, for example, authentication failures.

CometD JavaScript Publish

JavaScript CometD API: Publishing

The publish() method allow you to publish data onto a certain channel:

cometd.publish('/mychannel', { mydata: { foo: 'bar' } });

You cannot (and it makes no sense) to publish to a meta channel, and you can publish to a channel even if you are not subscribed to that channel.
However, you have to handshake before being able to publish.

As with other JavaScript CometD API, publish() involves a communication with the server and it is asynchronous: it returns immediately, well before the Bayeux server has received the message.

Note
Calling publish() does not mean that you have published the message when publish() returns.

If you have to publish several messages to different channels, you may want to use message batching.

CometD JavaScript Disconnection

JavaScript CometD API: Disconnecting

The JavaScript CometD implementation performs automatic reconnect in case of network or Bayeux server failures.
The reconnect parameters are described in the configuration section.

Short Network Failures

In case of temporary network failures, the client is notified through the /meta/connect channel (see this section about meta channels) with messages that have the successful field set to false (see also the archetypes in the primer as an example).
However, the Bayeux server may be able to keep the client's state, and when the network resumes the Bayeux server may behave as if nothing happened.
The client in this case just re-establishes the long poll, but any message published by the client during the network failure is not automatically re-sent (though it is possible to be notified, through the /meta/publish channel, of the failed publishes).

Long Network Failures or Server Failures

If the network failure is long enough, the Bayeux server times out the lost client, and deletes the state associated with it. The same happens when the Bayeux server crashes (except of course that the state of all clients is lost).
In this case, the reconnection mechanism on the client performs the following steps:

  • a long poll is re-attempted, but the server rejects it with a 402::Unknown client error message
  • a handshake is attempted, and the server normally accepts it and allocates a new client
  • upon the successful re-handshake, a long poll is re-established

If you register with meta channels, be aware of these steps, since a reconnection may involve more than one message exchange with the server.

Disconnecting

Calling the JavaScript CometD API disconnect() result in a message being sent to the Bayeux server, so that it can cleanup any state associated with that client.
As with all methods that involve a communication with the Bayeux server, it is an asynchronous method: it returns immediately, well before the Bayeux server has received the disconnect request.
If the server cannot be reached (because it is down or because of network failures), the JavaScript CometD implementation will stop any reconnection attempt and cleanup any local state.
It is normally safe to ignore if the disconnect() call has been successful or not: the client is in any case disconnected, its local state cleaned up, and if the server has not been reached it will eventually time out the client and cleanup any server-side state for that client.

Tip
If you are debugging your application with Firebug, and you shutdown the server, you'll see in the Firebug console the attempts to reconnect.
To stop those attempts, simply type in the Firebug command line: dojox.cometd.disconnect() (for Dojo) or $.cometd.disconnect() (for jQuery).

CometD JavaScript Message Batching

JavaScript CometD API: Message Batching

It is often needed by an application to send several messages to possibly different channels.

One, naive, way of doing it is the following:

cometd.handshake();

// Warning: non-optimal code
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });

You may think that the 3 publishes will leave the client one after the other, but that's actually not the case.
Remember that publish() is asynchronous (so it returns immediately), so the 3 publish() calls in sequence may return well before a single byte hits the network.

What happens is that the first publish() will be executed immediately, and the other 2 will be put in a queue, waiting for the first publish() to complete.
A publish() is complete when the server received it, the server sent back the meta response, and the client received the meta response for that publish.
When the first publish is completed, the second publish is executed and waited to complete. After that, finally the third publish() is executed.

Since CometD 1.1.2, a new configuration parameter called autoBatch can be set to true, allowing the implementation to automatically batch messages that have been queued up.
So, in the example above, the first publish() will be executed immediately, and when it completes, the implementation will batch the second and third publish() into one request to the server.
The autoBatch feature may be interesting for those systems where events received asynchronously and unpredictably, either at a fast rate or in bursts, end up in generating a publish() to the server: in such cases, using the batching API is not effective (as each event would generate only one publish()).
A bursts of events on the client will generate a bursts of publish() to the server, but this mechanism batches them automatically, making the communication more efficient.

This queueing mechanism is needed to avoid to queue a publish() behind a long poll. If not for this mechanism, the browser would receive 3 publish requests but only has 2 connections available, and one is already occupied by the long poll request. So the browser may decide to round robin the publish requests, so that the first publish goes on the second connection (remember that the first connection is already busy with the long poll request), which is free and it is actually sent over the network, schedule the second publish to the first connection (after the long poll returns), and schedule the third publish again to the second connection, after the first publish returns.
The result is that if you have a long poll timeout of 5 minutes, the second publish request may arrive to the server 5 minutes later than the first and the third publish request.

You can optimize the 3 publish using batching, which is a way to group messages together so that a single Bayeux message actually carries the 3 publish messages.

cometd.handshake();

cometd.batch(function()
{
    cometd.publish('/channel1', { product: 'foo' });
    cometd.publish('/channel2', { notificationType: 'all' });
    cometd.publish('/channel3', { update: false });
});

// Alternatively, but not recommended
cometd.startBatch()
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });
cometd.endBatch()

Note how the 3 publish() calls are now within a function passed to batch().
Alternatively, but less recommended, you can surround the 3 publish() calls between startBatch() and endBatch().

Warning
Remember to call endBatch() after having called startBatch().
If you don't, for example because an exception is thrown in the middle of the batch, your messages will continue to queue up, and your application will not work as expected.

Function batch() already does the correct batching for you (also in case of errors), so it's the recommended way to do message batching.

When a batch is started, subsequent API calls are not sent to the server, but instead queued up, until the batch is ended.
The end of the batch packs up all the queued messages into one single Bayeux message and send it over the network to the Bayeux server.

Message batching allow an efficient utilization of the network, as instead of making 3 requests/responses cycles, batching makes only one request/response cycle.

Batches can be made up of different API calls:

var _subscription;

cometd.batch(function()
{
    cometd.unsubscribe(_subscription);
    _subscription = cometd.subscribe('/foo', function(message) { ... });
    cometd.publish('/bar', { ... });
});

Batched messages will be processed by the Bayeux server in the order they are sent.

If you still want to risk and use the startBatch() and endBatch() calls, remember that they must be done from the same context of execution; message batching has not been designed to span multiple user interactions.
So, for example, it would be wrong to start a batch in, say, functionA (triggered by user interaction), and ending the batch in functionB (also triggered by user interaction and not called by functionA).
Similarly, it would be wrong to start a batch in functionA and then schedule (using setTimeout()) the execution of functionB to end the batch.

CometD JavaScript Transports

JavaScript CometD Transports

The Bayeux specification defines two mandatory transports:

  • long-polling
  • callback-polling

The JavaScript CometD implementation implements exactly these two transports.

For most recent browsers (such as Firefox 3.5) it is possible to use the long-polling transport also for cross-domain Bayeux communication, see below the cross-domain mode.

The long-polling Transport

The long-polling transport is the default transport.
This transport is used when the communication with the Bayeux server happens on the same domain, and in the cross-domain mode (see below).
The data is sent to the server by means of a POST request with Content-Type text/json via a plain XMLHttpRequest call.

The callback-polling Transport

The callback-polling transport is the transport that is used when the communication with the Bayeux server happens on a different domain (when the cross-domain mode is not supported, see below for the cross-domain mode section).

It is well known that XMLHttpRequest calls have restrictions when the invocation is directed to a domain different from the one the script has been downloaded (but see below the cross-domain mode for an alternative solution).
To overcome XMLHttpRequest restrictions, this transport uses the JSONP script injection: instead of using XMLHttpRequest it injects a <script> element whose src attribute points to the Bayeux server.
The browser will notice the script element injection and performs a GET request to the specified source URL.
The Bayeux server is aware that this is a JSONP request and replies with a JavaScript function that is then executed by the browser (and that calls back into the JavaScript Cometd implementation).

There are three main drawbacks in using this transport:

  • The transport is chattier.
    This is due to the fact that the browser executes the injected scripts sequentially, and until a script has been completely "downloaded", it cannot be executed.
    For example, imagine a communication that involves a script injection for the long poll, and a script injection for a message publish. The browser injects the long poll script, a request is made to the Bayeux server, but the Bayeux server holds the request waiting for server-side events (so the "script" is not "downloaded"). Then the browser injects the publish script, the request is made to the Bayeux server, which replies (so the "script is "downloaded"). However, the browser does not execute the second script, because it has not executed the first yet (since its "download" is not finished). In these conditions, the publish would be executed only after the long poll returns. To avoid this situation the Bayeux server, in case of callback-polling transport, resumes the client's long poll for every message that arrives from that client, and that's why the transport is chattier: the long poll returns more often.
  • The message size is limited.
    This is necessary to support IE7, that has a 2083 character limit for GET requests.
  • The reaction to failures is slower.
    This is due to the fact that if the script injection points to a URL that returns an error (for example the Bayeux server is down), this is silently ignored by the browser.

The cross-domain Mode

Firefox 3.5 introduced the capability for XMLHttpRequest calls to be performed towards a different domain (see here).

As of version 1.0.0.rc0, this is supported also in the JavaScript CometD implementation, with no configuration necessary on the client (if the browser supports XMLHttpRequest cross-domain calls, they will be used) and with a bit of configuration for the server. Refer to this document for the server configuration.

To use the cross-domain mode, you need:

  • a cross-domain compliant browser (for example Firefox 3.5)
  • a compliant server (for example Jetty configured with the CrossOriginFilter)

With this setup, even when the communication with the Bayeux server is cross-domain, the long-polling transport will be used, avoiding the drawbacks of the callback-polling transport.

CometD Java

CometD Java Implementation

The CometD Java implementation is based on the popular Jetty Http Server and Servlet Container, for both the client and the server.

The CometD Java implementation, though based on Jetty, is portable on other Servlet 2.5 compliant servlet containers (because it uses the portable Jetty Continuation API).

The war file resulting from the development of your CometD web application can be deployed to other Servlet 2.5 compliant servlet containers, but it will scale less because it will be less integrated with the servlet container.
When deployed to a Servlet 3.0 compliant servlet container, the CometD implementation will make use of the asynchronous features offered by the servlet container and will be fully portable and scalable (the scalability will be as good as the servlet container's implementation).

The CometD Java Implementation offers a client library and a server library, documented in details in the following sections.

CometD Server APIs

CometD Java Server Implementation

In order to run the CometD server implementation, you need to deploy a Java web application to a servlet container.

The web application needs to configure the CometD servlet (see here for configuration details) to interpret the Bayeux protocol.

Most of the times it is also needed to write one or more user-defined services that can act upon receiving messages on Bayeux channels.

CometD Java Server Configuration

CometD Java Server Configuration

Basic Configuration

The CometD servlet must be setup in web.xml.
If you followed the primer, then Maven has configured the web.xml file for you, but here we will detail its configuration.
This is a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

The org.cometd.server.continuation.ContinuationCometdServlet must be defined and mapped in web.xml, or otherwise the server will not be able to interpret the Bayeux protocol.
It is normally mapped on /cometd/*, but you can change the mapping url-pattern at your wish.

Here is the list of configuration init parameters accepted by the ContinuationCometdServlet:

Parameter Name Default Value Parameter Description
timeout 30000 The time, in milliseconds, that a server will wait for a message before responding to a long poll with an empty response.
interval 0 The time, in milliseconds, that specifies how long the client must wait between the end of one long poll requests and the start of the next.
maxInterval 10000 The max period of time, in milliseconds, that the server will wait for a new long poll from a client before that client is considered invalid and is removed
logLevel 0 The log level; 0 = warn, 1 = info, 2 = debug
multiFrameInterval -1 The period of time, in milliseconds, that specifies the client normal polling period in case the server detects more than one tab/frame connected from the same browser. A non-positive value means that the second tab/frame will be disconnected.
requestAvailable false Whether or not the current HttpServletRequest should be returned by Bayeux.getCurrentRequest()
filters The path of a JSON file, relative to the WEB-INF directory of the war, that specifies DataFilters to be installed
jsonDebug false Whether or not the full JSON input should be kept for debugging purposes
channelIdCacheLimit 0 The limit of the ChannelId cache. Set to -1 to disable caching, set to 0 for no limits, set to any positive value to clear the cache once the limit has been reached

 

Advanced Configuration

If you are using Jetty 7, you may want to configure also the CrossOriginFilter.
This filter implements the Cross-Origin Resource Sharing specification, and allows recent browsers that implements it (as of November 2009, Firefox 3.5.x, Chrome 3.x and Safari 4.x) to perform cross-domain JavaScript requests (see also the transport section).
Here is an example of web.xml configuration for the CrossOriginFilter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>cross-origin</filter-name>
        <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>cross-origin</filter-name>
        <url-pattern>/cometd/*</url-pattern>
    </filter-mapping>

</web-app>

Refer to this document for the filter configuration.

CometD Java Server Authorization

Java Server CometD API: Authorization

The Bayeux object can be configured with a org.cometd.SecurityPolicy object, which allows to control various steps of the Bayeux protocol such as handshake, subscription, publish, etc.
By default, the Bayeux object does not have a SecurityPolicy installed, which means that any operation is authorized.

The org.cometd.SecurityPolicy has a default implementation in org.cometd.server.AbstractBayeux$DefaultPolicy, that is useful as a base class in case of customization of the SecurityPolicy (see how authentication works for an example).

The org.cometd.SecurityPolicy methods are:

boolean canHandshake(Message message);

boolean canCreate(Client client, String channel, Message message);

boolean canSubscribe(Client client, String channel, Message message);

boolean canPublish(Client client, String channel, Message message);

The methods are self-speaking and control, respectively, if an handshake, a channel creation, a subscription to a channel and a publish to a channel are to be authorized.

The default implementation org.cometd.server.AbstractBayeux.DefaultPolicy:

  • allows any handshake
  • allows creation of channel only from clients that handshook and only if the channel is not a meta channel
  • allows subscription from clients that handshook, but not if the channel is a meta channels or the global channel wildcards /** and /*
  • allows publish from clients that handshook to any channel or from clients that want to handshake to the handshake meta channel only

To understand how to install your custom SecurityPolicy on the Bayeux object, see how it is done in the authentication howto.

CometD Java Server Services

CometD Java Server API: Services

A CometD service is a Java class that allow a developer to specify the code to run when Bayeux messages are received on Bayeux channels.

CometD Service Implementation

A CometD service is a Java class that extends the CometD class org.cometd.server.BayeuxService, that specifies the Bayeux channels the service is interested to, and that adheres to the contract required by the BayeuxService class:

public class EchoService extends BayeuxService                       (1)
{
    public EchoService(Bayeux bayeux)                                (2)
    {
        super(bayeux, "echo");                                       (3)
        subscribe("/echo", "processEcho");                           (4)
    }

    public void processEcho(Client remote, Map<String, Object> data) (5)
    {
        remote.deliver(getClient(), "/echo", data, null);            (6)
    }
}

This is a simple echo service that returns the message sent by the remote client on channel "/echo" to the remote client itself.

Note the following:

  • In (1) we extend from org.cometd.server.BayeuxService
  • In (2) we create a constructor that takes a org.cometd.Bayeux object
  • In (3) we call the superclass constructor, passing the Bayeux object and an arbitrary name of the service, in this case "echo"
  • In (4) we subscribe to channel "/echo", and we specify the name of a method that must be called when a message arrives to that channel
  • In (5) we define a method with the same name specified in (4), and with an appropriate signature (see below)
  • In (6) we use the org.cometd.Client API to echo the message back to that particular client

The contract that the BayeuxService class requires for callback methods is that the methods must have one of the following signatures:

// Obtains the remote client object and the message object
public void processEcho(Client remote, Message message)

// Obtains the remote client object and the message's data object
// (additional message information, such as the channel or the id is lost)
public void processEcho(Client remote, Map<String, Object> data)

// Obtains the remote client object, the channel name, the message object and the message id
public void processEcho(Client remote, String channelName, Message message, String messageId)

// Obtains the remote client object, the channel name, the message's data object and the message id
public void processEcho(Client remote, String channelName, Map<String, Object> data, String messageId)

Note that the channel name specified in the subscribe() method can be a wildcard, for example:

public class BaseballTeamService extends BayeuxService
{
    public BaseballTeamService(Bayeux bayeux)
    {
        super(bayeux, "baseballTeam");
        subscribe("/baseball/team/*", "processBaseballTeam");
    }

    public void processBaseballTeam(Client remote, String channelName, Map<String, Object> data, String messageId)
    {
        // Upon receiving a message on channel /baseball/team/*, forward to channel /events/baseball/team/*
        getBayeux().getChannel("/events" + channelName, true).publish(getClient(), data, null);
    }
}

Note also how in the first example we used Client.deliver() to send a message to a particular remote client, while in the second we used Channel.publish() to send a message to anyone who subscribed to channel "/events/baseball/team/*".

Once you have written your Bayeux services it is time to setup them in your web application, plain style or Spring style.

CometD Java Server Services Integration

CometD Java Server Services Integration

There are several ways to integrate your Bayeux services into your web application.

All of these ways are complicated by the fact that the Bayeux object is created by a servlet, and there is no easy way to detect, in general, when the Bayeux object has been created.

Integration via Configuration Servlet

The simplest way to initialize your web application with your Bayeux services is to use a configuration servlet.
This configuration servlet will have no mapping, because its only scope is to initialize (or "wire" together) your services for your web application to work properly.

Following you can find a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

</web-app>

Note how we specified <load-on-startup> to be 1 for the Cometd servlet (so that the Bayeux object gets created and put in the ServletContext), and to be 2 for the configuration servlet, so that it will be initialized only after the Cometd servlet has been initialized and hence the Bayeux object be available.

This is the code for the ConfigurationServlet:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab the Bayeux object
        Bayeux bayeux = (Bayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE);
        new EchoService(bayeux);
        // Create other services here

        // This is also the place where you can configure the Bayeux object
        // by adding extensions or specifying a SecurityPolicy
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

See here about the EchoService

Integration via Configuration Listener

Instead of using a configuration servlet, it is possible to use a configuration listener, by writing a class that implements ServletContextAttributeListener.

Following you can find the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.BayeuxInitializer</listener-class>
    </listener>

</web-app>

This is the code for the BayeuxInitializer:

public class BayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            // Grab the Bayeux object
            Bayeux bayeux = (Bayeux)event.getValue();
            new EchoService(bayeux);
            // Create other services here

            // This is also the place where you can configure the Bayeux object
            // by adding extensions or specifying a SecurityPolicy
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

Spring Integration

Bayeux Services Integration with Spring

Integration of CometD services with Spring is particularly interesting, since most of the times your Bayeux services will require other beans to perform their service.
Not all Bayeux services are as simple as the EchoService, and having Spring's dependency injection (as well as other facilities) integrated greatly simplifies development.

Below you can find 3 proposed strategies to integrate with Spring.

Late Spring Initialization

This strategy delays Spring initialization until the CometD servlet is initialized.
This strategy is suited for those cases where Spring beans can be initialized late because no other services or frameworks need Spring to be initialized upfront.

This strategy requires a bit of coding to glue Spring and Bayeux services together.
The reason to require glue code is twofold:

  • the Bayeux object is not created by Spring (but by the CometD servlet), so any bean that depends on the Bayeux object must be initialized only after the CometD servlet
  • the order of initialization of servlets and listeners in web.xml is not defined by the Servlet Specification (the order will only be fully specified in servlet 3.0)

So, in order to guarantee a portable initialization, a bit of code is required.

Below you can find the configuration files and the code needed to integrate with Spring.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.spring.LateSpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use a listener to initialize Spring. This listener completely replaces Spring's org.springframework.web.context.ContextLoaderListener, but it is based on the same classes used by it to perform context initialization at web application startup.
This means that the LateSpringBayeuxInitializer looks up by default Spring beans located in /WEB-INF/applicationContext.xml, or in locations specified by the servlet context init param contextConfigLocation, as per usual Spring configuration.

Second, Spring's applicationContext.xml file, defining the EchoService we defined earlier:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="echoService" class="com.acme.cometd.EchoService">
        <constructor-arg><ref bean="bayeux" /></constructor-arg>
    </bean>

</beans>

Note how there is a reference to the "bayeux" bean, which is not defined in any Spring XML file, but setup by LateSpringBayeuxInitializer, see below.

Lastly, the glue code to integrate Spring, the LateSpringBayeuxInitializer class referenced in web.xml above:

public class LateSpringBayeuxInitializer implements ServletContextListener, ServletContextAttributeListener
{
    private volatile ContextLoader loader;

    public void contextInitialized(ServletContextEvent event)
    {
    }

    public void contextDestroyed(ServletContextEvent event)
    {
        ContextLoader loader = this.loader;
        if (loader != null)
            loader.closeWebApplicationContext(event.getServletContext());
    }

    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();

            StaticListableBeanFactory factory = new StaticListableBeanFactory();
            factory.addBean("bayeux", bayeux);
            GenericApplicationContext bayeuxApplicationContext = new GenericApplicationContext(new DefaultListableBeanFactory(factory));
            bayeuxApplicationContext.refresh();

            loader = new BayeuxContextLoader(bayeuxApplicationContext);
            ApplicationContext applicationContext = loader.initWebApplicationContext(event.getServletContext());

            customizeBayeux(bayeux, applicationContext);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }

    protected void customizeBayeux(Bayeux bayeux, ApplicationContext applicationContext)
    {
    }

    private static class BayeuxContextLoader extends ContextLoader
    {
        private final ApplicationContext parentApplicationContext;

        public BayeuxContextLoader(ApplicationContext parentApplicationContext)
        {
            this.parentApplicationContext = parentApplicationContext;
        }

        @Override
        protected ApplicationContext loadParentContext(ServletContext servletContext) throws BeansException
        {
            return parentApplicationContext;
        }
    }
}

Note how you can override the customizeBayeux() method and further customize the Bayeux object by, for example, looking up a org.cometd.SecurityPolicy object from Spring's application context and set it on the Bayeux object. Or, in the same way, add your own Spring-configured Bayeux extensions to the Bayeux object.

Lazy Spring Initialization

This strategy allows for normal Spring initialization, but requires that Bayeux services are marked as lazy (and recursively all other beans/services that depend on Bayeux services), again to allow the CometD servlet to initialize properly.
This strategy is suited for those cases where other frameworks (for example, Struts2) require Spring to be initialized upfront.

This strategy requires a bit of coding to be able to access the Bayeux object from Spring configuration files.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.acme.cometd.spring.LazySpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use the normal Spring listener (ContextLoaderListener) to initialize Spring, but we also add another listener, LazySpringBayeuxInitializer.
The LazySpringBayeuxInitializer is the glue code that we need to make the Bayeux object accessible from Spring configuration files:

public class LazySpringBayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();
            BayeuxHolder.setBayeux(bayeux);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

The LazySpringBayeuxInitializer class makes use of another class, BayeuxHolder, that is very simple:

public class BayeuxHolder
{
    private static volatile Bayeux bayeux;

    public static void setBayeux(Bayeux bayeux)
    {
        BayeuxHolder.bayeux = bayeux;
    }

    public static Bayeux getBayeux()
    {
        return bayeux;
    }
}

At this point, we just need to write Spring's applicationContext.xml file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="com.acme.cometd.spring.BayeuxHolder" factory-method="getBayeux" lazy-init="true" />

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the Spring configuration file can have normal beans (such as nonLazyService), and how those beans can be injected in lazy beans such as our EchoService.
Also, note how we make use of the BayeuxHolder, which is lazy-initialized, to retrieve the Bayeux object stored by LazySpringBayeuxInitializer when the CometD servlet is initialized.

It is very important to note that the EchoService, which also is lazy-initialized, can be injected only in other lazy-initialized beans or in non-singleton beans (such as beans in scope prototype); if it is injected in singleton non-lazy beans, it will not be ready (because the CometD servlet is not yet initialized), and you will get an error.

Full Spring Initialization

This strategy initializes the Bayeux object directly in the Spring configuration file, and injects it in the servlet context, where it is picked up by the CometD servlet.
This strategy relies a bit more on CometD implementation details and may require changes if the implementation details change.
It may or may not require glue code, depending on other details of the application being developed (see below).
It requires also a bit more of discipline because the Bayeux object can now be configured in 2 places, the Spring configuration file and the web.xml file, and you don't want to split the Bayeux configuration in different places.

The web.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Spring's applicationContext.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="org.cometd.server.continuation.ContinuationBayeux">
        <property name="timeout" value="15000" />
    </bean>

    <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
        <property name="attributes">
            <map>
                <entry key="org.cometd.bayeux">
                    <ref local="bayeux" />
                </entry>
            </map>
        </property> 
    </bean>

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the implementation class of the Bayeux object is now referenced in applicationContext.xml and how the Bayeux object is exported into the servlet context via Spring's ServletContextAttributeExporter.
This only creates an un-initialized Bayeux object; only after the CometD servlet has been initialized the Bayeux object is ready to be used.
Because of this, it is important that the echoService (and any other Bayeux service and, recursively, other dependent services) is lazy-initialized.

Since the echoService is lazy-initialized, there must be a way to tell Spring to actually instantiate and initialize it when the application first needs it.

If you are using another framework that rely on Spring for dependency injection, then this step is performed by the other framework. For example, if you use Struts2 with Spring support, you don't need to tell Spring to instantiate and initialize you CometD services, because Struts2 will recognize that there is the need to inject your CometD service (for example in a Struts2 action) and Struts2 will ask Spring to provide it.
Therefore, the configuration above is perfectly sufficient.

However, if you are not using other frameworks, you need to manually tell Spring to instantiate and initialize your CometD services.
The technique to do so is similar to what described for the non-Spring service integration: writing a configuration servlet or listener that will reference your lazy-initialized CometD services. Below an example:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab Spring's ApplicationContent
        ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

        // Trigger CometD service initialization
        context.getBean("echoService");
    }
    
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

And of course this configuration servlet must be mapped with a higher value for the load-on-startup element in web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

org.cometd.Bayeux

Java Server Cometd API: Bayeux

The org.cometd.Bayeux interface is the central object that manages remote Cometd clients (or, better, their server-side counterpart) and channels.
One only of these objects exist for each web application, and it is created by the ContinuationCometdServlet, and put in the web application ServletContext to be accessed by other server side components.
See also the services section for how to retrieve and integrate the Bayeux object in your web application.

We have seen in the authorization section, how it is possible to use

Bayeux.setSecurityPolicy(SecurityPolicy policy)

to set the security policy for the Bayeux object, so that we can have full control on the authorization bits.

Another useful method is

Bayeux.getChannel(String channel, boolean create)

which allows to retrieve, and eventually create, a channel on which it is possible to publish messages, that will be received by all remote clients subscribed to that channel.
For example, imagine an online game where a player acts and we need to tell other players what that player did.
The acting player will communicate to the server its action, and the server will forward the action to other players:

public class GameService extends BayeuxService
{
    ...
    public void processPlayerAction(Client remote, Message message)
    {
        // Forward the action to other players
        Channel channel = getBayeux().getChannel("/dungeons/blue_cavern", false);
        channel.publish(getClient(), message.getData(), null);
    }
    ...
}

Sometimes, however, we need to send a message to a very specific client.
Imagine you're chatting with a friend, and you both logged in with your credentials at an imaginary site called www.bayeux-chat.org.
When you registered at www.bayeux-chat.org, the application stored your information in a database row with userId=100, and your friend was registered with userId=200.
When you connect to www.bayeux-chat.org to chat with your friend, you also have a Bayeux clientId=1234ABCD, while your friend has a Bayeux clientId=5678EFGH; you decide to start chatting privately with your friend.
Your chat message needs to contain an identifier of your friend (so the server will know to whom, of the thousands client connected, send your message), let's say it's the database's userId=200, but to deliver him your message, the server has to find the server-side Bayeux client object that represents your friend, and can do that using a mapping from userIds to clientId, and

Bayeux.getClient(String clientId)

in the following way:

public class ChatService extends BayeuxService
{
    ...
    public void processPrivateChat(Client remote, Map<String, Object> data)
    {
        // Extract the userId
        String friendUserId = (String)data.get("friendUserId");

        // Do not forget to check if that friendUserId is really your friend !

        // Map the database userId to a Bayeux clientId via some other service
        String friendClientId = this.userToClientMapper.get(friendUserId);
        
        Client remoteFriend = getBayeux().getClient(friendClientId);
        remoteFriend.deliver(getClient(), "/chat-room/1", data, null);
    }
    ...
}

How you map userIds to clientIds it's project dependent, but you can find some ideas in the authentication section.

Furthermore, it is possible to obtain the current HttpServletRequest from the Bayeux object via

Bayeux.getCurrentRequest()

This method returns null by default, unless the requestAvailable configuration parameter is set to true, see the configuration section.
From the HttpServletRequest it is possible to obtain the HttpSession, the ServletContext, etc. for any need you may have to interact with the servlet API.

Lastly, it is possible to add extensions to the Bayeux object, that will affect all clients, via:

Bayeux.addExtension(Extension)
Bayeux.removeExtension(Extension)

For a discussion about extensions, see here.

org.cometd.Channel

Java Server Cometd API: Channel

The org.cometd.Channel class represent a named "topic" to which clients subscribe to receive messages.

The name of a channel resembles that of a filesystem directory, for example: /level_3/dungeons/diamonds_cavern or /nyse/stocks/JAVA.
You can find more information on channel in the Bayeux channel section.

The most common operation on channel is to publish a message on it, using:

Channel.publish(Client local, Object data, String messageId)

The first argument represent the server-side local client that sends the message, and most of the time can be null, as the implementation will use a default value.
The second argument represent the data that you want to send, and the third argument is the messageId, which can be null, as the implementation will generate a new value.

public class StockService extends BayeuxService
{
    ...
    public void stockChanged(String stockName, double price)
    {
        Channel channel = getBayeux().getChannel("/nyse/stocks/" + stockName, false);
        if (channel != null)
        {
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("price", price);
            channel.publish(null, message, null);
        }
    }
    ...
}

A channel may have one or more org.cometd.DataFilters that are used to modify the incoming and outgoing messages (for example by replacing XML markups such as <foo> with &lt;foo&gt;). The data filters are added and removed via:

Channel.addDataFilter(DataFilter filter);
Channel.removeDataFilter(DataFilter filter);

Channels may be set to be persistent, which means that when the last subscriber unsubscribes, the channel object will remain in memory and can be accessed via the Bayeux API.

Channel.isPersistent()
Channel.setPersistent(boolean persistent)

Channels may also be set as lazy, which means that all messages sent to that channel will be marked as lazy. A lazy message is a message that does not wake up the long poll to be delivered immediately to the client, but it will be delivered on the first occasion (either a non-lazy message sent to the channel, or the long poll returning because it timed out).

Channel.isLazy()
Channel.setLazy(boolean lazy)

Subscriptions to channels are normally not done directly on the channel, but via the Client API.

org.cometd.Client

Java Server Cometd API: Client

The org.cometd.Client class is the server-side representation of a remote Bayeux client, that is, a client that connected successfully to the server using the Bayeux protocol.

An Client is identified by a unique clientId, which can be obtained via

Client.getId()

and can be used in conjunction with Bayeux.getClient(String).

Similarly to the Bayeux API, it is possible to add extensions to a Client instance. Differently from the Bayeux case where the extension is applied to all clients, in this case the extension is applied to this particular client only.

Client.addExtension(Extension extension)
Client.removeExtension(Extension extension)

For a discussion about extensions, see here.

Of particular interest are the APIs to add/remove listeners on a Client instance.

Client.addListener(ClientListener listener)
Client.removeListener(ClientListener listener)

The org.cometd.ClientListener interface is empty, but its subinterfaces are more interesting.

org.cometd.RemoveListener is the subinterface upon which

RemoveListener.removed(String clientId, boolean timeout)

gets called when the Bayeux server detects that a client has disconnected, either orderly (i.e. the client called disconnect()) or because or network failures or client crashes.
The listener is notified after the server-side data structures have already been cleaned up so, for example, calling Bayeux.getClient(String) from RemoveListener.removed(String, boolean) would return null.
There is an example of usage of the RemoveListener in the authentication example.

org.cometd.MessageListener is the subinterface upon which

MessageListener.deliver(Client from, Client to, Message message)

gets called when the client receives a message.
While normally it is better to use a Bayeux service to implement your business logic, a MessageListener may be used to count the number of messages, as well as to measure the latency between when a message was sent and when it was received, for example in collaboration with the timesync extension.

For completeness, there are other subinterfaces of ClientListener, but they are somewhat less interesting and documented in their relative javadocs.

We have already seen how it is possible to send a message using the Client interface.
It is however possible send multiple messages in a single batch, for example:

public class BatchingService extends BayeuxService
{
    ....
    public void processBatch(Client remote, Message message)
    {
        remote.startBatch();

        Map<String, Object> externalData = new HashMap<String, Object>();
        // Fill the external data
        remote.deliver(getClient(), "/external", externalData, null);

        Map<String, Object> emailData = new HashMap<String, Object>();
        // Fill the email data
        remote.deliver(getClient(), "/email", emailData, null);

        remote.endBatch();
    }
    ...
}

Multiple Clients

Multiple CometD Clients

The HTTP protocol recommends a connection limit of 2 connections per domain.
Virtually all browsers adhere to this recommendation, thus any iframes, tabs or windows on the same browser connecting to the same host need to share the two connections.

If two iframes/tabs/windows initiate a Bayeux communication, both will start a long poll connect request and both connections will be consumed.

The CometD Server implementation implements the multiple-clients advice specified in the Bayeux specification.
The server uses "BAYEUX_BROWSER" cookie to detect multiple CometD clients from the same browser.

If multiple clients are detected, then only one long poll connection is allowed and subsequent long poll requests will not wait for messages before returning. They will return immediately with the multiple-clients field of the advice object set to true. The advice will also contain an interval field set to the value of the "multiFrameInterval" servlet init parameter (see here). This instructs the client not to send another long poll until that interval has passed.

The effect of this advice is that additional client connections will poll the server with a period of "multiFrameInterval". This avoids consume both HTTP connections at the cost of some latency for the additional iframes/tabs/windows.

It is recommended that the client application monitor the /meta/connect channel for multiple-clients field in the advice. If detected, the application may ask the user to close the additional tabs, or it could automatically close them or take some other action.

CometD Client APIs

CometD Java Client Implementation

The CometD client implementation can be used in any JSETM or JEETM application.

It is made of one main class, org.cometd.client.BayeuxClient, that depends on Jetty's asynchronous HttpClient.

Typical usages of the CometD Java client are:

  • As the transport for a rich thick Java UI (for example, Swing) to communicate to a Bayeux Server (also via firewalls)
  • As a load generator to simulate thousands of CometD clients, like for example org.cometd.client.BayeuxLoadGenerator

A BayeuxClient also implements the org.cometd.Client interface, so all information given in the Client section regarding extensions and listeners is valid also for BayeuxClient.
Furthermore, the CometD Java Client API is very similar to the CometD JavaScript Client API, so reading that section may give you a better understanding.

The following sections will go in detail about the JavaScript Bayeux APIs and their implementation secrets.

CometD Java Client Handshake

Java CometD Client API: Hanshake

To initiate the communication with the Bayeux server, you need to call

BayeuxClient.start()

A typical usage is the following:

// Create (and eventually setup) Jetty's HttpClient
HttpClient httpClient = new HttpClient();
// Here setup Jetty's HttpClient, for example:
// httpClient.setMaxConnectionsPerAddress(2);
httpClient.start();

BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
// Here setup the BayeuxClient, for example:
// client.addListener(new MessageListener() { ... });
client.start();

The BayeuxClient is created with Jetty's HttpClient and with the URL that points to the Bayeux server.

When BayeuxClient.start() is called, the BayeuxClient will perform the handshake with the Bayeux server and then will establish the long poll connection, asynchronously.

Note
Calling start() does not mean that you have completed the handshake with the server when start() returns.

To verify if the handshake is successful, you can add a MessageListener before calling BayeuxClient.start():

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.addListener(new MessageListener() 
{ 
    public void deliver(Client from, Client to, Message message)
    {
        if (Bayeux.META_HANDSHAKE.equals(message.getChannel())
        {
            Boolean successful = message.get(Bayeux.SUCCESSFUL_FIELD);
            if (successful != null && successful)
            {
                // Here handshake is successful
            }
        }
    }
});
client.start();

CometD Java Client Subscription

Java CometD Client API: Subscribing and Unsubscribing

Subscription and unsubscription is done very similarly to the JavaScript counterpart explained here.

The Java API is only slightly different since it must register a MessageListener before subscribing to a channel:

public class Example 
{
    private static final String CHANNEL = "/foo";
    private final MessageListener fooListener = new FooListener();

    public void attach()
    {
        HttpClient httpClient = ...
        BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
        client.start();

        client.addListener(fooListener);
        client.subscribe(CHANNEL);
    }

    private static class FooListener implements MessageListener
    {
        public void deliver(Client from, Client to, Message message)
        {
            if (CHANNEL.equals(message.getChannel())
            {
                // Here we received a message on the channel
            }
        }
    }
}

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Differently from JavaScript, a MessageListener is notified for any type of channel (hence for meta channels as well as for normal channels).
Similarly, subscribe() only works properly for normal channels.

Unsubscription is straightforward: if you unsubscribe, messages on that channel will not be delivered to message listeners.
Using the Example class above:

public class Example 
{
    ...
    public void detach()
    {
        client.removeListener(fooListener);
        client.unsubscribe(CHANNEL);
    }
}

Tip
It is recommended that you remove also the MessageListener that you registered when you subscribed.
To make this simpler, avoid using the anonymous inner class style to register a message listener, since otherwise you will not have its reference when you want to remove it.

CometD Java Client Publish

Java CometD Client API: Publishing

Publishing a message on a channel is achieved using the method:

BayeuxClient.publish(String channel, Object data, String messageId)

A typical example is:

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.start();

Map<String, Object> data = new HashMap<String, Object>();
// Fill in the data
client.publish("/game/table/1", data, null);

Like its JavaScript counterpart, publishing data on a channel is an asynchronous operation.

Note
Calling publish() does not mean that you have published the message when publish() returns.

Message batching works in a way similar to the JavaScript message batching:

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.start();

client.startBatch();
try
{
    Map<String, Object> data = new HashMap<String, Object>();
    // Fill in the data map object
    client.publish("/game/table/1", data, null);

    Map<String, Object> extra = new HashMap<String, Object>();
    // Fill in the extra map object
    client.publish("/extra/1", data, null);
}
finally
{
    client.endBatch();
}

Warning
Remember to call endBatch() after having called startBatch(), for example in a finally block.
If you don't, your messages will continue to queue up, and your application will not work as expected.

CometD Java Client Disconnection

Java CometD Client API: Disconnecting

Like the JavaScript counterpart, disconnecting is straightforward:

BayeuxClient.disconnect();

CometD Extensions

CometD API: Extensions

The CometD implementation has the capability to add/remove extensions.
An extension is a function that is being called to give the chance to modify the message just before it is being sent (an outgoing extension) or just after it is being received (an incoming extension).

An extension normally adds fields to the message being sent or received in the ext object defined by the Bayeux specification (see here).

An extension is not a way to add business fields to a message, but rather a way to process all messages, including the meta messages used by the Bayeux protocol.

Extensions are normally setup on both the client and the server, since fields added by the client normally needs a special processing by the server; it may be possible that an extension is only client-side or only server-side, though.
Most of the times, however, they are needed in both client and server, and when the extension does not behave as expected, it's normally because the extension on one of the two sides is missing.

The JavaScript CometD Extensions are described in the next sections, and follow the same pattern used by the portable JavaScript CometD implementation: a portable implementation of the extension with bindings for the specific JavaScript toolkit, currently Dojo and jQuery.

Writing the Extension

An extension is a JavaScript object with 4 optional methods:

  • outgoing(message), called just before a message is being sent
  • incoming(message), called just after a message is received
  • registered(name, cometd), called when the extension is registered
  • unregistered(), called when the extension is unregistered

All 4 methods are optional, or there can be only one, or maybe two, three or all of them. If they are present, they will be invoked at the proper time.

Writing an extension that logs and counts the long polls is quite easy: we need a reference to the cometd object, that has the logging methods, and we need only the outgoing extension method:

var LoggerExt = function()
{
    var _cometd;
    var _counter;

    this.registered = function(name, cometd)
    {
        // Store the cometd object reference
        _cometd = cometd;
    };

    this.outgoing(message)
    {
        if (message.channel == '/meta/connect')
        {
            // Log the long poll
            _cometd._info('bayeux connect');

            // Count the long polls
            if (!message.ext) message.ext = {};
            if (!message.ext.logger) message.ext.logger = {};
            if (!message.ext.logger.counter) message.ext.logger.counter = 0;
            message.ext.logger.counter = ++_counter;
        }
    };
};

Note that also meta messages are passed to the extension methods; you normally have to filter the messages that the extension method receives by looking at the channel or at some other message value.
Note that the message can be modified by adding fields, normally in the ext field.

Note
Be careful to not overwrite the ext field that it may have been set by other extensions: check if it's present first.
It is also a good practice to group your extension fields so that there is no clash with other extensions (in the example above the only field - counter - is "grouped" in the message.ext.logger object).

The outgoing() and incoming() methods can avoid to return something, or return the message itself (or another message object). This means that the message has been processed by the extension and can therefore be processed by other extensions, if present, or processed by the implementation (either be sent to the server - for outgoing extensions - or be notified to listeners - for incoming extensions).
If null is returned by the extension method, this means that the processing should stop: the message will not be processed by other extensions and will not be further processed (therefore it will neither be sent to the server nor be notified to listeners).

Registering the Extension

The JavaScript CometD API defines 3 methods to manage extensions:

  • registerExtension(name, extension), to register an extension with the given name
  • unregisterExtension(name), to unregister the extension previously registered with the given name
  • getExtension(name), to obtain a reference to the extension previously registered with the given name

Following the example above, we can register the extension like this:

cometd.registerExtension('loggerExt', new LoggerExt());

From now on, the meta connect messages will be modified to carry the counter from the example extension above.

Unregistering the extension is similar:

cometd.unregisterExtension('loggerExt');

It is not possible to register 2 extensions under the same name.

You can register more than one extension, and they will be applied following the registration order: outgoing extensions methods will be called in registration order and, by default, incoming registration methods will be called in reverse registration order. See also the reverseIncomingExtensions configuration parameter in the configuration section.
For example, if you register extA and extB, then for outgoing messages the methods called are: extA.outgoing() and then extB.outgoing(), while for incoming messages the methods called are extB.incoming() and then extA.incoming().

Extension Exception Handling

While it is normally good practice to catch exceptions within extension functions, sometimes this is tedious to code, or there is no control about the quality of the extension (e.g. it's a third party extension).
The JavaScript CometD API provides a way to define the global extension exception handler that is invoked every time an extension throws an exception (for example, calling a method on an undefined object, etc.):

cometd.onExtensionException = function(exception, extensionName, outgoing, message)
{
    // Uh-oh, something went wrong, disable this extension
    // Object "this" points to the CometD object
    this.unregisterExtension(extensionName);

    // If the message is going to the server, add the error to the message
    if (outgoing)
    {
        // Assume we have created the message structure below
        var badExtension = message.ext.badExtensions[extensionName];
        badExtension.exception = exception;
    }
}

Be very careful to use the CometD object to publish messages within the extension exception handler, or you may end up in an infinite loop (the publish message is processed by the extensions, which may fail and call again the extension exception handler).
If the extension exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for listeners and subscribers, see here.

The following sections will explain in detail the usage of the provided extensions.

CometD Acknowledge Extension

CometD Acknowledge Extension

The acknowledged messages extension provides reliable ordered messaging to the Bayeux protocol.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.AcknowledgedMessagesExtension());

The AcknowledgedMessageExtension is a per-server extension that monitors handshakes from new clients, looking for clients that also support the acknowledged message extension and then adds the AcknowledgedMessagesClientExtension to each client during the handshake.

Once added to a client, the AcknowledgedMessagesClientExtension prevents messages being delivered on any request other than a long poll request.
This prevents the possibility of out of order delivery.
The extension also maintains a list of non-acknowledged messages and intercepts the traffic on the /meta/connect channel to insert and check acknowledge IDs.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.ack");

The client side extension binding for jQuery is provided by the file jquery.cometd-ack.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="AckExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-ack.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "ack".

Furthermore, the extension may be programmatically disabled/enabled before initialization by setting the ackEnabled boolean field on the cometd object:

// Disables the ack extension during handshake
cometd.ackEnabled = false;
cometd.init(cometdURL);

Acknowledge Extension Details

In order to enable message acknowledgement, both client and server must indicate that they support message acknowledgement.
This is negotiated during handshake. On handshake, the client sends {"ext":{"ack": "true"}} to indicate that it supports message acknowledgement.
If the server also supports message acknowledgment, it likewise replies with {"ext":{"ack": "true"}}.

The extension does not insert ack IDs to every message, as this would impose a significant burden on the server for messages sent to multiple clients (which would need to be reserialized to json for each client). Instead the ack ID is inserted in the ext field of the /meta/connect messages that are associated with message delivery. Each /meta/connect request contains the ack ID of the last received ack response: "ext":{"ack": 42}. Similarly, each /meta/connect response contains an ext ack ID that uniquely identifies the batch of responses sent.

If a /meta/connect message is received with an ack ID lower that any unacknowledged messages held by the extension, then these messages are requeued prior to any more recently queued messages and the /meta/connect response sent with a new ack ID.

Demo

There is an example of acknowledged messages in the Dojo chat demo that comes bundled with the Cometd distribution.
The example can also be run by building the Cometd project.

To run the demo, follow these steps:

$ cd cometd-demo
$ mvn jetty:run

Then point your browser to http://localhost:8080/dojo-examples/chat/ and make sure to check "Enable reliable messaging".

Use two different browsers instances to begin a chat session, then briefly disconnect one browser from the network (you can do this by setting the "work offline" feature).
While one browser is disconnected, type some chat in the other browser and this will be received when the disconnected browser is reconnected to the network.

Note that if the disconnected browser is disconnected for in excess of maxInterval (default 10s), then the client will be timed out and the unacknowledged queue discarded.

CometD Timestamp Extension

CometD Timestamp Extension

The timestamp extension add a timestamp to the message object, for every message sent by the client and/or server.
It is a non standard extension because it does not add the additional fields to the ext field, but to the message object itself.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for timestamped messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.TimestampExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timestamp");

The client side extension binding for jQuery is provided by the file jquery.cometd-timestamp.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timestamp.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timestamp".

CometD Timesync Extension

CometD Timesync Extension

The timesync extension uses the messages exchanged between a client and a server to calculate the offset between the client's clock and the server's clock.
This is independent from the timestamp extension, which uses the local clock for all timestamps.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.TimesyncExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/timesync.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timesync");

The client side extension binding for jQuery is provided by the file jquery.cometd-timesync.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timesync.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timesync".

Timesync Extension Details

The timesync extension allows the client and server to exchange time information on every handshake and connect message so that the client may calculate an approximate offset from it's own clock epoch to that of the server.
The algorithm used is very similar to the NTP algorithm.

With each handshake or connect, the extension sends timestamps within the ext field like:

{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}

where:

  • tc is the client timestamp in ms since 1970 of when the message was sent
  • l is the network lag that the client has calculated
  • o is the clock offset that the client has calculated

The accuracy of the offset and lag may be calculated with tc-now-l-o, which should be zero if the calculated offset and lag are perfectly accurate.

A Bayeux server that supports timesync, should respond only if the measured accuracy value is greater than accuracy target.
The response will be an ext field like:

{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}

where:

  • tc is the client timestamp of when the message was sent
  • ts is the server timestamp of when the message was received
  • p is the poll duration in ms - ie the time the server took before sending the response
  • a is the measured accuracy of the calculated offset and lag sent by the client

On receipt of the response, the client is able to use current time to determine the total trip time, from which p is subtracted to determine an approximate two way network traversal time. Thus:

  • lag = (now-tc-p)/2
  • offset = ts-tc-lag

In order to smooth over any transient fluctuations, the extension keeps a sliding average of the offsets received.
By default this is over 10 messages, but this can be changed by passing a configuration object during the creation of the extension:

// Unregister the default timesync extension
cometd.unregisterExtension('timesync');

// Re-register with different configuration
cometd.registerExtension('timesync', new org.cometd.TimeSyncExtension({ maxSamples: 20 }));

The client-side timesync extension also exposes several methods to deal with the result of the time synchronization:

  • getNetworkLag(), to obtain the calculated network latency between client and server
  • getTimeOffset(), to obtain the offset between the client's clock and the server's clock in ms
  • getServerTime(), to obtain the server's time
  • setTimeout(), to schedule a function to be executed at a certain server time

Reload

Reload Extension

The reload extension allows a page to be loaded (or the same page to be reloaded) without having to re-handshake in the new (or reloaded) page, therefore resuming the existing CometD connection.
This extension requires only the client-side extension.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/reload.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.reload");

The client side extension binding for jQuery is provided by the file jquery.cometd-reload.js. This file must be included in the HTML page via the <script> tag, along with the jQuery cookie plugin and the reload extension implementation:

<script type="text/javascript" src="jquery.cookie.js"></script>
<script type="text/javascript" src="ReloadExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-reload.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "reload".

Reload Extension Details

The reload extension allows a page to be reloaded and an existing CometD connection resumed by the reloaded page.

To activate the reload extension, the page must call cometd.reload() just before the page is reloaded (for example, after a link is clicked or on the page unload event).
This saves a short lived cookie with the connection details.
The new page still needs to call subscribe to the channels it is interested in to register the callbacks, since the client state has been completely reset due to the reload.

An example simple usage is:

<html>
    <head>
        <script type="text/javascript" src="dojo/dojo.js"></script>
        <script type="text/javascript">
            dojo.require("dojox.cometd");
            dojo.require("dojox.cometd.reload");

            dojox.cometd.init({ url: "/context/cometd", logLevel: "info" });
            dojox.cometd.subscribe("/some/channel", function() { ... });
            dojox.cometd.subscribe("/some/other/channel", function() { ... });
            
            // On page unload, call dojox.cometd.reload()
            dojo.addOnUnload(dojox.cometd, "reload");
        </script>
    </head>
    <body>
    ...
    </body>
</html>

CometD Howtos

CometD How-Tos

This is a list of how-tos for common problems that you can find while developing with CometD.

Primer

Preparation

Working on a project that uses the CometD API requires some preparation, especially regarding tools, that can save you a huge amount of time.
One tool that should not be missing is Firebug (if you're using Firefox for development), or the equivalent for Internet Explorer 8, called Developer Tools.

The CometD project is built using Maven, and using Maven to build also your application is a natural fit.
The Primer will use Maven as the basis for the setup, build and run of your application, but the same concepts can be applied by other build tools as well.

Warning
If you are working in the Windows OS, avoid at all costs to use a path that contains spaces, such as "C:\Document And Settings\", as your base path.
Use a base path such as "C:\CometD\" instead.

Project Setup

The project can be setup in 2 ways, using the Maven Way or the Non-Maven Way.
For both, you can follow the detailed explanation of how some of the files of the project have been setup.

The Maven Way


Setting up a project based on the CometD libraries using Maven is very simple, and leverages the Maven archetypes, which create the skeleton of the project, in a style very similar to Rails scaffolding.
Issue the following command from a directory that does not contain a pom.xml file (otherwise you will get a Maven error), for example an empty directory:

$ mvn archetype:generate -DarchetypeCatalog=http://cometd.org
...
Choose archetype:
1: local -> cometd-archetype-dojo-jetty6 (CometD :: Archetypes :: Dojo and Jetty6)
2: local -> cometd-archetype-jquery-jetty6 (CometD :: Archetypes :: jQuery and Jetty6)
3: local -> cometd-archetype-dojo-jetty7 (CometD :: Archetypes :: Dojo and Jetty7)
4: local -> cometd-archetype-jquery-jetty7 (CometD :: Archetypes :: jQuery and Jetty7)
Choose a number:  (1/2/3/4): 

As you can see, there are 4 archetypes available, that build a skeleton application using the Dojo or jQuery JavaScript toolkits, both with the choice of using Jetty6 or Jetty7.
Let's choose Dojo with Jetty7, so choice number 3.
The archetype generation will ask few more questions and generate the application skeleton for you, for example:

Choose a number:  (1/2/3/4): 3
Define value for groupId: : org.cometd.primers
Define value for artifactId: : dojo-jetty7-primer
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  org.cometd.primers: : 
Confirm properties configuration:
jettyVersion: 7.0.1.v20091125
cometdVersion: 1.0.0
groupId: org.cometd.primers
artifactId: dojo-jetty7-primer
version: 1.0-SNAPSHOT
package: org.cometd.primers
 Y: : 
[INFO] BUILD SUCCESSFUL

Then:

$ cd dojo-jetty7-primer/

The skeleton project has now been created as follows:

$ tree
.
|-- pom.xml
`-- src
    `-- main
        |-- java
        |   `-- org
        |       `-- cometd
        |           `-- primers
        |               |-- BayeuxInitializer.java
        |               `-- HelloService.java
        `-- webapp
            |-- WEB-INF
            |   `-- web.xml
            |-- application.js
            `-- index.jsp

The skeleton project is ready to be run with the following command:

$ mvn install jetty:run
...

Now point your browser at http://localhost:8080/dojo-jetty7-primer, and you should see this message:

CometD Connection Succeeded
Server Says: Hello, World

That's it. You have already written your first CometD application :-)


The Non-Maven Way

The first step is to configure your favorite JavaScript toolkit, in our example Dojo, that must be served by the web container.
Using the Maven Way, this is obtained automatically by overlaying the CometD Dojo bindings war file, cometd-javascript-dojo-[version].war, but here must be done manually.
The cometd-javascript-dojo-[version].war is located in the cometd-javascript/dojo/target directory of the CometD distribution.

  • Unpack the Dojo toolkit to a directory.
  • Delete the dojox/cometd directory provided by Dojo and replace it with the one contained in the cometd-javascript-dojo-[version].war.
    The content of the dojox/cometd directory should be the following:

    dojox/cometd
    |-- ack.js
    |-- reload.js
    |-- timestamp.js
    `-- timesync.js
    
  • Delete the file dojox/cometd.js provided by Dojo and replace it with the one at the same path in the cometd-javascript-dojo-[version].war.
  • Add the org directory from the cometd-javascript-dojo-[version].war, and all its content, at the same level of the dojox directory.

The final content, equivalent to that produced by the Maven way, should be like this, for CometD 1:

.
|-- dijit
|-- dojo
|-- dojox
|   |-- cometd
|   |   |-- ack.js
|   |   |-- reload.js
|   |   |-- timestamp.js
|   |   `-- timesync.js
|   `-- cometd.js
|-- org
|   |-- cometd
|   |   |-- AckExtension.js
|   |   |-- ReloadExtension.js
|   |   |-- TimeStampExtension.js
|   |   `-- TimeSyncExtension.js
|   `-- cometd.js
|-- WEB-INF
|   |-- classes
|   |   `-- org
|   |       `-- cometd
|   |           `-- primers
|   |               |-- BayeuxInitializer.class
|   |               `-- HelloService.class
|   |-- lib
|   |   |-- cometd-api-[version].jar
|   |   |-- cometd-java-server-[version].jar
|   |   |-- jetty-continuation-[version].jar
|   |   |-- jetty-servlets-[version].jar
|   |   `-- jetty-util-[version].jar
|   `-- web.xml
|-- application.js
`-- index.jsp

and like this for CometD 2:

.
├── dijit
├── dojo
├── dojox
│   ├── cometd
│   │   ├── ack.js
│   │   ├── reload.js
│   │   ├── timestamp.js
│   │   └── timesync.js
│   ├── cometd.js
├── org
│   ├── cometd
│   │   ├── AckExtension.js
│   │   ├── ReloadExtension.js
│   │   ├── TimeStampExtension.js
│   │   └── TimeSyncExtension.js
│   └── cometd.js
├── WEB-INF
│   ├── classes
│   │   └── org
│   │       └── cometd
│   │           └── primers
│   │               ├── BayeuxInitializer.class
│   │               └── HelloService.class
│   ├── lib
│   │   ├── bayeux-api-[version].jar
│   │   ├── cometd-java-common-[version].jar
│   │   ├── cometd-java-server-[version].jar
│   │   ├── jetty-continuation-[version].jar
│   │   ├── jetty-jmx-[version].jar
│   │   ├── jetty-servlets-[version].jar
│   │   └── jetty-util-[version].jar
│   └── web.xml
├── application.js
└── index.jsp

The org directory contains the new shared CometD implementation and the shared extensions, while the correspondent files in the dojox directory are the Dojo bindings. Other bindings exist for the jQuery toolkit, but the shared CometD implementation is the same.

The second step is to configure the server side. If you use Java, this means that you have to setup the CometD servlet that respond to the Bayeux messages.
The details of the server side configuration and service development are explained here for CometD 1 and here for CometD 2.

The last step is to write a JSP (or HTML) file that downloads the JavaScript dependencies and the JavaScript application, as explained in the following section.


Setup Details


The JSP file, index.jsp, contains the reference to the JavaScript toolkit dependencies and to the JavaScript application file:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
    <script type="text/javascript" src="${pageContext.request.contextPath}/dojo/dojo.js.uncompressed.js"></script>
    <script type="text/javascript" src="application.js"></script>
    <script type="text/javascript">
        var config = {
            contextPath: '${pageContext.request.contextPath}'
        };
    </script>
</head>
<body>
    ...
</body>
</html>

It also configures a JavaScript configuration object, config, with variables that may be needed by the JavaScript application. This is totally optional.

The JavaScript application, contained in the application.js file, configures the cometd object and starts the application.
Here is a sketch of what the archetypes provide:

dojo.require("dojox.cometd");

dojo.addOnLoad(function()
{
    var _connected = false;

    function _connectionSucceeded()
    {
        dojo.byId('body').innerHTML = 'CometD Connection Succeeded';
    }

    function _connectionBroken()
    {
        dojo.byId('body').innerHTML = 'CometD Connection Broken';
    }

    function _metaConnect(message)
    {
        var wasConnected = _connected;
        _connected = message.successful === true;
        if (!wasConnected && _connected)
        {
            _connectionSucceeded();
        }
        else if (wasConnected && !_connected)
        {
            _connectionBroken();
        }
    }

    var cometd = dojox.cometd;

    // Disconnect when the page unloads
    dojo.addOnUnload(function()
    {
        cometd.disconnect();
    });

    var cometURL = location.protocol + "//" + location.host + config.contextPath + "/cometd";
    cometd.configure({
        url: cometURL,
        logLevel: 'debug'
    });

    cometd.addListener('/meta/connect', _metaConnect);

    cometd.handshake();
});

Note the following:

  • The use of dojo.addOnLoad() to wait for the document to load up before executing the cometd object initialization.
  • The use of dojo.addOnUnload() to disconnect when the page is refreshed or closed.
  • The use of the function _metaConnect() to detect when the Bayeux communication has been successfully established (or re-established). This is totally optional, but highly recommended.
    Be warned that the use of the _metaConnect() along with the _connected status variable can result in your code (that in this simple example sets the innerHTML property) to be called more than once if, for example, you experience temporary network failures or if the server restarts.

    Therefore is it very important that the code that you put in the _connectionSucceeded() function must be idempotent.
    In other words, you have to make sure that if the _connectionSucceeded() function is called more than one time, it will behave exactly as if it is called exactly once.

Authentication

CometD Authentication

Authentication is a complex task, and can be achieved in many ways, and most often each way is peculiar to a particular project.

Below you can find few ideas on how you can perform authentication.

Authentication via XMLHttpRequest

Authentication information could be passed by the CometD JavaScript client to the server via XMLHttpRequest.open(method, url, async, user, password), but this is currently not supported.
The main reason is that it will not work in the cross domain case, since XMLHttpRequest does not support cross domain authentication.

Authentication via HTTP request headers

Authentication information can be passed by the CometD JavaScript client to the server via HTTP request headers in each CometD request, and verified by the server (for example via a servlet filter).
It's possible to specify such request headers using the requestHeaders configuration parameter as specified here.

Authentication via Bayeux SecurityPolicy and Extension

This method is more complicated than previous ones, but gives more flexibility.
Authentication information can be passed by the CometD JavaScript client to the server during Bayeux handshake.
The CometD JavaScript client handshake() and init() methods take an extra object that's merged with the handshake message, for example:

cometd.configure({
    url: 'http://localhost:8080/myapp/cometd'
});

// Register a listener to be notified of authentication success or failure
cometd.addListener('/meta/handshake', function(message) 
{
    var auth = message.ext && message.ext.authentication;
    if (auth && auth.failed === true)
    {
        // Authentication failed, tell the user
        window.alert('Authentication failed!');
    }
});

var username = 'foo';
var password = 'bar';

// Send the authentication information
cometd.handshake({
    ext: {
        authentication: {
            user: username,
            credentials: password
        }
    }
});

The Bayeux specification suggests that authentication information is stored in the ext field of the handshake message (see here).

On the server, we need to configure the Bayeux object with a org.cometd.SecurityPolicy (to deny the handshake to clients that provide bad authentication information) and with an org.cometd.Extension to process the successful authentication.

It is possible to configure the Bayeux object using the same methods used to integrate Bayeux services (see also the Spring Integration).
For example, using a configuration servlet:

public class BayeuxInitializer extends GenericServlet
{
    @Override
    public void init() throws ServletException
    {
        Bayeux bayeux = (Bayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE);
        BayeuxAuthenticator authenticator = new BayeuxAuthenticator();
        bayeux.setSecurityPolicy(authenticator);
        bayeux.addExtension(authenticator);
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

Below you can find the code for the BayeuxAuthenticator class:

public class BayeuxAuthenticator extends AbstractBayeux.DefaultPolicy implements Extension, RemoveListener (1)
{
    @Override
    public boolean canHandshake(Message message)                                                           (2)
    {
        Map<String, Object> ext = message.getExt(false);
        if (ext == null)
            return false;

        // Be sure the client does not cheat us
        ext.remove("authenticationData");

        Map<String, Object> authentication = (Map<String, Object>)ext.get("authentication");
        if (authentication == null)
            return false;

        Object authenticationData = verify(authentication);                                                (3)
        if (authenticationData == null)
            return false;

        // Store the authentication result in the message for later processing
        ext.put("authenticationData", authenticationData);                                                 (4)

        return true;
    }

    public Message sendMeta(Client remote, Message responseMessage)                                        (5)
    {
        if (Bayeux.META_HANDSHAKE.equals(responseMessage.getChannel()))                                    (6)
        {
            Message requestMessage = responseMessage.getAssociated();                                      (7)

            Map<String, Object> requestExt = requestMessage.getExt(false);
            Object authenticationData = requestExt.get("authenticationData");
            if (authenticationData != null)
            {
                // Authentication successful

                // Link authentication data to the remote client                                           (8)

                // Be notified when the remote client disappears
                remote.addListener(this);                                                                  (9)
            }
            else
            {
                // Authentication failed
                
                // Add extra fields to the response                                                        (10)
                Map<String, Object> responseExt = responseMessage.getExt(true);
                Map<String, Object> authentication = new HashMap<String, Object>();
                responseExt.put("authentication", authentication);
                authentication.put("failed", true);

                // Tell the client to stop any further attempt to handshake                                (11)
                Map<String, Object> advice = new HashMap<String, Object>();
                advice.put(Bayeux.RECONNECT_FIELD, Bayeux.NONE_RESPONSE);
                responseMessage.put(Bayeux.ADVICE_FIELD, advice);
            }
        }
        return responseMessage;
    }

    public void removed(String clientId, boolean expired)                                                  (12)
    {
        // Unlink authentication data from the remote client                                               (13)
    }
    
    // Other methods omitted
}

Note that:

  • in (1) we make BayeuxAuthenticator be a SecurityPolicy, an Extension and a RemoveListener, since the code is really tied together
  • in (2) we override canHandshake() (a SecurityPolicy method), where we extract the authentication information from the message sent by the client
  • in (3) we verify the authentication information sent by the client, and obtain back server-side authentication data that we can later associate with the remote client
  • in (4) we store the server-side authentication data in the message to be processed later in (8)
  • in (5) we implement sendMeta() (an Extension method), which is called just after canHandshake() and just before sending the handshake response to the remote client
  • in (6) we perform our authentication logic only for handshake messages (i.e. messages that are sent on the handshake meta channel)
  • in (7) we retrieve the request message sent by the client from the response message that has been passed to sendMeta(), and we further retrieve the server-side authentication data set in (4)
  • in (8) we link the server-side authentication data to the remote client object
  • in (9) we register to be notified when the remote client disappears (which we will react to in (12))
  • in (10) we may want to add extra fields to the response message to detail why the authentication failed to the client
  • in (11) we set a Bayeux advice that tells the Bayeux client implementation to not retry to handshake automatically (since retrying automatically will resend the same credentials, and the authentication will fail again)
  • in (12) we implement removed() (a RemoveListener method), which is called when the remote client disappears, either because it disconnected or because it crashed
  • in (13) we unlink the server-side authentication data from the remote client object, the operation opposite to (8)

The reason for all this machinery is that at the moment of the invocation of canHandshake() the org.cometd.Client (server-side) object representing the remote client has not been created yet; it is only created if the handshake is allowed.

The most important steps are the number 8 and the number 13, where the server-side authentication data is linked/unlinked to/from the org.cometd.Client object.
This linking depends very much from project to project.
It may link a database primary key (of the row representing the user account) with the remote client id (obtained with remote.getId()), and/or viceversa, or it may link OAUTH tokens with the remote client id, etc.

Each Bayeux message always come with a client id, which can be thought as similar to the Http session id.
In the same way it is widespread practice to put the server-side authentication data in the HttpSession object (identified by the Http session id), in CometD web applications we can put server-side authentication data in a data structure where it can be later retrieved by Bayeux client id.
This data structure representing the "session" of a Bayeux client is not (yet) offered by the CometD API, and must be therefore coded by the CometD web application developer.

The Bayeux client ids are long, randomly generated numbers, exactly like Http session ids, and offer the same level security offered by a Http session id.
If an attacker manages to sniff a Bayeux client id, it can impersonate that Bayeux Client exactly in the same way it can sniff a Http session id and impersonate that Http session.
And, of course, the same solutions to this problem used to secure Http applications can be used to secure CometD web applications.

Load Testing

Load Testing

Please look at CometD 2 Load Testing.

CometD 2.x Reference

CometD 2.x Reference

CometD 2.5.0 Reference Manual

CometD 2.4.x Reference Manual

Older CometD 2.3.x Reference Documentation

The pages below refer to CometD 2.3.x and are maintained for historical purposes.
We strongly suggest to upgrade to CometD 2.4.0.

CometD 2 Concepts

CometD 2 Concepts

The CometD project implements various Comet techniques to provide a scalable web messaging system, one that can run over HTTP or over other emerging web protocol such as WebSocket.

Definitions

Let's define the client as the entity that initiates a connection, and the server as the entity that accepts the connection.
The connection established is persistent - that is, it remains open until either side decides to close it.

Typical clients are browsers (after all, we are in a web environment), but may also be other applications such as Java applications, browser plugin applications (such as Silverlight), or scripts in any scripting language.

Depending on the Comet technique employed, a client may open more than one physical connection to the server, but we can assume there exists only one logical conduit between the client and the server.

The CometD project uses the Bayeux protocol to exchange information between the client and the server.
The unit of information exchanged is a Bayeux message and it is formatted in JSON.
A message contains several fields, some of which are mandated by the Bayeux protocol, and others may be added by applications.
A field is a key/value pair, and saying that a message has a foo field means that the message has a field whose key is the string "foo".

All messages that are exchanged between client and server have a channel field.

There are 3 types of channels: meta channels, service channels and broadcast channels.
Meta channels are created by the Bayeux implementation, and applications cannot create new meta channels.
Service channels and broadcast channels are created by the application; they can be created at any time, and as many as necessary.
Channels are represented by a string and are similar to a URL path, for example: /meta/handshake, /service/game or /foo/bar.
A channel that starts with /meta/ is a meta channel, a channel that starts with /service/ is a service channel, and all other channels are broadcast channels.

A message whose channel field is a meta channel is referred to as a meta message, and similarly there are service messages and broadcast messages.

The High Level View

CometD implements a web messaging system, in particular a web messaging system based on the publish/subscribe paradigm.

In a publish/subscribe messaging system publishers send messages, and those messages are characterized in classes. Subscribers express their interest in one or more classes of messages, and receive only messages that match the interest they subscribed to. Senders, in general, have no idea which recipient or how many recipients will receive the messages they publish.

In CometD, the characterization of messages in classes is given by the channel field.
The channel is a central concept in CometD: publishers publish messages to channels, and subscribers subscribe to channels to receive messages.
We will see how this is strongly reflected in the CometD APIs.

One of the features of messaging systems is their topology, and CometD implements the hub-spoke topology.
In the default configuration, this means that there is one central server (the hub) and all clients are connected to that server via conduit links (the spokes).

hub-spoke topology

In CometD, the server receives messages from publishers and, if the message's channel is a broadcast channel, re-routes the messages to interested subscribers.
Meta messages and service messages are treated specially by the CometD server, and are not re-routed to any subscriber (in fact, by default it is forbidden to subscribe to meta channels, and it is a no-operation to subscribe to service channels).

For example, let's imagine that clientAB is subscribed to channels /A and /B, and clientB is subscribed to channel /B.
If a publisher publishes a message on channel /A, only clientAB will receive it. On the other hand, if a publisher publishes a message on channel /B, both clientAB and clientB will receive the message. Furthermore, if a publisher publishes a message on channel /C, neither clientAB nor clientB will receive the message, that will just end its journey on the server.
Re-routing broadcast messages is the default behavior of the server, and it does not need any application code to perform the re-routing.

Looking from high level then, you will see messages flowing back and forth among clients and server through the conduits.
A single broadcast message may arrive to the server and be re-routed to all clients; you can imagine that when it arrives on the server, the message is copied and that a copy is sent to each client (although, for efficiency reasons, this is not exactly what happens). If the sender is also subscribed to the channel it published the message to, it will receive a copy of the message back.

There are open questions, though:
What an application can do with meta messages and service messages, if their journey ends on the server ?
How can a client communicate with another, specific, client ?
Can the server be a publisher too ?

To answer these and others questions, we need to take a closer look at how CometD works. Read on.

A Lower Level View

In the following sections we will take a deeper look at how the CometD implementation works.

It should be clear by now that CometD, at its heart, is a client/server system that communicates via a protocol, the Bayeux protocol.

In the CometD implementation, the client/server communication is captured by the half-object plus protocol pattern: when a half-object on the client establishes a communication conduit with the server, its correspondent half-object is created on the server, and the two may - logically - communicate.
In CometD, a variation of this pattern is used, because there is the need to abstract the transport used to carry messages to and from the server.
The transport can be based on the HTTP protocol, but in recent CometD versions also on the WebSocket protocol (and more transports can be plugged in).

In broad terms, the "client" is composed of the client half-object (represented by the org.cometd.Cometd object in JavaScript, and by the org.cometd.bayeux.client.ClientSession class in Java) and of the client transport (represented by the org.cometd.Transport object in JavaScript and by the org.cometd.client.transport.ClientTransport in Java).
The "server" is a more complex entity represented by an instance of org.cometd.bayeux.server.BayeuxServer, and composed of server transports (represented by the org.cometd.bayeux.server.ServerTransport class), of a message processing component, of a repository of server-side half-objects (represented by org.cometd.bayeux.server.ServerSession) and of a repository of channels (represented by org.cometd.bayeux.server.ServerChannel).

detailed component view

Message Flow

Client half-objects delegate to a client transport the delivery of messages; the client transport is responsible to establish the conduit with the server, and to send messages on that conduit.
On the server, it is the server transport that receives the messages.
The server transport delegates the processing of the messages to the message processing component that processes incoming messages, generates a message replies, and delegates back to the server transport the delivery of the replies to the client.

In the figure above, you can see that the conduit links the client transport and the server transport, and that the ServerSession may additionally participate in sending (only sending, and not directly receiving) messages to the client (again via the server transport).

TODO: explain how broadcast message flow works, but this requires to introduce the concept of queue in server session

Bayeux Protocol

A client communicates with the server by exchanging Bayeux messages.

The Bayeux protocol requires that the first message sent by a new client is a handshake message (a message sent on /meta/handshake channel).
On the server, if the processing of the incoming handshake message is successful, then BayeuxServer creates the server-side half object instance (a ServerSession) that represents, on the server, the client that initiated the handshake.
When the processing of the handshake is completed, the server sends back a reply to the client.

The client processes the handshake message reply, and if it is successful, starts - under the covers - a heartbeat mechanism with the server, by exchanging connect messages.
The details of this heartbeat mechanism depend on the client transport used, but can be seen as the client sending a connect message and expecting a reply after some time (when using HTTP transports, the heartbeat mechanism is also known as "long-polling").
The heartbeat mechanism allows a client to detect if the server is gone (the client will not receive the connect message reply from the server), and allows the server to detect if the client is gone (the server will not receive the connect message request from the client).

Connect messages continue to flow between client and server until either side decides to disconnect by sending a disconnect message (a message sent on the /meta/disconnect channel).

Migrating from CometD 1.x to CometD 2.x

Migrating from CometD 1.x to CometD 2.x

Required JDK Version

CometD 1.x CometD 2.x
JDK 1.5 JDK 1.6 (2.0.0)
JDK 1.5 (2.1.0)


Java Packages and Classes Migration

In general the API classes and interfaces moved from org.cometd.* to org.cometd.bayeux.*.
Server-side implementation classes remained in org.cometd.server.*, and client-side implementation classes remained in org.cometd.client.*.

CometD 1.x CometD 2.x
org.cometd.* org.cometd.bayeux.*
org.cometd.Bayeux org.cometd.bayeux.server.BayeuxServer
org.cometd.Client org.cometd.bayeux.server.ServerSession
org.cometd.Channel org.cometd.bayeux.server.ServerChannel
org.cometd.server.BayeuxService org.cometd.server.AbstractService


Maven Artifacts Migration

CometD 1.x CometD 2.x
org.cometd.java:cometd-api org.cometd.java:bayeux-api

CometD Servlet Migration

CometD 1.x CometD 2.x
org.cometd.server.continuation.ContinuationCometdServlet org.cometd.server.CometdServlet

CometD Servlet Parameter Migration

CometD 1.x CometD 2.x
multiFrameInterval long-polling.multiSessionInterval
requestAvailable N/A (always true)

BayeuxService Migration

CometD 1.x CometD 2.x
org.cometd.server.BayeuxService.subscribe() org.cometd.server.AbstractService.addService()

BayeuxService Method Migration

CometD 1.x CometD 2.x
public void method(Client c, Message m) public void method(ServerSession c, ServerMessage m)

Channel Retrieval Migration

CometD 1.x CometD 2.x
Channel c = bayeux.getChannel("/foo", true) bayeuxServer.createIfAbsent("/foo");
Channel c = bayeuxServer.getChannel("/foo");

Constants Migration

CometD 1.x CometD 2.x
org.cometd.Bayeux.ATTRIBUTE org.cometd.bayeux.server.BayeuxServer.ATTRIBUTE
org.cometd.Bayeux.META_HANDSHAKE org.cometd.bayeux.Channel.META_HANDSHAKE
org.cometd.Bayeux.META_CONNECT org.cometd.bayeux.Channel.META_CONNECT
org.cometd.Bayeux.META_SUBSCRIBE org.cometd.bayeux.Channel.META_SUBSCRIBE
org.cometd.Bayeux.META_UNSUBSCRIBE org.cometd.bayeux.Channel.META_UNSUBSCRIBE
org.cometd.Bayeux.META_DISCONNECT org.cometd.bayeux.Channel.META_DISCONNECT

CometD 2 JavaScript

CometD 2 JavaScript Library

The CometD 2 JavaScript library is a portable JavaScript implementation with bindings for the major JavaScript toolkits, currently Dojo and jQuery.

What this means is that the CometD Bayeux JavaScript implementation is written in pure JavaScript with no dependencies on the toolkits, and that the toolkit bindings add the syntactic sugar that makes the Bayeux APIs feel like they are native to the toolkit.
For example, it is possible to refer to the standard cometd object using the following notation:

// Dojo style
var cometd = dojox.cometd;

// jQuery style
var cometd = $.cometd;

If you followed the Primer, you may have noticed that the skeleton project requires to reference both the portable implementation, under org/cometd.js, and one binding - for example Dojo's - under dojox/cometd.js. For jQuery, the binding is under jquery/jquery.cometd.js.

The usage of the Bayeux APIs from the JavaScript toolkits is almost identical, and in the following we will not refer to a particular toolkit.
Small differences only surface when passing callback functions to the Bayeux API, where Dojo users may like to use dojo.hitch(), while jQuery users may like an anonymous function approach.

The following sections will go in detail about the JavaScript Bayeux APIs and their implementation secrets.

CometD 2 JavaScript Configuration

JavaScript CometD 2 API: Configuration and Initialization

After you have setup your skeleton project following the Primer, you may want to fully understand how to customize and configure the parameters that govern the behavior of the Cometd implementation.

The whole API is available through a single object prototype named org.cometd.Cometd.
The Dojo toolkit has one instance of this object available under the name dojox.cometd, while for jQuery it is available under the name $.cometd.

This default cometd object has been instantiated and configured with the default values and it has not started any Bayeux communication yet.
Before it can start any Bayeux communication it needs a mandatory parameter: the URL of the Bayeux server.

There are 2 ways of passing this parameter:

// First style: URL string
cometd.configure('http://localhost:8080/cometd');

// Second style: configuration object
cometd.configure({
    url: 'http://localhost:8080/cometd'
});

The first way is a shorthand for the second way.
However, the second way allows to pass other configuration parameters, currently:

Parameter Name Required Default Value Parameter Description
url yes The URL of the Bayeux server this client will connect to
logLevel no info The log level. Possible values are: "warn", "info", "debug". Output to window.console if available
maxConnections no 2 The max number of connections used to connect to the Bayeux server.
Only change this value if you know exactly what is the client's connection limit and what "request queued behind long poll" means
backoffIncrement no 1000 The number of milliseconds of which the backoff time is incremented every time a connection with the Bayeux server fails.
A reconnection will be attempted after the backoff time elapses
maxBackoff no 60000 The max number of milliseconds of the backoff time after which the backoff time is not incremented anymore
reverseIncomingExtensions no true Controls whether the incoming extensions will be called in reverse order with respect to the registration order
maxNetworkDelay no 10000 The max number of milliseconds to wait before considering a request to the Bayeux server failed.
requestHeaders no {} An object containing the request headers to be sent for every bayeux request (for example: {"My-Custom-Header":"MyValue"})
appendMessageTypeToURL no true Whether or not the Bayeux message type (handshake, connect, disconnect) is appended to URL of the Bayeux server (see above).
autoBatch no false Whether multiple publishes that gets queued up will be sent as a batch on the first occasion, without requiring explicit batching.

 
After you have configured the cometd object, it has not started the Bayeux communication yet. To start the Bayeux communication, you need to call handshake(), see the next section.

Previous users of the JavaScript CometD implementation were used to call a method called init(). This method still exists, and it is a shorthand for calling configure() followed by handshake().
Follow the advices in the next section as they apply as well to init().

CometD 2 JavaScript Handshake

JavaScript CometD 2 API: Handshake

The call to handshake() (or to init()) is the one that initiates the Bayeux communication with the Bayeux server.

The Bayeux handshake performs 2 tasks:

  • the client and the server negotiate the type of transport to use
  • once the transport is negotiated successfully, the server informs the client with the detailed timings of the requests

As with several methods of the JavaScript CometD API, it is an asynchronous method: it returns immediately, well before the Bayeux handshake steps have completed.

Note
Calling handshake() does not mean that you have completed the handshake with the server when handshake() returns.

The handshake may fail for several reasons:

  • you mistyped the server URL
  • the transport could not be negotiated successfully
  • the server denied the handshake (for example, the authentication credentials were wrong)
  • the server crashed
  • there was a network failure

Therefore it is not a good idea to write this code:

// Configure and handshake
cometd.init('http://localhost:8080/cometd');

// Publish to a channel
cometd.publish('/foo', { foo: 'bar' });

It is not a good idea, because there is no guarantee that the call to publish() (which we cover in a later section) can actually succeed in contacting the Bayeux server.
Since the API is asynchronous, you have no way of knowing synchronously (i.e. by having handshake() return an error code or by throwing an exception) that the handshake failed.
Even if the handshake succeeds, you may still be "disconnected" from the Bayeux server, for example because the server crashed just after the successful handshake.

Fortunately there is a way to be notified about the details of the Bayeux protocol message exchange: by adding listeners to special channels (called meta channels).
This is explained in the section about subscriptions.

CometD 2 JavaScript Subscription

JavaScript CometD 2 API: Subscribing and Unsubscribing

Channels

The Bayeux specification defines the concept of a channel: it is like a messaging topic where interested parties can subscribe to receive information published onto the channel.
There are 3 types of channels:

  • meta channels
  • service channels
  • normal channels

A channel looks like a directory path such as /meta/connect (a meta channel; all meta channels starts with the prefix /meta/), or /service/chat (a service channel; all service channels starts with the prefix /service/) or /foo/bar (a normal channel).

Meta Channels

Meta channels are created by the Bayeux protocol itself.
It is not possible to subscribe to meta channels: the server will reply with an error message. However, it is possible to listen to meta channels (see below the difference between subscribing and listening).
It makes no sense to publish messages to meta channels: only the Bayeux protocol implementation creates and sends messages on meta channels.
Meta channels are useful on the client to listen for error messages like handshake errors (for example because the client did not provide the correct credentials) or network errors (for example to know when the connection with the server has broken or when it has been re-established).

Service Channels

Service channels are used in the case of request/response style of communication between client and server (as opposed to the publish/subscribe style of communication or normal channels).
While subscribing to service channels yields no errors, this is a no-operation for the server: the server ignores the subscription request.
It is possible to publish to service channels, with the semantic of a communication between a specific client (the one that's publishing the message on the service channel) and the server.
Service channels are useful to implement, for example, private chat messages: in a chat with userA, userB and userC, userA can publish a private message to userC (without userB knowing about) using service channels.

Normal Channels

Normal channels have the semantic of a messaging topic and are used in the case of publish/subscribe style of communication.
Usually, it is possible to subscribe to normal channels and to publish to normal channels; this can only be forbidden using a security policy on the Bayeux server or by using authorizers.
Normal channels are useful to implement broadcasting of messages to all subscribed clients, for example in case of a stock price change.

Subscribers versus Listeners

The JavaScript CometD API has 2 APIs to work with channel subscriptions:

  • addListener() and the correspondent removeListener()
  • subscribe() and the correspondent unsubscribe()

The addListener() method:

  • must be used to listen to meta channel messages
  • may be used to listen to service channel messages (you may also use subscribe(), but is less recommended)
  • should not be used to listen normal channel messages (use subscribe() instead)
  • does not involve any communication with the Bayeux server, and as such can be called before calling handshake()
  • is synchronous: when it returns, you are guaranteed that the listener has been added

The subscribe() method:

  • must not be used to listen to meta channels messages (otherwise the server will return an error)
  • may be used to listen to service channel messages (you may also use addListener(), which is preferred)
  • should be used to listen to normal channel messages
  • involves a communication with the Bayeux server and as such cannot be called before calling handshake()
  • is asynchronous: it returns immediately, well before the Bayeux server has received the subscription request

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Both addListener() and subscribe() return a subscription object that must be passed to, respectively, removeListener() and unsubscribe():

// Some initialization code
var subscription1 = cometd.addListener('/meta/connect', function() { ... });
var subscription2 = cometd.subscribe('/foo/bar/', function() { ... });

// Some de-initialization code
cometd.unsubscribe(subscription2);
cometd.removeListener(subscription1);

A common pattern of utilization is to handle the subscription code in an idempotent method, like this:

var _subscription;

// The idempotent method
function _refresh()
{
    _appUnsubscribe();
    _appSubscribe();
}

function _appUnsubscribe()
{
    if (_subscription) 
        cometd.unsubscribe(_subscription);
    _subscription = null;
}

function _appSubscribe()
{
    _subscription = cometd.subscribe('/foo/bar', function() { ... });
}

The same of course applies also for addListener()/removeListener().

The point is that you have to be careful in your application: you have to remove your subscriptions, in order to avoid to leak functions, or to execute functions more than once (since you could erroneously bind the same callback twice).
Refer also to the Primer for a discussion about using idempotent methods.

How do subscribe() and unsubscribe() behave in case the Bayeux server is not reachable (due to network failures or because the server crashed) ?
In subscribe() the local listener is first added to the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will not know that it has to send messages to this client and therefore on the client the local listener (although present) will never be invoked.
In unsubscribe(), the local listener is first removed from the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will still send the message to the client but there will be no local listener to dispatch to.

Listeners/Subscribers Exception Handling

If a listener or subscriber function throws an exception (for example, calls a method on an undefined object, etc.), then the error message is logged (at level "debug").
However, there is a way to intercept these errors by defining the global listener exception handler, that is invoked every time a listener or subscriber throws an exception:

cometd.onListenerException = function(exception, subscriptionHandle, isListener, message)
{
    // Uh-oh, something went wrong, disable this listener/subscriber
    // Object "this" points to the CometD object
    if (isListener)
        this.removeListener(subscriptionHandle);
    else
        this.unsubscribe(subscriptionHandle);
}

It is be possible to send messages to the server from the listener exception handler.
If the listener exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for extensions, see here.

Wildcard Subscriptions

It is possible to subscribe to several channels at once using wildcards, like this:

cometd.subscribe("/chatrooms/*", function(message) { ... });

A single asterisk has the meaning of matching a single channel segment, so in the example above it will match channels /chatrooms/12 and /chatrooms/15, but not /chatrooms/12/upload.
To match multiple channel segments, use the double asterisk:

cometd.subscribe("/events/**", function(message) { ... });

With the double asterisk, the channels /events/stock/FOO and /events/forex/EUR will match, as well as /events/feed and /events/feed/2009/08/03.

The wildcard mechanism works also for listeners, so it is possible to listen to all meta channels like this:

cometd.addListener("/meta/*", function(message) { ... });

By default, subscriptions to the global wildcards /* and /** result in an error, but this behavior can be changed by specifying a custom security policy on the Bayeux server.

The wildcards can only be specified as last segment of the channel, so these are invalid subscriptions: /**/foo or /foo/*/bar.

Meta Channel List

These are the meta channels available in the JavaScript CometD implementation:

  • /meta/handshake
  • /meta/connect
  • /meta/disconnect
  • /meta/subscribe
  • /meta/unsubscribe
  • /meta/publish
  • /meta/unsuccessful

Each meta channel is notified when the correspondent Bayeux message is handled by the JavaScript Cometd implementation.
The /meta/unsuccessful channel is notified in case of any failure.

By far the most interesting meta channel to subscribe to is /meta/connect, because it gives the status of the current connection with the Bayeux server. In combination with /meta/disconnect, it can be used, for example, to display a green "connected" icon or a red "disconnected" icon on the page, depending on the connection status with the Bayeux server.

This is a common pattern using the /meta/connect and /meta/disconnect channels:

var _connected = false;

cometd.addListener('/meta/connect', function(message)
{
    // if (cometd.getStatus() === 'disconnecting' || cometd.getStatus() === 'disconnected')
    if (cometd.isDisconnected()) // Available since 1.1.2
    {
        return;
    }
    var wasConnected = _connected;
    _connected = message.successful;
    if (!wasConnected && _connected)
    {
        // Reconnected
    }
    else if (wasConnected && !_connected)
    {
        // Disconnected
    }
});

cometd.addListener('/meta/disconnect', function(message)
{
    if (message.successful)
    {
        _connected = false;
    }
}

One small caveat with the /meta/connect channel is that /meta/connect is also used for polling the server.
Therefore, if a disconnect is issued during an active poll, the active poll is returned by the server and this triggers the /meta/connect listener.
The initial check on the status verifies that is not the case before executing the connection logic.

Another interesting usage of meta channels is when there is an authentication step during the handshake.
In this case the registration to the /meta/handshake channel can give details about, for example, authentication failures.

CometD 2 JavaScript Publish

JavaScript CometD 2 API: Publishing

The publish() method allow you to publish data onto a certain channel:

cometd.publish('/mychannel', { mydata: { foo: 'bar' } });

You cannot (and it makes no sense) to publish to a meta channel, and you can publish to a channel even if you are not subscribed to that channel.
However, you have to handshake before being able to publish.

As with other JavaScript CometD API, publish() involves a communication with the server and it is asynchronous: it returns immediately, well before the Bayeux server has received the message.

Note
Calling publish() does not mean that you have published the message when publish() returns.

If you have to publish several messages to different channels, you may want to use message batching.

CometD 2 JavaScript Disconnection

JavaScript CometD 2 API: Disconnecting

The JavaScript CometD implementation performs automatic reconnect in case of network or Bayeux server failures.
The reconnect parameters are described in the configuration section.

Short Network Failures

In case of temporary network failures, the client is notified through the /meta/connect channel (see this section about meta channels) with messages that have the successful field set to false (see also the archetypes in the primer as an example).
However, the Bayeux server may be able to keep the client's state, and when the network resumes the Bayeux server may behave as if nothing happened.
The client in this case just re-establishes the long poll, but any message published by the client during the network failure is not automatically re-sent (though it is possible to be notified, through the /meta/publish channel, of the failed publishes).

Long Network Failures or Server Failures

If the network failure is long enough, the Bayeux server times out the lost client, and deletes the state associated with it. The same happens when the Bayeux server crashes (except of course that the state of all clients is lost).
In this case, the reconnection mechanism on the client performs the following steps:

  • a long poll is re-attempted, but the server rejects it with a 402::Unknown client error message
  • a handshake is attempted, and the server normally accepts it and allocates a new client
  • upon the successful re-handshake, a long poll is re-established

If you register with meta channels, be aware of these steps, since a reconnection may involve more than one message exchange with the server.

Disconnecting

Calling the JavaScript CometD API disconnect() result in a message being sent to the Bayeux server, so that it can cleanup any state associated with that client.
As with all methods that involve a communication with the Bayeux server, it is an asynchronous method: it returns immediately, well before the Bayeux server has received the disconnect request.
If the server cannot be reached (because it is down or because of network failures), the JavaScript CometD implementation will stop any reconnection attempt and cleanup any local state.
It is normally safe to ignore if the disconnect() call has been successful or not: the client is in any case disconnected, its local state cleaned up, and if the server has not been reached it will eventually time out the client and cleanup any server-side state for that client.

Tip
If you are debugging your application with Firebug, and you shutdown the server, you'll see in the Firebug console the attempts to reconnect.
To stop those attempts, simply type in the Firebug command line: dojox.cometd.disconnect() (for Dojo) or $.cometd.disconnect() (for jQuery).

CometD 2 JavaScript Message Batching

JavaScript CometD 2 API: Message Batching

It is often needed by an application to send several messages to possibly different channels.

One, naive, way of doing it is the following:

cometd.handshake();

// Warning: non-optimal code
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });

You may think that the 3 publishes will leave the client one after the other, but that's actually not the case.
Remember that publish() is asynchronous (so it returns immediately), so the 3 publish() calls in sequence may return well before a single byte hits the network.

What happens is that the first publish() will be executed immediately, and the other 2 will be put in a queue, waiting for the first publish() to complete.
A publish() is complete when the server received it, the server sent back the meta response, and the client received the meta response for that publish.
When the first publish is completed, the second publish is executed and waited to complete. After that, finally the third publish() is executed.

If the configuration parameter called autoBatch is set to true, the implementation automatically batches messages that have been queued up.
So, in the example above, the first publish() will be executed immediately, and when it completes, the implementation will batch the second and third publish() into one request to the server.
The autoBatch feature may be interesting for those systems where events received asynchronously and unpredictably, either at a fast rate or in bursts, end up in generating a publish() to the server: in such cases, using the batching API is not effective (as each event would generate only one publish()).
A bursts of events on the client will generate a bursts of publish() to the server, but this mechanism batches them automatically, making the communication more efficient.

This queueing mechanism is needed to avoid to queue a publish() behind a long poll. If not for this mechanism, the browser would receive 3 publish requests but only has 2 connections available, and one is already occupied by the long poll request. So the browser may decide to round robin the publish requests, so that the first publish goes on the second connection (remember that the first connection is already busy with the long poll request), which is free and it is actually sent over the network, schedule the second publish to the first connection (after the long poll returns), and schedule the third publish again to the second connection, after the first publish returns.
The result is that if you have a long poll timeout of 5 minutes, the second publish request may arrive to the server 5 minutes later than the first and the third publish request.

You can optimize the 3 publish using batching, which is a way to group messages together so that a single Bayeux message actually carries the 3 publish messages.

cometd.handshake();

cometd.batch(function()
{
    cometd.publish('/channel1', { product: 'foo' });
    cometd.publish('/channel2', { notificationType: 'all' });
    cometd.publish('/channel3', { update: false });
});

// Alternatively, but not recommended
cometd.startBatch()
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });
cometd.endBatch()

Note how the 3 publish() calls are now within a function passed to batch().
Alternatively, but less recommended, you can surround the 3 publish() calls between startBatch() and endBatch().

Warning
Remember to call endBatch() after having called startBatch().
If you don't, for example because an exception is thrown in the middle of the batch, your messages will continue to queue up, and your application will not work as expected.

Function batch() already does the correct batching for you (also in case of errors), so it's the recommended way to do message batching.

When a batch is started, subsequent API calls are not sent to the server, but instead queued up, until the batch is ended.
The end of the batch packs up all the queued messages into one single Bayeux message and send it over the network to the Bayeux server.

Message batching allow an efficient utilization of the network, as instead of making 3 requests/responses cycles, batching makes only one request/response cycle.

Batches can be made up of different API calls:

var _subscription;

cometd.batch(function()
{
    cometd.unsubscribe(_subscription);
    _subscription = cometd.subscribe('/foo', function(message) { ... });
    cometd.publish('/bar', { ... });
});

Batched messages will be processed by the Bayeux server in the order they are sent.

If you still want to risk and use the startBatch() and endBatch() calls, remember that they must be done from the same context of execution; message batching has not been designed to span multiple user interactions.
So, for example, it would be wrong to start a batch in, say, functionA (triggered by user interaction), and ending the batch in functionB (also triggered by user interaction and not called by functionA).
Similarly, it would be wrong to start a batch in functionA and then schedule (using setTimeout()) the execution of functionB to end the batch.

CometD 2 JavaScript Transports

JavaScript CometD 2 Transports

The Bayeux specification defines two mandatory transports:

  • long-polling
  • callback-polling

The JavaScript CometD implementation implements these two transports and supports also the websocket transport.

Note
The websocket protocol is still an experimental protocol being changed by the IETF working group that is standardizing it.
Browsers that have deployed an initial support for the websocket protocol could be buggy, so we do not recommend to use the websocket protocol yet, though the CometD project supports it.

For recent browsers (such as Firefox 3.5+) it is possible to use the long-polling transport also for cross-domain Bayeux communication, see below the cross-domain mode.

The long-polling Transport

The long-polling transport is the default transport if the browser and the server do not support websocket.
This transport is used when the communication with the Bayeux server happens on the same domain, and in the cross-domain mode (see below).
The data is sent to the server by means of a POST request with Content-Type text/json via a plain XMLHttpRequest call.

The callback-polling Transport

The callback-polling transport is the transport that is used when the communication with the Bayeux server happens on a different domain (when the cross-domain mode is not supported, see below for the cross-domain mode section).

It is well known that XMLHttpRequest calls have restrictions when the invocation is directed to a domain different from the one the script has been downloaded (but see below the cross-domain mode for an alternative solution).
To overcome XMLHttpRequest restrictions, this transport uses the JSONP script injection: instead of using XMLHttpRequest it injects a <script> element whose src attribute points to the Bayeux server.
The browser will notice the script element injection and performs a GET request to the specified source URL.
The Bayeux server is aware that this is a JSONP request and replies with a JavaScript function that is then executed by the browser (and that calls back into the JavaScript Cometd implementation).

There are three main drawbacks in using this transport:

  • The transport is chattier.
    This is due to the fact that the browser executes the injected scripts sequentially, and until a script has been completely "downloaded", it cannot be executed.
    For example, imagine a communication that involves a script injection for the long poll, and a script injection for a message publish. The browser injects the long poll script, a request is made to the Bayeux server, but the Bayeux server holds the request waiting for server-side events (so the "script" is not "downloaded"). Then the browser injects the publish script, the request is made to the Bayeux server, which replies (so the "script is "downloaded"). However, the browser does not execute the second script, because it has not executed the first yet (since its "download" is not finished). In these conditions, the publish would be executed only after the long poll returns. To avoid this situation the Bayeux server, in case of callback-polling transport, resumes the client's long poll for every message that arrives from that client, and that's why the transport is chattier: the long poll returns more often.
  • The message size is limited.
    This is necessary to support IE7, that has a 2083 character limit for GET requests.
  • The reaction to failures is slower.
    This is due to the fact that if the script injection points to a URL that returns an error (for example the Bayeux server is down), this is silently ignored by the browser.

The websocket transport

The websocket transport is available if the browser and the server supports websocket (Jetty 7 supports the websocket protocol).
This transport is still experimental, but it is being designed to be the bidirectional communication protocol for the web, so it is a natural fit in the CometD project.
However, the websocket protocol is still in alpha state, and browsers support it to various degree of reliability (often times the support is buggy), so currently it is not yet recommended to use it in production (see below on how to disable transports).
The websocket transport is disabled by default since CometD 2.1.0, but can be enabled easily, following this section.

Unregistering Transports

CometD JavaScript transports are added in the JavaScript toolkit bindings for the CometD JavaScript library.

The CometD JavaScript API allow to unregister transports, and this can be useful to force the use of only one transport (for example for testing purposes), or to disable certain transports that may be unreliable.

For example, it is possible to unregister the websocket transport by unregistering it with the following code:

var cometd = dojox.cometd; // Dojo style
var cometd = $.cometd; // jQuery style

cometd.unregisterTransport('websocket');

The cross-domain Mode

Firefox 3.5 introduced the capability for XMLHttpRequest calls to be performed towards a different domain (see here).

This is supported also in the JavaScript CometD implementation, with no configuration necessary on the client (if the browser supports XMLHttpRequest cross-domain calls, they will be used) and with a bit of configuration for the server. Refer to this document for the server configuration.

To use the cross-domain mode, you need:

  • a cross-domain compliant browser (for example Firefox 3.5)
  • a compliant server (for example Jetty configured with the CrossOriginFilter)

With this setup, even when the communication with the Bayeux server is cross-domain, the long-polling transport will be used, avoiding the drawbacks of the callback-polling transport.

CometD 2 Java

CometD Java Implementation

The CometD Java implementation is based on the popular Jetty Http Server and Servlet Container, for both the client and the server.

The CometD Java implementation, though based on Jetty, is portable on other Servlet 2.5 compliant servlet containers (because it uses the portable Jetty Continuation API).

The war file resulting from the development of your CometD web application can be deployed to other Servlet 2.5 compliant servlet containers, but it will scale less because it will be less integrated with the servlet container.
When deployed to a Servlet 3.0 compliant servlet container, the CometD implementation will make use of the asynchronous features offered by the servlet container and will be fully portable and scalable (the scalability will be as good as the servlet container's implementation).
See this FAQ entry when deploying to Servlet 3.0 compliant servlet containers.

The CometD Java Implementation offers a client library and a server library, documented in details in the following sections.

CometD 2 Java Concepts

CometD 2 Java Concepts

The CometD project implements the Comet technique using the Bayeux protocol to provide a scalable HTTP-based messaging system.

In general, messaging system are made of a client part and of a server part that communicate via a procotol. This is captured by a pattern called half object plus protocol.

half object plus protocol diagram

Sessions

A org.cometd.bayeux.client.ClientSession is the client-side half object that represent a communication session with a Bayeux server.

When client session half objects are created by a client, they are not initially associated with a correspondent org.cometd.bayeux.server.ServerSession half object.
Only when a client session handshakes with the server, its correspondent server session is created, and the link between the two half objects is created.

The concept of client session is straightforward for remote clients, but it exist on server-side as well.
The Bayeux server only knows about server session half objects, and the only way to create a server session half object is to create its correspondent client session first, and then make it handshake with the server.

For this reason, on server-side, there is the additional concept of a org.cometd.bayeux.server.LocalSession, which extends org.cometd.bayeux.client.ClientSession; it is a client session that happens to live on the server, and hence is local to the server.

For example, server-side services are associated with a local session. Upon creation of the server-side service, the local session handshakes and creates the correspondent server session half object, so that the Bayeux server can treat remote sessions and local session in the same way via org.cometd.bayeux.server.ServerSession.

In the CometD implementation, the two halves communicate by exchanging Bayeux messages.

Message

The Java API offers - on client-side - the org.cometd.bayeux.Message interface to interact with the content of a message in a read-only way.
Where user code is allowed to modify the content of a message, the inner sub-interface org.cometd.bayeux.Message.Mutable is passed as parameter to callback methods (for example in client-side extensions).

On server-side, the sub-interface org.cometd.bayeux.server.ServerMessage allows read-only interaction with messages, and the inner sub-interface org.cometd.bayeux.server.ServerMessage.Mutable is passed as parameter to callback methods where user code is allowed to modify the message (for example server-side extensions).

Messages are sent to channels.

Channel

A channel is a named topic to which messages are sent to and to which subscribers register if they are interested in receiving messages sent to that channel.
Channel names resembles directory paths and can be wildcarded, for example: /chat/room/1, /stocks/**, /cinema/trailers/*.
Bayeux channels are divided in three categories:

  • Meta Channels (whose name starts with /meta/ and are reserved to the Bayeux protocol)
  • Service Channels (whose name starts with /services/ and are used from client to server communication)
  • Normal Channels (whose name starts with any other string and are used to broadcast messages between clients)

On server-side, the sub-interface org.cometd.bayeux.server.ServerChannel allows interaction with the channel (for example by publishing messages or adding listeners).

On client-side, channels are scoped to the client session that provides them via org.cometd.bayeux.client.ClientSessionChannel.

CometD 2 JSON Library Pluggability

CometD 2 JSON Library Pluggability

Since version 2.4.0, CometD allows to customize the JSON library that is used to convert incoming JSON into Bayeux messages and generate JSON from Bayeux messages.

There are two implementations available, one based on Jetty's org.eclipse.jetty.util.ajax.JSON class, and the other based on the Jackson library.
The default implementation uses the Jetty library.

The two libraries are among the fastest around and it turns out that, for the CometD specific case, the Jetty library is faster than Jackson in parsing, but slower in generating.

The Jackson library offers a richer API to customize JSON generation and parsing based on annotations, so it may have an advantage if you want to use objects of your custom classes as data of Bayeux messages (instead of using java.util.Map objects). Refer to the Jackson documentation for further details.

Also the Jetty library allows to plug in custom serializers and deserializers, perhaps in a simpler but more invasive way (your custom classes must implement Jetty's library interfaces). Refer to the org.eclipse.jetty.util.ajax.JSON javadocs for further details.

CometD JSONContext API

The JSON library is used by the CometD Java client implementation (see here) to generate JSON from and to parse JSON to org.cometd.bayeux.Message instances.
The JSON library class that performs this generation/parsing on the client must implement org.cometd.common.JSONContext.Client.

Similarly, on the server, a org.cometd.common.JSONContext.Server implementation generates JSON from and parses JSON to org.cometd.bayeux.server.ServerMessage instances.

Client Configuration

On the client, the org.cometd.common.JSONContext.Client instance must be passed directly into the transport configuration; if omitted, the default Jetty library will be used.
For example:

HttpClient httpClient = ...;

Map clientOptions = new HashMap();

// Use the Jackson implementation
JSONContext.Client jsonContext = new JacksonJSONContextClient();
clientOptions.put(ClientTransport.JSON_CONTEXT, jsonContext);

ClientTransport transport = new LongPollingTransport(clientOptions, httpClient);

BayeuxClient client = new BayeuxClient(cometdURL, transport);

The org.cometd.common.JSONContext.Client instance may be shared by all client transports (since only one transport will be used at any time).

You can customize the Jackson implementation and add your own serializers/deserializer in the following way:

public class MyJacksonJSONContextClient extends org.cometd.common.JacksonJSONContextClient
{
    public MyJacksonJSONContextClient()
    {
        org.codehaus.jackson.map.ObjectMapper objectMapper = getObjectMapper();
        objectMapper.registerModule(new MyModule());
    }

    private class MyModule extends org.codehaus.jackson.map.module.SimpleModule
    {
        public MyModule()
        {
            // Add your custom serializers/deserializers here
            addSerializer(Foo.class, new FooSerializer());
        }
    }
}

Server Configuration

On the server, the full qualified name of a class implementing org.cometd.common.JSONContext.Server may be specified as init-parameter of the CometdServlet (see also here); if omitted, the default Jetty library will be used.
For example:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        ... other parameters
        <init-param>
            <param-name>jsonContext</param-name>
            <param-value>org.cometd.server.JacksonJSONContextServer</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

The class specified must be instantiable using the default parameterless constructor and implement org.cometd.common.JSONContext.Server, and it can be customized by adding serializers/deserializers as explained above.

Portability Considerations

It is possible to switch from one implementation of the JSON library to another - for example from the Jetty library to the Jackson library , provided that the application code is written carefully.

As of version 1.8.4, Jackson can only produce instances of java.util.List when deserializing JSON arrays.
The Jetty library, however, produces Object[] when deserializing JSON arrays.

Similarly, Jackson may produce lava.lang.Integer where the Jetty library produces java.lang.Long.

To write portable application code, use the following code patterns:

Message message = ...;
Map<String, Object> data = message.getDataAsMap();

// Expecting a JSON array

// WRONG
Object[] array = (Object[])data.get("array");

// CORRECT
Object field = data.get("array");
Object[] array = field instanceof List ? ((List)field).toArray() : (Object[])field;


// Expecting a long

// WRONG
long value = (Long)data.get("value");

// CORRECT
long value = ((Number)data.get("value")).longValue();

CometD 2 Java Server API

CometD 2 Java Server Implementation

In order to run the CometD server implementation, you need to deploy a Java web application to a servlet container.

The web application needs to configure the CometD servlet (see here for configuration details) to interpret the Bayeux protocol.

Most of the times it is also needed to write one or more user-defined services that can act upon receiving messages on Bayeux channels.

CometD 2 Java Server Configuration

CometD 2 Java Server Configuration

BayeuxServer and server transport parameters can be specified in web.xml as init parameters of the org.cometd.server.CometdServlet.
If the CometD servlet creates the BayeuxServer instance, the servlet init parameters will be passed to the BayeuxServer instance, which will in turn configure the server transports.

If you followed the primer, then Maven has configured the web.xml file for you, but here we will detail its configuration.
This is a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
        <init-param>
            <param-name>logLevel</param-name>
            <param-value>3</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

The org.cometd.server.CometdServlet must be defined and mapped in web.xml, or otherwise the server will not be able to interpret the Bayeux protocol.
It is normally mapped on /cometd/*, but you can change the mapping url-pattern at your wish.

BayeuxServer Configuration

Here is the list of configuration init parameters accepted by the BayeuxServer implementation:

Parameter Name Default Value Since Parameter Description
logLevel 0 2.0.0 The log level; 0 = off, 1 = config, 2 = info, 3 = debug
transports empty string 2.2.0 A comma separated list of ServerTransport implementation class names (that take a org.cometd.server.BayeuxServerImpl as only constructor parameter) to add to the default server transports
allowedTransports empty string 2.2.0 A comma separated list of ServerTransports names allowed. If not specified, the default server transports are allowed
jsonContext org.cometd.server.JettyJSONContextServer 2.4.0 The full qualified name of a class implementing org.cometd.common.JSONContext.Server. The class is loaded and instantiated using the default constructor.

 

Server Transport Configuration

In CometD 2 the server transports are pluggable and can be configured using parameters that may have a prefix that specifies the transport that the parameter refers to.
For example, the parameter timeout has no prefix, and hence it is valid for all transports; the parameter long-polling.jsonp.timeout overrides the timeout parameter for the callback polling transport only, while ws.timeout overrides it for the websocket transport (see org.cometd.bayeux.Transport javadocs for details).

Here is the list of configuration init parameters accepted by server transports:

Parameter Name Default Value Parameter Description
timeout 30000 The time, in milliseconds, that a server will wait for a message before responding to a long poll with an empty response
ws.timeout 15000 Like the timeout parameter, but for the websocket transport
interval 0 The time, in milliseconds, that specifies how long the client must wait between the end of one long poll requests and the start of the next
ws.interval 2500 Like the interval parameter but for the websocket transport
maxInterval 10000 The max period of time, in milliseconds, that the server will wait for a new long poll from a client before that client is considered invalid and is removed
ws.maxInterval 15000 Like the maxInterval parameter, but for the websocket transport
maxLazyTimeout 5000 The max period of time, in milliseconds, that the server will wait before delivering or publishing lazy messages
metaConnectDeliverOnly false Whether the transport should deliver the messages only via long poll (enables strict message ordering)
jsonDebug false Whether or not the full JSON input should be kept for debugging purposes
maxSessionsPerBrowser 1 The max number of sessions (tab/frames) allowed to long poll from the same browser; a negative value allows unlimited sessions (see also here)
allowMultiSessionsNoBrowser false Whether to allow multiple sessions (tab/frames) in case the browser cannot be detected (see also here)
multiSessionInterval 2000 The period of time, in milliseconds, that specifies the client normal polling period in case the server detects more sessions (tabs/frames) connected from the same browser than allowed by the maxSessionsPerBrowser parameter. A non-positive value means that additional sessions will be disconnected.
long-polling.json.metaConnectDeliverOnly false Whether or not delivery of messages should only happen via /meta/connect. Enabling this option allows for strict message ordering at the cost of a slightly more chattier protocol (because delivery via /meta/connect requires waking up the long poll).

 

Advanced Configuration

If you are using Jetty 7, you may want to configure also the CrossOriginFilter.
This filter implements the Cross-Origin Resource Sharing specification, and allows recent browsers that implements it (as of November 2009, Firefox 3.5.x, Chrome 3.x and Safari 4.x) to perform cross-domain JavaScript requests (see also the transport section).
Here is an example of web.xml configuration for the CrossOriginFilter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>cross-origin</filter-name>
        <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>cross-origin</filter-name>
        <url-pattern>/cometd/*</url-pattern>
    </filter-mapping>

</web-app>

Refer to this document for the filter configuration.

Servlet 3 configuration

Refer to this FAQ.

CometD 2 Java Server Authorization

CometD Java Server API: Authorization

The Bayeux object can be configured with a org.cometd.bayeux.server.SecurityPolicy object, which allows to control various steps of the Bayeux protocol such as handshake, subscription, publish, etc.
By default, the Bayeux object does not have a SecurityPolicy installed, which means that any operation is authorized.

The org.cometd.bayeux.server.SecurityPolicy has a default implementation in org.cometd.server.DefaultSecurityPolicy, that is useful as a base class in case of customization of the SecurityPolicy (see how authentication works for an example).

The org.cometd.bayeux.server.SecurityPolicy methods are:

boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message);

boolean canCreate(BayeuxServer server, ServerSession session, String channelId, ServerMessage message);

boolean canSubscribe(BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage messsage);

boolean canPublish(BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage messsage);

The methods are self-speaking and control, respectively, if an handshake, a channel creation, a subscription to a channel and a publish to a channel are to be authorized.

The default implementation org.cometd.server.DefaultSecurityPolicy:

  • allows any handshake
  • allows creation of channel only from clients that handshook and only if the channel is not a meta channel
  • allows subscription from clients that handshook, but not if the channel is a meta channel
  • allows publish from clients that handshook to any channel that is not a meta channel

To understand how to install your custom SecurityPolicy on the Bayeux object, see how it is done in the authentication howto.

Follow also the section on authorizers.

CometD 2 Java Server Authorizers

CometD 2 Java Server API: Authorizers

Introduction

From CometD 2.1.0, a new feature has been implemented to allow fine-grained control of authorization on channel operations: authorizers.

Authorizers are objects that implement org.cometd.bayeux.server.Authorizer, and are added to server-side channels at setup time (before any operation on that channel may happen).

Authorizers are particularly suited to control authorization on those channels that are created at runtime, or for those channels whose id is built at runtime by concatenating strings that are unknown at application startup time (see examples below).
Authorizers do not apply to meta channels, but only to service channels and to normal (broadcast) channels.

Authorizers may be added to wildcard channels (such as /chat/*), and will impact all channels matched by the wildcard channel onto the authorizer has been added.
An authorizer added to /** impacts all (non-meta) channels.

For a non wildcard channel such as /chat/room/10, the authorizer set is the union of all authorizers on that channel and of all authorizers on wilcard channels that match the channel (in this case authorizers on channels /chat/room/*, /chat/room/**, /chat/** and /**).

Authorization Algorithm

Authorizers control access to channels in collaboration with the org.cometd.bayeux.server.SecurityPolicy that is currently installed.

The org.cometd.bayeux.server.SecurityPolicy class exposes three methods that can be used to control access to channels:

public boolean canCreate   (BayeuxServer server, ServerSession session, String channelId,      ServerMessage message);
public boolean canSubscribe(BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage message);
public boolean canPublish  (BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage message);

which control, respectively, the operation to create a channel, the operation to subscribe to a channel, and the operation to publish to a channel.

The complete algorithm for the authorization is the following:

  1. If there is a security policy, and the security policy denies the request, then the request is denied.
  2. Otherwise, if the authorizer set is empty, the request is granted.
  3. Otherwise, if at least one authorizer explicitly denies the operation, the request is denied and remaining authorizers are no consulted.
  4. Otherwise, if at least one authorizer explicitly grants the operation, the request is granted.
  5. Otherwise, as no authorizer has explicitly granted the operation, the request is denied.

The order in which the authorizers are called is not important.

Authorization is performed by implementing the method:

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);

There are three possible results that could be returned:

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.grant();
}

Result.grant() explicitly grants the permission to perform the operation passed in as parameter on the channel passed in as parameter.
There must be at least one authorizer that grants the operation, otherwise the operation is denied.
The fact that one (or multiple) authorizers grant an operation does not imply that the operation is granted at the end of the authorization algorithm: it could be denied by another authorizer in the authorizer set for that channel.

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.ignore();
}

Result.ignore() neither grants nor denies the permission to perform the operation passed in as parameter on the channel passed in as parameter.
Ignoring the authorization request is the usual way to not granting authorization if the conditions for which the operation must be granted do not apply.

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.deny("reason to deny");
}

Result.deny() explicitly denies the permission to perform the operation passed in as parameter on the channel passed in as parameter.
Denying the authorization request immediately result in the authorization being denied without even consulting other authorizers in the authorizer set for that channel.

Typically, denying authorizers are used for cross-cutting concerns: where you can have a sophisticated logic in authorizers to grant access to users for specific paid TV channels based on the user's contract (imagine that bronze, silver and gold contracts give access to different TV channels), you have a single authorizer that denies the operation if the user's balance is insufficient, no matter the contract or the TV channel being requested.

Example

The following example assume that the security policy does not interfere with the authorizers, and that the code is executed when the channel does not exist yet (either at application startup or in places where the application logic ensures that the channel has not been created yet).

Let's imagine an application that allows to watch and play games.

Typically, an ignoring authorizer is added on a root channel:

BayeuxServer bayeuxServer = ...;
bayeuxServer.createIfAbsent("/game/**");
ServerChannel gameStarStar = bayeuxServer.getChannel("/game/**");
gameStarStar.addAuthorizer(GrantAuthorizer.GRANT_NONE);

This ensures that the authorizer set is not empty, and that by default (if no other authorizer grants or deny) the authorization is ignored and hence denied.

Only captains can start a new game, and to do so they create a new channel for that game, for example /game/123 (where 123 is the gameId):

gameStarStar.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        boolean isGameChannel = !channel.isWild() && new ChannelId("/game").isParentOf(channel);
        if (operation == Operation.CREATE && isGameChannel)
        {
            if (isCaptain(session))
                return Result.grant();
            return Result.deny("Only captains can create game channels");
        } 
        return Result.ignore();
    }
});

Everyone can watch the game:

gameStarStar.addAuthorizer(GrantAuthorizer.GRANT_SUBSCRIBE);

Only players can play:

ServerChannel gameChannel = bayeuxServer.getChannel("/game/" + gameId);
gameChannel.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        if (operation == Operation.PUBLISH)
        {
            if(isPlayer(session,channel))
                return Result.grant();
            return Result.deny("Only players can publish to "+channel);
        } 
        return Result.ignore();
    }
});

The authorizers are the following:

/game/**  --> one authorizer that ignores everything
          --> one authorizer that grants captains to create games
          --> one authorizer that grants everyone to watch games
/game/123 --> one authorizer that grants players to play

Imagine that later you want to forbid to criminal supporters to watch games, so you can add another authorizer (instead of modifying the one that grants everyone to watch games):

gameStarStar.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        boolean isCriminalSupporter = isCriminalSupporter(session);
        if (operation == Operation.SUBSCRIBE && isCriminalSupporter)
            return Result.deny("criminal_supporter"); 
        return Result.ignore();
    }
});

The authorizers are now the following:

/game/**  --> one authorizer that ignores everything
          --> one authorizer that grants captains to create games
          --> one authorizer that grants everyone to watch games
          --> one authorizer that denies criminal supporters to watch games
/game/123 --> one authorizer that grants players to play

Note how authorizers on /game/** never grant Operation.PUBLISH, which is only granted by authorizers on specific game channels.
Also, the specific game channel does not need to grant Operation.SUBSCRIBE, because its authorizer ignores the subscribe operation that is therefore handled by authorizers on the /game/** channel.

Atomic Authorizer Registration

If channels are created while load is being offered to the server, it is important to create the channel and register the authorizers in an atomic operation, so that no messages are processed before the authorizers are registered. This can be achieved by using initialiazers:

bayeuxServer.createIfAbsent("/game/"+game_id, new ConfigurableServerChannel.Initializer()
{
    public void configureChannel(ConfigurableServerChannel channel)
    {
        channel.addAuthorizer(new PlayerAuthorizer());
    }
});

CometD 2 & Multiple Sessions

CometD 2 & Multiple Sessions

The HTTP protocol recommends a connection limit of two connections per domain.
While modern browsers are configured by default with more than two connections per domain, it is not safe to make such assumption; thus any iframes, tabs or windows on the same browser connecting to the same host need to share two connections.

If two iframes/tabs/windows initiate a Bayeux communication, both will start a long poll connect request and both connections will be consumed, making impossible to send another Bayeux request (for example a publish) until one of the two long polls returns.

The CometD Server implements the multiple-clients advice specified in the Bayeux specification.
The server uses BAYEUX_BROWSER cookie to detect multiple CometD clients from the same browser.

If multiple clients are detected, then only one long poll connection is allowed and subsequent long poll requests will not wait for messages before returning. They will return immediately with the multiple-clients field of the advice object set to true. The advice will also contain an interval field set to the value of the multiSessionInterval servlet init parameter (see here). This instructs the client not to send another poll until that interval has passed.

The effect of this advice is that additional client connections will normal poll the server with a period of multiSessionInterval. This avoids consume both HTTP connections at the cost of some latency for the additional iframes/tabs/windows.

It is recommended that the client application monitor the /meta/connect channel for multiple-clients field in the advice. If detected, the application may ask the user to close the additional tabs, or it could automatically close them or take some other action.

Non-browser clients (or browsers with cookies disabled) must handle the BAYEUX_BROWSER cookie with the same semantic of browsers, or configure the server to allow multiple sessions even without BAYEUX_BROWSER information via the allowMultiSessionsNoBrowser servlet init parameter (see here).

CometD 2 Java Server Services

CometD 2 Java Server API: Services

A CometD service is a Java class that allow a developer to specify the code to run when Bayeux messages are received on Bayeux channels.
When a message arrives on a channel for which the service instance has subscribed to, a callback method is invoked to execute user-specific code.

CometD 2 services can be of two kinds: inherited or annotated.

CometD 2 services can also be integrated with the Spring Framework as explained here.

CometD 2 Java Server Inherited Services

CometD Java Server API: Inherited Services

A CometD inherited service is a Java class that extends the CometD class org.cometd.server.AbstractService, that specifies the Bayeux channels the service is interested to, and that adheres to the contract required by the AbstractService class:

public class EchoService extends AbstractService                                  (1)
{
    public EchoService(BayeuxServer bayeuxServer)                                 (2)
    {
        super(bayeuxServer, "echo");                                              (3)
        addService("/echo", "processEcho");                                       (4)
    }

    public void processEcho(ServerSession remote, Map<String, Object> data) (5)
    {
        remote.deliver(getServerSession(), "/echo", data, null);                  (6)
    }
}

This is a simple echo service that returns the message sent by the remote client on channel "/echo" to the remote client itself.

Note the following:

  • In (1) we extend from org.cometd.server.AbstractService
  • In (2) we create a constructor that takes a org.cometd.bayeux.server.BayeuxServer object
  • In (3) we call the superclass constructor, passing the BayeuxServer object and an arbitrary name of the service, in this case "echo"
  • In (4) we subscribe to channel "/echo", and we specify the name of a method that must be called when a message arrives to that channel
  • In (5) we define a method with the same name specified in (4), and with an appropriate signature (see below)
  • In (6) we use the org.cometd.bayeux.server.ServerSession API to echo the message back to that particular client

The contract that the BayeuxService class requires for callback methods is that the methods must have one of the following signatures:

// Obtains the remote session object and the message object
public void processEcho(ServerSession remote, Message message)

// Obtains the remote session object and the message's data object
// (additional message information, such as the channel or the id is lost)
public void processEcho(ServerSession remote, Map<String, Object> data)

// Obtains the remote session object, the channel name, the message object and the message id
public void processEcho(ServerSession remote, String channelName, Message message, String messageId)

// Obtains the remote session object, the channel name, the message's data object and the message id
public void processEcho(ServerSession remote, String channelName, Map<String, Object> data, String messageId)

Note that the channel name specified in the subscribe() method can be a wildcard, for example:

public class BaseballTeamService extends AbstractService
{
    public BaseballTeamService(BayeuxServer bayeux)
    {
        super(bayeux, "baseballTeam");
        addService("/baseball/team/*", "processBaseballTeam");
    }

    public void processBaseballTeam(ServerSession remote, String channelName, Map<String, Object> data, String messageId)
    {
        // Upon receiving a message on channel /baseball/team/*, forward to channel /events/baseball/team/*
        getBayeux().getChannel("/events" + channelName).publish(getServerSession(), data, null);
    }
}

Note also how in the first example we used ServerSession.deliver() to send a message to a particular remote client, while in the second we used ServerChannel.publish() to send a message to anyone who subscribed to channel "/events/baseball/team/*".

Once you have written your Bayeux services it is time to setup them in your web application, plain style or Spring style.

CometD 2 Java Annotated Services

CometD 2 Annotated Client-Side and Server-Side Services

Support for annotated services is available since CometD 2.1.0.
Classes annotated with @Service qualify as annotated services, both on client-side and on server-side.

Server-Side Annotated Services

Server-side services were usually written by extending the org.cometd.server.AbstractServer class, and instances of these classes have normally singleton semantic and are created and configured at web application startup.

The org.cometd.server.AbstractServer class provides (via inheritance) a few facilities that are useful when implementing the service, mainly access to the ServerSession associated with the service instance and registration of methods as a callbacks to receive messages from channels.

Services may depend on other services (for example a data source to access the database), and may require lifecycle management (i.e. the services have start()/stop() methods that needs to be invoked at appropriate times).
In services extending org.cometd.server.AbstractServer, dependency injection and lifecycle management needed to be written by hand in configuration servlet or configuration listeners.

Annotated server-side services offer full support for CometD features, and limited support for dependency injection and lifecycle management via the org.cometd.annotation.ServerAnnotationProcessor class.

Dependency Injection Support

The CometD project offers limited support for dependency injection, since normally this is accomplished by other frameworks such as Spring or Guice.

In particular, it supports only the injection of the BayeuxServer object on fields and methods (not on constructors) and the injection is performed only if the injection has not been performed yet.

The reason for this limited support is that the CometD project does not want to implement and support a generic dependency injection container, but only offer a simple integration with existing dependency injection containers and a minimal support for required CometD objects (such as the BayeuxServer instance).

Annotated Style Inherited Style
@org.cometd.annotation.Service("echoService")
public class EchoService
{
    @javax.inject.Inject
    private BayeuxServer bayeux;
}
 
public class EchoService extends AbstractService
{
    public EchoService(BayeuxServer bayeux)
    {
        super(bayeux, "echoService");
    }
}

The service class is annotated with @Service and specifies the (optional) service name of "echoService".
The BayeuxServer field is annotated with the standard JSR 330 @Inject annotation.
The @Inject annotation is supported (for example by Spring 3.x) for standard dependency injection as specified by JSR 330.

Lifecycle Management Support

The CometD project provides lifecycle management via the standard JSR 250 @PostConstruct and @PreDestroy annotations.
This support is offered for those that do not use a dependency injection container with lifecycle management such as Spring.

Channel Configuration Support

In order to initialize channels before they can be actually referenced for subscriptions, the CometD API provides the BayeuxServer.createIfAbsent(String channelId, ConfigurableServerChannel.Initializer... initializers) method, which allows to pass initializers to configure the given channel.
Furthermore, it is useful to have a configuration step for channels that happens before any subscription or listener addition, for example to configure authorizers on the channel.

In annotated services, you can use the @Configure annotation on methods:

@Service("echoService")
public class EchoService
{
    @Inject
    private BayeuxServer bayeux;

    @Configure("/echo")
    public void configure(ConfigurableServerChannel channel)
    {
        channel.setLazy(true);
        channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
    }
}
Session Configuration Support

Services that extend org.cometd.server.AbstractServer have two facility methods to access the LocalSession and the ServerSession, namely getLocalSession() and getServerSession().

In annotated services, this is accomplished using the @Session annotation:

@Service("echoService")
public class EchoService
{
    @Inject
    private BayeuxServer bayeux;

    @org.cometd.annotation.Session
    private LocalSession localSession;

    @org.cometd.annotation.Session
    private ServerSession serverSession;
}

Fields (or methods) annotated with the @Session annotation are optional; you can just have the LocalSession field, or only the ServerSession field, or both or none, depending if you need them or not.

Session fields (or methods) cannot be injected with @Inject. This is because the LocalSession object and the ServerSession object are related, and tied to a particular service instance. Using a generic injection mechanism could have led to confusion (e.g. using the same sessions in two different services, etc.).

Listener Configuration Support

For server-side services, methods annotated with @Listener represent callbacks that are invoked during the server-side processing of the message.
Listener methods get passed a reference to the ServerSession half object that sent the message and to the ServerMessage that the server is processing.
The callback method must have the following signature, or a covariant version of it:

Annotated Style Inherited Style
@Service("echoService")
public class EchoService
{
    @Inject
    private BayeuxServer bayeux;
    @Session
    private ServerSession serverSession;

    @org.cometd.annotation.Listener("/echo")
    public void echo(ServerSession remote, ServerMessage.Mutable message)
    {
        String channel = message.getChannel();
        Object data = message.getData();
        remote.deliver(serverSession, channel, data, null);
    }
}
 
public class EchoService extends AbstractService
{
    public EchoService(BayeuxServer bayeux)
    {
        super(bayeux, "echoService");
        addService("/echo", "echo");
    }

    public void echo(ServerSession remote, ServerMessage.Mutable message)
    {
        String channel = message.getChannel();
        Object data = message.getData();
        remote.deliver(getServerSession(), channel, data, null);
    }
}

The callback method could return false to indicate that the processing of subsequent listeners should not be performed and that the message should not be published.

Subscription Configuration Support

For server-side services, methods annotated with @Subscription represent callbacks that are invoked during the local-side processing of the message.
The local-side processing is equivalent to the remote client-side processing, but it's local to the server.
The semantic is very similar to the remote client-side processing, in the sense that the message has finished the server-side processing and has been published. When it arrives to the local side the information on the publisher is not available anymore, and the message is a plain org.cometd.bayeux.Message and not a org.cometd.bayeux.server.ServerMessage, exactly how it would happen for a remote client.

This is a rarer use case (most of the times user code needs to be triggered with @Listener semantic), but nonetheless is available.

The callback method must have the following signature:

@Service("echoService")
public class EchoService
{
    @Inject
    private BayeuxServer bayeux;
    @Session
    private ServerSession serverSession;

    @org.cometd.annotation.Subscription("/echo")
    public void echo(Message message)
    {
        System.out.println("Echo service published " + message);
    }
}
Annotation Processing

Annotation processing is done by the org.cometd.annotation.ServerAnnotationProcessor class.

BayeuxServer bayeux = ...;

// Create the ServerAnnotationProcessor
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(bayeux);

// Create the service instance
EchoService service = new EchoService();

// Process the annotated service
processor.process(service);

After the ServerAnnotationProcessor.process() method returns, the service has been processed by injecting the BayeuxServer object and the sessions objects, by calling initialization lifecycle methods, and by registering listeners and subscribers.

Symmetrically, annotation de-processing is performed by ServerAnnotationProcessor.deprocess(), which deregisters listeners and subscribers, and then calls destruction lifecycle methods (but does not de-inject the BayeuxServer object or session objects).

Client-Side Annotated Services

Client-side services have no equivalent in CometD releases prior 2.1.0.
Like their server-side counterpart, client-side services consist in classes annotated with @Service.

Client-side services have been introduced to reduce the boilerplate code that usually needs to be written:

Annotated Style Inherited Style
@Service
public class Service
{
    @Session
    private ClientSession bayeuxClient;

    @Listener(Channel.META_CONNECT)
    public void metaConnect(Message connect)
    {
        // Connect handling...
    }

    @Subscription("/foo")
    public void foo(Message message)
    {
        // Message handling...
    }
}
 
ClientSession bayeuxClient = ...;

bayeuxClient.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener()
{
    public void onMessage(ClientSessionChannel channel, Message message)
    {
        // Connect handling...
    }
});

bayeuxClient.handshake();
bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED);

bayeuxClient.getChannel("/foo").subscribe(new ClientSessionChannel.MessageListener()
{
    public void onMessage(ClientSessionChannel channel, Message message)
    {
        // Message handling...
    }
});
Dependency Injection and Lifecycle Management Support

The CometD project does not offer dependency injection for client-side services, but supports lifecycle management via the standard JSR 250 @PostConstruct and @PreDestroy annotations.
Client-side services usually have a shorter lifecycle than server-side services and their dependencies are usually injected directly while creating the client-side service instance.

Session Configuration Support

In client-side annotated services, the @Session annotation allows the service instance to have the ClientSession object injected in a field or method.
Like server-side annotated services, the session field (or method) cannot be injected with @Inject. This is to allow the maximum configuration flexibility between service instances and ClientSession instances.

@Service
public class Service
{
    @org.cometd.annotation.Session
    private ClientSession bayeuxClient;
}
Listener Configuration Support

In client-side annotated services, methods annotated with @Listener represent callbacks that are called upon receipt of messages on meta channels.
Listener callbacks must not be used to subscribe to normal channels.

Annotated Style Inherited Style
@Service
public class Service
{
    @Listener(Channel.META_CONNECT)
    public void metaConnect(Message connect)
    {
        // Connect handling...
    }
}
 
bayeuxClient.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener()
{
    public void onMessage(ClientSessionChannel channel, Message message)
    {
        // Connect handling...
    }
});
Subscription Configuration Support

In client-side annotated services, methods annotated with @Subscription represent callbacks that are called upon receipt of messages on normal channels.

Annotated Style Inherited Style
@Service
public class Service
{
    @Listener("/foo/*")
    public void foos(Message message)
    {
        // Message handling...
    }
}
 
bayeuxClient.getChannel("/foo/*").subscribe(new ClientSessionChannel.MessageListener()
{
    public void onMessage(ClientSessionChannel channel, Message message)
    {
        // Message handling...
    }
});
Annotation Processing

Annotation processing is done by the org.cometd.annotation.ClientAnnotationProcessor class.

ClientSession bayeuxClient = ...;

// Create the ClientAnnotationProcessor
ClientAnnotationProcessor processor = new ClientAnnotationProcessor(bayeuxClient);

// Create the service instance
Service service = new Service();

// Process the annotated service
processor.process(service);

bayeuxClient.handshake();

Listener callbacks are configured immediately on the ClientSession object, while subscription callbacks are automatically delayed until the handshake is successfully completed.

CometD 2 Java Server Services Integration

CometD 2 Java Server Services Integration

There are several ways to integrate your Bayeux services into your web application.

All of these ways are complicated by the fact that the BayeuxServer object is created by a servlet, and there is no easy way to detect, in general, when the BayeuxServer object has been created.

Integration via Configuration Servlet

The simplest way to initialize your web application with your services is to use a configuration servlet.
This configuration servlet will have no URL mapping, because its only scope is to initialize (or "wire" together) your services for your web application to work properly.

Following you can find a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

</web-app>

Note how we specified <load-on-startup> to be 1 for the CometD servlet (so that the Bayeux object gets created and put in the ServletContext), and to be 2 for the configuration servlet, so that it will be initialized only after the CometD servlet has been initialized and hence the BayeuxServer object be available.

This is the code for the ConfigurationServlet:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab the Bayeux object
        BayeuxServer bayeux = (BayeuxServer)getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);
        new EchoService(bayeux);
        // Create other services here

        // This is also the place where you can configure the Bayeux object
        // by adding extensions or specifying a SecurityPolicy
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

See here about the EchoService

Integration via Configuration Listener

Instead of using a configuration servlet, it is possible to use a configuration listener, by writing a class that implements ServletContextAttributeListener.

Following you can find the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.BayeuxInitializer</listener-class>
    </listener>

</web-app>

This is the code for the BayeuxInitializer:

public class BayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            // Grab the Bayeux object
            BayeuxServer bayeux = (BayeuxServer)event.getValue();
            new EchoService(bayeux);
            // Create other services here

            // This is also the place where you can configure the Bayeux object
            // by adding extensions or specifying a SecurityPolicy
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

Integration of Annotated Services

If you prefer annotated services, you still have to integrate them into your web application.
The procedure is very similar to the procedures above, but it requires usage of the annotation processor in order to process the annotations in your services.

For example, the ConfigurationServlet will become:

public class ConfigurationServlet extends GenericServlet
{
    private final List<Object> services = new ArrayList<Object>();
    private ServerAnnotationProcessor processor;

    public void init() throws ServletException
    {
        // Grab the BayeuxServer object
        BayeuxServer bayeux = (BayeuxServer)getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);
        
        // Create the annotation processor
        processor = new ServerAnnotationProcessor(bayeux);

        // Create your annotated service instance and process it
        Object service = new EchoService();
        processor.process(service);
        services.add(service);

        // Create other services here

        // This is also the place where you can configure the Bayeux object
        // by adding extensions or specifying a SecurityPolicy
    }

    public void destroy() throws ServletException
    {
        // Deprocess the services that have been created
        for (Object service : services)
            processor.deprocess(service);
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

Integration of Annotated Services via AnnotationCometdServlet

The org.cometd.java.annotation.AnnotationCometdServlet allows to specify a comma separared list of class names to instantiate and process using a ServerAnnotationProcessor.

This is a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.java.annotation.AnnotationCometdServlet</servlet-class>
        <init-param>
            <param-name>services</param-name>
            <param-value>com.acme.cometd.FooService, com.acme.cometd.BarService</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

In this example, the AnnotationCometdServlet will instantiate and process the annotations of one object of class com.acme.cometd.FooService and of one object of class com.acme.cometd.BarService.
The services created will be deprocessed when AnnotationCometdServlet is destroyed.

CometD 2 Java Server Services Spring Integration

CometD 2 Services Integration with Spring

Integration of CometD services with Spring is particularly interesting, since most of the times your Bayeux services will require other beans to perform their service.
Not all Bayeux services are as simple as the EchoService, and having Spring's dependency injection (as well as other facilities) integrated greatly simplifies development.

The following instructions are valid from the 2.1.0 release onwards.

XML Based Spring Configuration

The BayeuxServer object is directly configured and initialized in the Spring configuration file, and injects it in the servlet context, where it is picked up by the CometD servlet, which performs no further configuration or initialization.

The web.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Spring's applicationContext.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="otherService" class="com.acme..." />

    <bean id="bayeux" class="org.cometd.server.BayeuxServerImpl" init-method="start" destroy-method="stop">
        <property name="options">
            <map>
                <entry key="logLevel" value="3" />
                <entry key="timeout" value="15000" />
            </map>
        </property>  
    </bean>

    <bean id="echoService" class="com.acme.cometd.EchoService">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="otherService" /></constructor-arg>
    </bean>

    <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
        <property name="attributes">
            <map>
                <entry key="org.cometd.bayeux">
                    <ref local="bayeux" />
                </entry>
            </map>
        </property> 
    </bean>
</beans>

The BayeuxServer object is now created by Spring, configured via the options property, initialized via the start() method, and exported to the servlet context via Spring's ServletContextAttributeExporter.

Differently from CometD 1's Spring integration, in CometD 2 the BayeuxServer object created in this way is fully functional, and therefore there is no need to mark dependent services as lazy, and no need for glue code.

Annotation Based Spring Configuration

Spring 3.x supports annotation based configuration and annotated services integrate nicely with Spring 3.x.
Spring 3.x is required over Spring 2.5.x because it supports injection via JSR 330.
Prerequisite to make Spring 3.x work with CometD annotated services is to have JSR 330's javax.inject classes in the classpath along with JSR 250's javax.annotation classes (these are included in JDK 6 and therefore only required if you use JDK 5).
Do not forget that Spring 3.x requires CGLIB classes in the classpath as well.

The web.xml file is exactly equal to the one given as example in the XML based configuration above.

Spring's applicationContext.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.acme..." />

</beans>

Spring will scan the classpath for classes that qualify as Spring beans in the given base package.

The CometD annotated service needs some additional annotation to make it qualify as a Spring bean:

@javax.inject.Named // Tells Spring that this is a bean
@javax.inject.Singleton // Tells Spring that this is a singleton
@Service("echoService")
public class EchoService
{
    @Inject
    private BayeuxServer bayeux;
    @Session
    private ServerSession serverSession;

    @PostConstruct
    public void init()
    {
        System.out.println("Echo Service Initialized");
    }

    @Listener("/echo")
    public void echo(ServerSession remote, ServerMessage.Mutable message)
    {
        String channel = message.getChannel();
        Object data = message.getData();
        remote.deliver(serverSession, channel, data, null);
    }
}

The missing piece is that we need to tell Spring to perform the processing of the CometD annotations, and we do so using a Spring component:

@Component
public class Configurer implements DestructionAwareBeanPostProcessor, ServletContextAware
{
    private BayeuxServer bayeuxServer;
    private ServerAnnotationProcessor processor;

    @Inject
    private void setBayeuxServer(BayeuxServer bayeuxServer)
    {
        this.bayeuxServer = bayeuxServer;
    }

    @PostConstruct
    private void init()
    {
        this.processor = new ServerAnnotationProcessor(bayeuxServer);
    }

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException
    {
        processor.processDependencies(bean);
        processor.processConfigurations(bean);
        processor.processCallbacks(bean);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String name) throws BeansException
    {
        return bean;
    }

    public void postProcessBeforeDestruction(Object bean, String name) throws BeansException
    {
        processor.deprocessCallbacks(bean);
    }

    @Bean(initMethod = "start", destroyMethod = "stop")
    public BayeuxServer bayeuxServer()
    {
        BayeuxServerImpl bean = new BayeuxServerImpl();
        bean.setOption(BayeuxServerImpl.LOG_LEVEL, "3");
        return bean;
    }

    public void setServletContext(ServletContext servletContext)
    {
        servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer);
    }
}

This Spring component is the factory for the BayeuxServer object via the bayeuxServer() method (annotated with Spring's @Bean).
It requires itself the BayeuxServer object in order to create CometD's ServerAnnotationProcessor, and therefore it @Injects it into a setter method.
The lifecycle callback init() takes care of creating CometD's ServerAnnotationProcessor, which is then used during Spring's bean post processing phases.
Finally, the BayeuxServer object is exported into the servlet context to be used by the CometD servlet.

CometD 2 Java Client API

CometD 2 Java Client Implementation

The CometD client implementation can be used in any JSETM or JEETM application.

It is made of one main class, org.cometd.client.BayeuxClient, which implements the org.cometd.bayeux.client.ClientSession interface.
Differently from CometD 1, this class now supports pluggable transports (to allow a WebSocket transport to be implemented in future when the WebSocket protocol is standardized), and currently only supports the long polling transport via HTTP, that depends on Jetty's asynchronous HttpClient.

Typical usages of the CometD Java client are:

  • As the transport for a rich thick Java UI (for example, Swing) to communicate to a Bayeux Server (also via firewalls)
  • As a load generator to simulate thousands of CometD clients, like for example org.cometd.client.BayeuxLoadGenerator

The following sections will go in detail about the Java BayeuxClient APIs and their implementation secrets.

CometD 2 Java Client Handshake

CometD 2 Java Client API: Hanshake

To initiate the communication with the Bayeux server, you need to call

BayeuxClient.handshake()

A typical usage is the following:

// Create (and eventually setup) Jetty's HttpClient
HttpClient httpClient = new HttpClient();
// Here setup Jetty's HttpClient, for example:
// httpClient.setMaxConnectionsPerAddress(2);
httpClient.start();

// Prepare the transport
Map<String, Object> options = new HashMap<String, Object>();
ClientTransport transport = LongPollingTransport.create(options, httpClient);

ClientSession client = new BayeuxClient("http://localhost:8080/cometd", transport);
// Here setup the BayeuxClient, for example:
// client.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener() { ... });
client.handshake();

When BayeuxClient.handshake() is called, the BayeuxClient will perform the handshake with the Bayeux server and then will establish the long poll connection, asynchronously.

Note
Calling handshake() does not mean that you have completed the handshake with the server when handshake() returns.

To verify if the handshake is successful, you can add a MessageListener before calling BayeuxClient.handshake():

ClientTransport transport = ...
ClientSession client = new BayeuxClient("http://localhost:8080/cometd", transport);
client.getChannel(Channel.META_HANDSHAKE).addListener(new ClientSessionChannel.MessageListener() 
{ 
    public void onMessage(ClientSessionChannel channel, Message message)
    {
        if (message.isSuccessful())
        {
            // Here handshake is successful
        }
    }
});
client.handshake();

An alternative way is to use the built-in synchronous features of CometD 2's BayeuxClient and wait for the handshake to complete:

ClientTransport transport = ...
BayeuxClient client = new BayeuxClient("http://localhost:8080/cometd", transport);
client.handshake();
boolean handshaken = client.waitFor(1000, BayeuxClient.State.CONNECTED);
if (handshaken)
{
    // Here handshake is successful
}

The BayeuxClient.waitFor() method waits the given timeout (in milliseconds) for the BayeuxClient to reach the given state, and returns true if the state is reached before the timeout expires.

CometD 2 Java Client Subscription

CometD 2 Java Client API: Subscribing and Unsubscribing

Subscription to Normal (Broadcast) Channels

The CometD 2 Java Client API for subscribing and unsubscribing is better designed than CometD 1's and has been moved to the channel (where it belongs).
Subscriptions and unsubscriptions can only be done once the handshake is completed.

public class Example 
{
    private static final String CHANNEL = "/foo";
    private final ClientSessionChannel.MessageListener fooListener = new FooListener();

    public void attach()
    {
        ClientTransport transport = ...
        ClientSession client = new BayeuxClient("http://localhost:8080/cometd", transport);
        client.handshake();
        boolean handshaken = client.waitFor(1000, BayeuxClient.State.CONNECTED);
        if (handshaken)
        {
            client.getChannel(CHANNEL).subscribe(fooListener);
        }
    }

    private static class FooListener implements ClientSessionChannel.MessageListener
    {
        public void onMessage(ClientSessionChannel channel, Message message)
        {
            // Here we received a message on the channel
        }
    }
}

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Unsubscription is straightforward: if you unsubscribe, messages on that channel will not be delivered to message listeners.
Using the Example class above:

public class Example 
{
    ...
    public void detach()
    {
        client.getChannel(CHANNEL).unsubscribe(fooListener);
    }
}

Tip
It is recommended that you remove also the ClientSessionChannel.MessageListener that you registered when you subscribed.
To make this simpler, avoid using the anonymous inner class style to register a message listener, since otherwise you will not have its reference when you want to remove it.

Listening to Meta Channels

Meta channels are used for the internal implementation of the Bayeux protocol, and does not make any sense to subscribe to them.
It only make sense to listen to messages that could arrive to those channels.

public class Example
{
    public void init()
    {
        ClientSession client = ...;
        client.getChannel(Channel.META_HANDSHAKE).addListener(new ClientSessionChannel.MessageListener()
        {
            public void onMessage(ClientSessionChannel channel, Message message)
            {
                // Here we received a handshake response message
            }
        });
    }
}

CometD 2 Java Client Publish

CometD 2 Java Client API: Publishing

A typical example of publishing a message on a channel is the following:

ClientTransport transport = ...
ClientSession client = new BayeuxClient("http://localhost:8080/cometd", transport);
client.handshake();

Map<String, Object> data = new HashMap<String, Object>();
// Fill in the data
client.getChannel("/game/table/1").publish(data);

Publishing data on a channel is an asynchronous operation.

Note
Calling publish() does not mean that you have published the message when publish() returns.

Message batching is also available:

final ClientSession client = ...;
client.handshake();

client.batch(new Runnable()
{
    public void run()
    {
        Map<String, Object> data1 = new HashMap<String, Object>();
        // Fill in the data1 map object
        client.getChannel("/game/table/1").publish(data1);

        Map<String, Object> data2 = new HashMap<String, Object>();
        // Fill in the data2 map object
        client.getChannel("/game/chat/1").publish(data2);
    }
});

Warning
The ClientSession API allows also to batch using startBatch() and endBatch(), but remember to call endBatch() after having called startBatch(), for example in a finally block.
If you don't, your messages will continue to queue up, and your application will not work as expected.

CometD 2 Java Client Disconnection

CometD 2 Java Client API: Disconnecting

Disconnecting is straightforward:

BayeuxClient.disconnect();

Additionally, you can wait for the disconnect to complete:

BayeuxClient client = ...;
client.disconnect();
client.waitFor(1000, BayeuxClient.State.DISCONNECTED);

CometD 2 Clustering with Oort

CometD 2 Scalability Clustering with Oort

The CometD distribution ships a clustering solution called Oort that enhances the scalability of a CometD-based system.
Instead of connecting to a single node (usually represented by a virtual or physical host), clients connect to multiple nodes so that the processing power needed to cope with the load is spread among multiple nodes, giving the whole system more scalability then using a single node.

Oort clustering is not a high availability clustering solution: if one of the nodes crashes, then all the clients will be disconnected and will reconnect to other nodes (with a new CometD handshake).
All the information built by one client with its server up to that point (for example, the state of an online chess game) is generally lost (unless - of course - the application has implemented some other way to retrieve that information).

Typical Infrastructure

A typical, but not the only, infrastructure to setup a Oort cluster is to have a load balancer in front of Oort nodes, so that clients can connect transparently to any node.
The load balancer should implement stickyness, and this may be based on the remote IP address or on CometD's BAYEUX_BROWSER cookie (see the Bayeux Specification, section 8.1), or could be based on some other mechanism supported by the load balancer.
DNS should be configured with a single host name / IP address pair (the one of the load balancer), so that in case of a node crash, clients will attempt to reconnect to the same host name, but the load balancer will notice that the node is crashed and direct the connection to another node. The second node will not know about this client, and upon receiving the connect request will send to the client the advice to handshake.

oort infrastructure setup

Terminology

In the following sections, the following terminology will be used: an Oort cluster is also referred to as "Oort cloud" (and therefore "cloud" is a synonym for "cluster"), and a Oort node is also referred to as "Oort comet" (and therefore "comet" is a synonym for "node").

CometD 2 Oort Cloud

CometD 2 Oort Cloud

Any CometD server can become an Oort comet by configuring an instance of org.cometd.oort.Oort.
The org.cometd.oort.Oort instance is associated to the org.cometd.bayeux.server.BayeuxServer instance, and there can be one Oort instance for each BayeuxServer instance.

Oort comets need to know each other's URL in order to connect together and form a cloud.
From CometD 2.3.0 onwards, an automatic discovery mechanism based on multicast is available; for CometD pre 2.3.0 only a static configuration mechanism exists.

Common Configuration

For both static and automatic discovery there exist a set of parameters can be used to configure the Oort instance.
The following is the list of common configuration parameters shared by both the automatic discovery and static configuration servlets:

Parameter Name Mandatory Default Value Parameter Description
oort.url Y N/A The unique URL of the Bayeux server associated to the Oort comet
oort.secret N random string The pre-shared secret used to authenticate connections from other Oort comets
oort.channels N empty string A comma separated list of channels to observe at startup
clientDebug N false Whether to enable debug logging in the OortComet instances

 

Automatic Discovery Configuration

Configuration of the automatic discovery mechanism can be done either via code, or by configuring a org.cometd.oort.OortMulticastConfigServlet in web.xml, for example:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>oort</servlet-name>
        <servlet-class>org.cometd.oort.OortMulticastConfigServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>oort.url</param-name>
            <param-value>http://host:port/context/cometd</param-value>
        </init-param>
    </servlet>
</web-app>

Since Oort depends on BayeuxServer, the load-on-startup parameter of the OortMulticastConfigServlet must be greater than the one of the CometdServlet.

The mandatory oort.url init parameter must identify the URL at which this Oort comet can be contacted, and it must be the URL served by the CometdServlet of this node.
This URL will be sent to other Oort comets, so it is important that the host part of the URL does not point to "localhost" but to a resolvable host name or to an IP address, so that other comets in the cluster can contact this comet.
Likewise, the context path part of the URL must be configured properly for this web application.

In addition to the common configuration init parameters, OortMulticastConfigServlet supports the configuration of these additional init parameters:

Parameter Name Mandatory Default Value Parameter Description
oort.multicast.bindAddress N the wildcard address The bind address of the MulticastSocket that receives the advertisements
oort.multicast.groupAddress N 239.255.0.1 The multicast group address to join to receive the advertisements
oort.multicast.groupPort N 5577 The port over which advertisements are sent and received
oort.multicast.timeToLive N 1 The time to live of advertisement packets (1 = same subnet, 32 = same site, 255 = global)
oort.multicast.advertiseInterval N 1000 The interval in milliseconds at which advertisements are sent

 

Each comet that is configured with automatic discovery will emit an advertisement (containing the comet URL) every oort.multicast.advertiseInterval milliseconds on the specified multicast address and port (oort.multicast.groupAddress and oort.multicast.groupPort) with the specified time-to-live (oort.multicast.timeToLive).
Advertisements are emitted until the web application is stopped, and only serve to advertise that a new node has appeared. Oort has a built-in mechanism that takes care of membership organization (see below for details).

When enabling the Oort automatic discovery mechanism, you must be sure that:

  • Multicast is enabled in the operative system of your choice
  • The network interfaces have multicast enabled
  • Multicast traffic routing is properly configured

Linux is normally compiled with multicast support in the most common distributions, and network interfaces can be controlled with the ifconfig command to check if they have multicast enabled.
Multicast routing can be checked with the command route -n, and the output should contain a line similar to:

Destination    Gateway    Genmask      Flags    Metric    Ref    Use    Iface
224.0.0.0      0.0.0.0    240.0.0.0      U         0       0      0     eth0

You may also want to force the JVM to prefer an IPv4 stack by setting the system property -Djava.net.preferIPv4Stack=true to facilitate multicast networking.

Static Discovery Configuration

The static discovery mechanism can be used if multicast is not available on the system where CometD is deployed to, or when using CometD pre 2.3.0.

It is more cumbersome to setup and does not allow dynamic discovery of new nodes; where possible, the automatic discovery mechanism should be used instead.

The static discovery configuration can be done either via code, or by configuring an org.cometd.oort.OortStaticConfigServlet (or org.cometd.oort.OortServlet for CometD pre 2.3.0) in web.xml, for example:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>oort</servlet-name>
        <servlet-class>org.cometd.oort.OortStaticConfigServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>oort.url</param-name>
            <param-value>http://host:port/context/cometd</param-value>
        </init-param>
    </servlet>
</web-app>

Like for the automatic discovery, the load-on-startup parameter of the OortStaticConfigServlet must be greater than the one of the CometdServlet.

OortStaticConfigServlet supports the common init parameters listed in the previous section, and the following additional init parameters:

Parameter Name Mandatory Default Value Parameter Description
oort.cloud N empty string A comma separated list of URLs of other Oort comets to connect to at startup

 

Configured in this way, the Oort comet is ready to be part of the Oort cloud, but it's not part of the could yet, since it does not know the URLs of other comets (and there is no automatic discovery).
In order to make the Oort comet part of the Oort cloud, you can configure the oort.cloud init parameter of the OortStaticConfigServlet with a comma separated list of other Oort comet URLs to connect to:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>oort</servlet-name>
        <servlet-class>org.cometd.oort.OortStaticConfigServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>oort.url</param-name>
            <param-value>http://host1:port/context/cometd</param-value>
        </init-param>
        <init-param>
            <param-name>oort.cloud</param-name>
            <param-value>http://host2:port/context/cometd,http://host3:port/context/cometd</param-value>
        </init-param>
    </servlet>
</web-app>

Alternatively, it's possible to write custom initialization code (see the section on service integration for suggestions on how to do it) that links the node to the Oort cloud (this may be useful if Oort comet URLs cannot be know a priori, but may be known at runtime), for example:

public class OortConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab the Oort object
        Oort oort = (Oort)getServletContext().getAttribute(Oort.OORT_ATTRIBUTE);

        // Figure out the URLs to connect to, using other discovery means
        List<String> urls = ...;

        // Connect to the other Oort comets
        for (String url : urls)
        {
            OortComet oortComet = oort.observeComet(url);
            if (!oortComet.waitFor(1000, BayeuxClient.State.CONNECTED))
                throw new ServletException("Cannot connect to Oort comet " + url);
        }
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

The OortComet instance returned by Oort.observeComet(url) is a specialized version of BayeuxClient.

Membership Organization

When a Oort comet is instructed to connect to another Oort comet, a bidirectional communication is established.
If cometA is being connected to cometB (for example via oortA.observeComet(urlB)), then an OortComet instance will be created in cometA connected to cometB, and another OortComet instance will be created in cometB connected to cometA.

After this direct bidirectional communication has been established, a special message is broadcasted on the whole Oort cloud (on channel /oort/cloud) where the two comets broadcast their known siblings.
Every node receiving this message that does not know about those siblings will establish a bidirectional communication with them.

For example, imagine that there are two simple Oort clouds, one made of comets A and B and the other made of comets C and D.
When A and C gets connected, they broadcast their siblings (A broadcasts its siblings, now B and C, while C broadcasts its siblings, now A and D). All nodes connected, directly or indirectly, to the broadcaster will receive this message.
When C receives A's siblings it notices that one is itself (so it does nothing since it's already connected to A), but the other is the unknown sibling B, and establishes a bidirectional connection with B as well. Likewise, A receives the sibling broadcast message from C, and connects to D. Each new bidirectional connection that is established triggers a sibling broadcast message on the whole cloud, until all comets are connected to all comets.

If a comet crashes, for example D, then all other comets will detect that and disconnect from the faulty comet.

oort cloud membership organization

In this way, an Oort cloud is aware of its members, but it does not do anything useful for the application.
The next section will cover broadcast messages forwarding over the entire cloud.

Authentication

When a Oort comet connects to another Oort comet, it sends a handshake message containing an extension field that is peculiar to Oort, with the following format:

{
    "channel": "/meta/handshake",
    ... /* other usual handshake fields */
    "ext": {
        "org.cometd.oort": {
            "oortURL": "http://halley.cometd.org:8080/cometd",
            "cometURL": "http://halebopp.cometd.org:8080/cometd",
            "oortSecret": "cstw27r+l+XqE62IrNZdCDiUObA="
        }
    }
}

The oortURL field is the URL of the comet that initiates the handshakes; the cometURL field is the URL of the comet that receives the handshake; the oortSecret is the base64 encoding of the SHA-1 digested bytes of the pre-shared secret of the initiating Oort comet (see the section on common configuration above).

These extension fields provide a way for a Oort comet to distinguish a handshake of a remote client (which may be subject to authentication checks) from a handshake performed by remote comet.
For example, let's assume that remote clients always send an extension field containing an authentication token; then it is possible to write an implementation of SecurityPolicy as follows (see also the section about authentication):

public class OortSecurityPolicy extends DefaultSecurityPolicy
{
    private final Oort oort;

    private OortSecurityPolicy(Oort oort)
    {
        this.oort = oort;
    }

    @Override
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)
    {
        // Local sessions can always handshake
        if (session.isLocalSession())
            return true;

        // Remote Oort comets are allowed to handshake
        if (oort.isOortHandshake(message))
            return true;

        // Remote clients must have a valid token
        Map ext = message.getExt();
        return ext != null && isValid(ext.get("token"));
    }
}

The Oort.isOortHandshake(Message) method validates the handshake message and returns true if it is a handshake from another Oort comet that has been configured with the same pre-shared secret. The pre-shared secret must be explicitly set because it defaults to a random string that is different for each Oort comet.

Broadcast Messages Forwarding

Broadcast messages (that is, messages sent to non-meta and non-service channels, see here for further details) are by definition messages that should be received by all clients that subscribed to the channel the message is being sent.

In an Oort cloud, you may have clients connected to different comets but subscribed to the same channel.
If we have clientA connected to cometA, clientB connected to cometB and clientC connected to cometC, then when clientA broadcasts a message we want clientB and clientC to receive that message, and therefore the Oort cloud must forward the message (sent by clientA and received by cometA) to cometB and cometC.

This is accomplished by configuring the Oort configuration servlets to set the oort.channels init parameter to a comma separated list of channels whose messages will be forwarded to the Oort cloud:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>oort</servlet-name>
        <servlet-class>org.cometd.oort.OortMulticastConfigServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>oort.url</param-name>
            <param-value>http://host1:port/context/cometd</param-value>
        </init-param>
        <init-param>
            <param-name>oort.channels</param-name>
            <param-value>/stock/**,/forex/*,/alerts</param-value>
        </init-param>
    </servlet>
</web-app>

Alternatively, it is possible to use Oort.observeChannel(String channelName) to instruct a comet to listen for messages on that channel published to one of the known comets it is connected to.

When cometA observes a channel, it means that messages sent on that channel, but received by other comets, are automatically forwarded to cometA.

Note
Message forwarding is not bidirectional; if cometA forwards messages to cometB it is not automatic that cometB forwards messages to cometA.
However, in most cases the Oort comets are configured in the same way by the same initialization code, and therefore all comets will forward the same channels.

With the ability of observing messages published to broadcast channels, an Oort cloud can already implement a simple chat application among users connected to different nodes.
In the example below, when clientA publishes a message on channel /chat (green arrow), it arrives on cometA; since cometB and cometC have been configured to observe channel /chat, they will both receive the message from cometA (green arrows), and therefore they can deliver the chat message to clientB and clientC respectively (green arrows).

oort chat

If your application only needs to broadcast messages to clients connected to other comets, an Oort instance is all you need.

If you need to send messages directly to particular clients (for example, clientA wants to send a message to clientC but not to clientB, then you need to setup an additional component of the Oort clustering called Seti.

CometD 2 Oort Seti

CometD 2 Oort Seti

Seti is the Oort clustering component that tracks clients connected to any comet in the cloud, and allows an application to send messages to particular client(s) in the cloud transparently, as if they were in the local comet.

Configuration

An org.cometd.oort.Seti instance must be configured with an associated org.cometd.oort.Oort instance, either via code, or by configuring an org.cometd.oort.SetiServlet in web.xml, for example:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>oort</servlet-name>
        <servlet-class>org.cometd.oort.OortServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <init-param>
            <param-name>oort.url</param-name>
            <param-value>http://host:port/context/cometd</param-value>
        </init-param>
    </servlet>

    <servlet>
        <servlet-name>seti</servlet-name>
        <servlet-class>org.cometd.oort.SetiServlet</servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>
</web-app>

Note how the load-on-startup parameter of the SetiServlet must be greater than that of the OortServlet.
SetiServlet does not have any configuration init parameter.

Associating Users

Seti allows to associate a unique string representation of a user with one or more ServerSession (see the CometD concepts for more details on ServerSession).
This is normally done when the user first logs in into the application, and the unique string representation of the user can be anything that the user provides to authenticate itself (a user name, a token, a database id, etc). For brevity, we will call this unique string representation of the user simply userId.
Note that the same userId may login multiple times (for example from a desktop computer and from a mobile device), so it will be associated to multiple ServerSessions.

In practice, the best way of associating a userId with a ServerSession is in a SecurityPolicy during authentication, for example:

public class MySecurityPolicy extends DefaultSecurityPolicy
{
    private final Seti seti;

    public MySecurityPolicy(Seti seti)
    {
        this.seti = seti;
    }

    @Override
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)
    {
        if (session.isLocalSession())
            return true;

        // Authenticate
        String userId = performAuthentication(session, message);
        if (userId == null)
            return false;

        // Associate
        seti.associate(userId, session);

        return true;
    }   
}

Alternatively, you can perform the association in an BayeuxServer.Extension or in a CometD service, in response to a specific message that it is always sent by the client after a successful handshake.

When Seti associates a userId with a session, it broadcasts an internal message on the cloud (on channel /seti/all) that tells to all the other comets where this userId is.
In this way, all the comets in the cloud know where a particular userId resides.
The same userId may be associated in different comets (for example, the desktop computer logs in - and therefore is associated - in comet1, while the mobile device is associated in comet2).

Similarly, you can disassociate a userId at any time by calling Seti.disassociate(userId, session).
If the user disconnects or "disappears" (for example, it crashed or its network dropped), its session will be removed or expired by the server and Seti will automatically disassociate the userId.

Sending Messages

After users have been associated, sending a message to a particular user in the cloud can be done via Seti.sendMessage(String userId, String channel, Object data).

@Service("seti_forwarder");
public class SetiForwarder
{
    private final Seti seti;

    private SetiService(Seti seti)
    {
        this.seti = seti;
    }

    @Listener("/service/forward")
    public void forward(ServerSession session, ServerMessage message)
    {
        Map data = message.getDataAsMap();
        String targetUserId = (String)data.get("targetUserId");
        seti.sendMessage(targetUserId, message.getChannel(), data);
    }
}

In the example below, clientA wants to send a message to clientC but not to clientB.
Therefore clientA sends a message to the server it is connected to using a service channel (so that the message is not broadcasted), and then a specialized CometD service will route the message to the appropriate user using Seti (see above). The Seti on cometA knows that the target user is on cometC (thanks to the association) and forwards the message to cometC which will in turn deliver the message to clientC.

seti chat

CometD 2 Extensions

CometD 2 API: Extensions

The CometD implementation has the capability to add/remove extensions.
An extension is a function that is being called to give the chance to modify the message just before it is being sent (an outgoing extension) or just after it is being received (an incoming extension).

An extension normally adds fields to the message being sent or received in the ext object defined by the Bayeux specification (see here).

An extension is not a way to add business fields to a message, but rather a way to process all messages, including the meta messages used by the Bayeux protocol and to extend the Bayeux protocol itself.

Extensions are normally setup on both the client and the server, since fields added by the client normally needs a special processing by the server; it may be possible that an extension is only client-side or only server-side, though.
Most of the times, however, they are needed in both client and server, and when the extension does not behave as expected, it's normally because the extension on one of the two sides is missing.

The JavaScript CometD Extensions are described in the next sections, and follow the same pattern used by the portable JavaScript CometD implementation: a portable implementation of the extension with bindings for the specific JavaScript toolkit, currently Dojo and jQuery.

Writing the Extension

An extension is a JavaScript object with 4 optional methods:

  • outgoing(message), called just before a message is being sent
  • incoming(message), called just after a message is received
  • registered(name, cometd), called when the extension is registered
  • unregistered(), called when the extension is unregistered

All 4 methods are optional, or there can be only one, or maybe two, three or all of them. If they are present, they will be invoked at the proper time.

Writing an extension that logs and counts the long polls is quite easy: we need a reference to the cometd object, that has the logging methods, and we need only the outgoing extension method:

var LoggerExt = function()
{
    var _cometd;
    var _counter;

    this.registered = function(name, cometd)
    {
        // Store the cometd object reference
        _cometd = cometd;
    };

    this.outgoing(message)
    {
        if (message.channel == '/meta/connect')
        {
            // Log the long poll
            _cometd._info('bayeux connect');

            // Count the long polls
            if (!message.ext) message.ext = {};
            if (!message.ext.logger) message.ext.logger = {};
            if (!message.ext.logger.counter) message.ext.logger.counter = 0;
            message.ext.logger.counter = ++_counter;
        }
    };
};

Note that also meta messages are passed to the extension methods; you normally have to filter the messages that the extension method receives by looking at the channel or at some other message value.
Note that the message can be modified by adding fields, normally in the ext field.

Note
Be careful to not overwrite the ext field that it may have been set by other extensions: check if it's present first.
It is also a good practice to group your extension fields so that there is no clash with other extensions (in the example above the only field - counter - is "grouped" in the message.ext.logger object).

The outgoing() and incoming() methods can avoid to return something, or return the message itself (or another message object). This means that the message has been processed by the extension and can therefore be processed by other extensions, if present, or processed by the implementation (either be sent to the server - for outgoing extensions - or be notified to listeners - for incoming extensions).
If null is returned by the extension method, this means that the processing should stop: the message will not be processed by other extensions and will not be further processed (therefore it will neither be sent to the server nor be notified to listeners).

Registering the Extension

The JavaScript CometD API defines 3 methods to manage extensions:

  • registerExtension(name, extension), to register an extension with the given name
  • unregisterExtension(name), to unregister the extension previously registered with the given name
  • getExtension(name), to obtain a reference to the extension previously registered with the given name

Following the example above, we can register the extension like this:

cometd.registerExtension('loggerExt', new LoggerExt());

From now on, the meta connect messages will be modified to carry the counter from the example extension above.

Unregistering the extension is similar:

cometd.unregisterExtension('loggerExt');

It is not possible to register 2 extensions under the same name.

You can register more than one extension, and they will be applied following the registration order: outgoing extensions methods will be called in registration order and, by default, incoming registration methods will be called in reverse registration order. See also the reverseIncomingExtensions configuration parameter in the configuration section.
For example, if you register extA and extB, then for outgoing messages the methods called are: extA.outgoing() and then extB.outgoing(), while for incoming messages the methods called are extB.incoming() and then extA.incoming().

Extension Exception Handling

While it is normally good practice to catch exceptions within extension functions, sometimes this is tedious to code, or there is no control about the quality of the extension (e.g. it's a third party extension).
The JavaScript CometD API provides a way to define the global extension exception handler that is invoked every time an extension throws an exception (for example, calling a method on an undefined object, etc.):

cometd.onExtensionException = function(exception, extensionName, outgoing, message)
{
    // Uh-oh, something went wrong, disable this extension
    // Object "this" points to the CometD object
    this.unregisterExtension(extensionName);

    // If the message is going to the server, add the error to the message
    if (outgoing)
    {
        // Assume we have created the message structure below
        var badExtension = message.ext.badExtensions[extensionName];
        badExtension.exception = exception;
    }
}

Be very careful to use the CometD object to publish messages within the extension exception handler, or you may end up in an infinite loop (the publish message is processed by the extensions, which may fail and call again the extension exception handler).
If the extension exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for listeners and subscribers, see here.

The following sections will explain in detail the usage of the provided extensions.

CometD 2 Acknowledge Extension

CometD 2 Acknowledge Extension

The acknowledged messages extension provides reliable ordered messaging to the Bayeux protocol.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.
If you are interested only in ordered messaging, see here.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.bayeux.server.BayeuxServer instance during initialization:

bayeuxServer.addExtension(new org.cometd.server.ext.AcknowledgedMessagesExtension());

The AcknowledgedMessageExtension is a per-server extension that monitors handshakes from new clients, looking for clients that also support the acknowledged message extension and then adds the AcknowledgedMessagesClientExtension to each client during the handshake.

Once added to a client, the AcknowledgedMessagesClientExtension guarantees ordered delivery of messages, and resend of unacknowledged messages.
The extension also maintains a list of unacknowledged messages and intercepts the traffic on the /meta/connect channel to insert and check acknowledge IDs.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.ack");

The client side extension binding for jQuery is provided by the file jquery.cometd-ack.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="AckExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-ack.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "ack".

Furthermore, the extension may be programmatically disabled/enabled before initialization by setting the ackEnabled boolean field on the cometd object:

// Disables the ack extension during handshake
cometd.ackEnabled = false;
cometd.init(cometdURL);

Acknowledge Extension Details

In order to enable message acknowledgement, both client and server must indicate that they support message acknowledgement.
This is negotiated during handshake. On handshake, the client sends {"ext":{"ack": "true"}} to indicate that it supports message acknowledgement.
If the server also supports message acknowledgment, it likewise replies with {"ext":{"ack": "true"}}.

The extension does not insert ack IDs to every message, as this would impose a significant burden on the server for messages sent to multiple clients (which would need to be reserialized to json for each client). Instead the ack ID is inserted in the ext field of the /meta/connect messages that are associated with message delivery. Each /meta/connect request contains the ack ID of the last received ack response: "ext":{"ack": 42}. Similarly, each /meta/connect response contains an ext ack ID that uniquely identifies the batch of responses sent.

If a /meta/connect message is received with an ack ID lower that any unacknowledged messages held by the extension, then these messages are requeued prior to any more recently queued messages and the /meta/connect response sent with a new ack ID.

Demo

There is an example of acknowledged messages in the Dojo chat demo that comes bundled with the Cometd distribution.
The example can also be run by building the Cometd project.

To run the demo, follow these steps:

$ cd cometd-demo
$ mvn jetty:run

Then point your browser to http://localhost:8080/dojo-examples/chat/ and make sure to check "Enable reliable messaging".

Use two different browsers instances to begin a chat session, then briefly disconnect one browser from the network (you can do this by setting the "work offline" feature).
While one browser is disconnected, type some chat in the other browser and this will be received when the disconnected browser is reconnected to the network.

Note that if the disconnected browser is disconnected for in excess of maxInterval (default 10s), then the client will be timed out and the unacknowledged queue discarded.

CometD 2 Reload Extension

CometD 2 Reload Extension

The reload extension allows a page to be loaded (or the same page to be reloaded) without having to re-handshake in the new (or reloaded) page, therefore resuming the existing CometD connection.
This extension requires only the client-side extension.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/reload.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.reload");

The client side extension binding for jQuery is provided by the file jquery.cometd-reload.js. This file must be included in the HTML page via the <script> tag, along with the jQuery cookie plugin and the reload extension implementation:

<script type="text/javascript" src="jquery.cookie.js"></script>
<script type="text/javascript" src="ReloadExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-reload.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "reload".

Reload Extension Details

The reload extension allows a page to be reloaded and an existing CometD connection resumed by the reloaded page.

To activate the reload extension, the page must call cometd.reload() just before the page is reloaded (for example, after a link is clicked or on the page unload event).
This saves a short lived cookie with the connection details.
The new page still needs to call subscribe to the channels it is interested in to register the callbacks, since the client state has been completely reset due to the reload.

An example simple usage is:

<html>
    <head>
        <script type="text/javascript" src="dojo/dojo.js"></script>
        <script type="text/javascript">
            dojo.require("dojox.cometd");
            dojo.require("dojox.cometd.reload");

            dojox.cometd.init({ url: "/context/cometd", logLevel: "info" });
            dojox.cometd.subscribe("/some/channel", function() { ... });
            dojox.cometd.subscribe("/some/other/channel", function() { ... });
            
            // On page unload, call dojox.cometd.reload()
            dojo.addOnUnload(dojox.cometd, "reload");
        </script>
    </head>
    <body>
    ...
    </body>
</html>

CometD 2 Timestamp Extension

CometD 2 Timestamp Extension

The timestamp extension add a timestamp to the message object, for every message sent by the client and/or server.
It is a non standard extension because it does not add the additional fields to the ext field, but to the message object itself.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for timestamped messages, the extension must be added to the org.cometd.bayeux.server.BayeuxServer instance during initialization:

bayeuxServer.addExtension(new org.cometd.server.ext.TimestampExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timestamp");

The client side extension binding for jQuery is provided by the file jquery.cometd-timestamp.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timestamp.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timestamp".

CometD 2 Timesync Extension

CometD 2 Timesync Extension

The timesync extension uses the messages exchanged between a client and a server to calculate the offset between the client's clock and the server's clock.
This is independent from the timestamp extension, which uses the local clock for all timestamps.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.bayeux.server.BayeuxServer instance during initialization:

bayeuxServer.addExtension(new org.cometd.server.ext.TimesyncExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/timesync.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timesync");

The client side extension binding for jQuery is provided by the file jquery.cometd-timesync.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timesync.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timesync".

Timesync Extension Details

The timesync extension allows the client and server to exchange time information on every handshake and connect message so that the client may calculate an approximate offset from it's own clock epoch to that of the server.
The algorithm used is very similar to the NTP algorithm.

With each handshake or connect, the extension sends timestamps within the ext field like:

{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}

where:

  • tc is the client timestamp in ms since 1970 of when the message was sent
  • l is the network lag that the client has calculated
  • o is the clock offset that the client has calculated

The accuracy of the offset and lag may be calculated with tc-now-l-o, which should be zero if the calculated offset and lag are perfectly accurate.

A Bayeux server that supports timesync, should respond only if the measured accuracy value is greater than accuracy target.
The response will be an ext field like:

{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}

where:

  • tc is the client timestamp of when the message was sent
  • ts is the server timestamp of when the message was received
  • p is the poll duration in ms - ie the time the server took before sending the response
  • a is the measured accuracy of the calculated offset and lag sent by the client

On receipt of the response, the client is able to use current time to determine the total trip time, from which p is subtracted to determine an approximate two way network traversal time. Thus:

  • lag = (now-tc-p)/2
  • offset = ts-tc-lag

In order to smooth over any transient fluctuations, the extension keeps a sliding average of the offsets received.
By default this is over 10 messages, but this can be changed by passing a configuration object during the creation of the extension:

// Unregister the default timesync extension
cometd.unregisterExtension('timesync');

// Re-register with different configuration
cometd.registerExtension('timesync', new org.cometd.TimeSyncExtension({ maxSamples: 20 }));

The client-side timesync extension also exposes several methods to deal with the result of the time synchronization:

  • getNetworkLag(), to obtain the calculated network latency between client and server
  • getTimeOffset(), to obtain the offset between the client's clock and the server's clock in ms
  • getServerTime(), to obtain the server's time
  • setTimeout(), to schedule a function to be executed at a certain server time

CometD 2 Howtos

CometD 2 Howtos

This is a list of how-tos for common problems that you can find while developing with CometD 2.

CometD 2 Authentication

CometD 2 Authentication

Authentication is a complex task, and can be achieved in many ways, and most often each way is peculiar to a particular project.

Below you can find few ideas on how you can perform authentication.

Authentication via XMLHttpRequest

Authentication information could be passed by the CometD JavaScript client to the server via XMLHttpRequest.open(method, url, async, user, password), but this is currently not well supported.
The main reason is that it will not work in the cross domain case, since XMLHttpRequest does not support cross domain authentication.

Authentication via HTTP request headers

Authentication information can be passed by the CometD JavaScript client to the server via HTTP request headers in each CometD request, and verified by the server (for example via a servlet filter).
It's possible to specify such request headers using the requestHeaders configuration parameter as specified here.

Authentication via Bayeux SecurityPolicy and Extension

This method is more complicated than previous ones, but gives more flexibility.
Authentication information can be passed by the CometD JavaScript client to the server during Bayeux handshake.
The CometD JavaScript client handshake() and init() methods take an extra object that's merged with the handshake message, for example:

cometd.configure({
    url: 'http://localhost:8080/myapp/cometd'
});

// Register a listener to be notified of authentication success or failure
cometd.addListener('/meta/handshake', function(message) 
{
    var auth = message.ext && message.ext.authentication;
    if (auth && auth.failed === true)
    {
        // Authentication failed, tell the user
        window.alert('Authentication failed!');
    }
});

var username = 'foo';
var password = 'bar';

// Send the authentication information
cometd.handshake({
    ext: {
        authentication: {
            user: username,
            credentials: password
        }
    }
});

The Bayeux specification suggests that authentication information is stored in the ext field of the handshake message (see here).

On the server, we need to configure the BayeuxServer object with an implementation of org.cometd.bayeux.server.SecurityPolicy (to deny the handshake to clients that provide bad authentication information).

The BayeuxServer object need to be configured with the custom security policy, for example using a configuration servlet:

public class BayeuxInitializer extends GenericServlet
{
    @Override
    public void init() throws ServletException
    {
        BayeuxServer bayeux = (BayeuxServer)getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);
        BayeuxAuthenticator authenticator = new BayeuxAuthenticator();
        bayeux.setSecurityPolicy(authenticator);
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

Below you can find the code for the BayeuxAuthenticator class:

public class BayeuxAuthenticator extends DefaultSecurityPolicy implements ServerSession.RemoveListener      (1)
{
    @Override
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)          (2)
    {
        if (session.isLocalSession())                                                                       (3)
            return true;

        Map<String, Object> ext = message.getExt();
        if (ext == null)
            return false;

        Map<String, Object> authentication = (Map<String, Object>)ext.get("authentication");
        if (authentication == null)
            return false;

        Object authenticationData = verify(authentication);                                                 (4)
        if (authenticationData == null)
            return false;

        // Authentication successful                                      

        // Link authentication data to the session                                                          (5)

        // Be notified when the session disappears
        session.addListener(this);                                                                          (6)

        return true;
    }

    public void removed(ServerSession session, boolean expired)                                             (7)
    {
        // Unlink authentication data from the remote client                                                (8)
    }
}

Note that:

  • in (1) we make BayeuxAuthenticator be a SecurityPolicy and a ServerSession.RemoveListener, since the code is really tied together
  • in (2) we override canHandshake() (a SecurityPolicy method), where we extract the authentication information from the message sent by the client
  • in (3) we allow handshake for any server-side local session (such as those associated with services)
  • in (4) we verify the authentication information sent by the client, and obtain back server-side authentication data that we can later associate with the remote client
  • in (5) we link the server-side authentication data to the session
  • in (6) we register to be notified when the remote client disappears (which we will react to in (8))
  • in (7) we implement removed() (a RemoveListener method), which is called when the remote client disappears, either because it disconnected or because it crashed
  • in (8) we unlink the server-side authentication data from the remote client object, the operation opposite to (5)

The most important steps are the number 5 and the number 8, where the server-side authentication data is linked/unlinked to/from the org.cometd.bayeux.server.ServerSession object.
This linking depends very much from project to project.
It may link a database primary key (of the row representing the user account) with the remote session id (obtained with session.getId()), and/or viceversa, or it may link OAUTH tokens with the remote session id, etc.

Each Bayeux message always come with a session id, which can be thought as similar to the HTTP session id.
In the same way it is widespread practice to put the server-side authentication data in the HttpSession object (identified by the HTTP session id), in CometD web applications we can put server-side authentication data in the ServerSession object.

The Bayeux session ids are long, randomly generated numbers, exactly like HTTP session ids, and offer the same level security offered by a HTTP session id.
If an attacker manages to sniff a Bayeux session id, it can impersonate that Bayeux session exactly in the same way it can sniff a HTTP session id and impersonate that HTTP session.
And, of course, the same solutions to this problem used to secure Http applications can be used to secure CometD web applications.

Customizing the handshake response message

The handshake response message can be customized, for example adding an object to the ext field of the response, that specify further challenges data or the code/reason of the failure, and what action should be done by the client (for example, disconnecting or retrying the handshake).

This is an example of how the handshake response message can be customized in the SecurityPolicy:

public class MySecurityPolicy extends DefaultSecurityPolicy
{
    public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)
    {
        if (!canAuthenticate(session, message))
        {
            // Retrieve the handshake response
            ServerMessage.Mutable handshakeReply = message.getAssociated();

            // Modify the advice, in this case tell to try again
            // If the advice is not modified it will default to disconnect the client
            Map advice = handshakeReply.getAdvice(true);
            advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_HANDSHAKE_VALUE);
            
            // Modify the ext field with extra information on the authentication failure
            Map ext = handshakeReply.getExt(true);
            Map authentication = new HashMap();
            ext.put("com.acme.auth", authentication);
            authentication.put("failureReason", "invalid_credentials");
            return false;
        }
        return true;
    }
}

Alternatively, it is possible to customize the handshake response message by implementing a BayeuxServer.Extension:

public class HandshakeExtension implements BayeuxServer.Extension
{
    public boolean sendMeta(ServerSession to, ServerMessage.Mutable message)
    {
        if (Channel.META_HANDSHAKE.equals(message.getChannel()))
        {
            if (!message.isSuccessful())
            {
                Map advice = message.getAdvice(true);
                advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_HANDSHAKE_VALUE);

                Map ext = message.getExt(true);
                Map authentication = new HashMap();
                ext.put("com.acme.auth", authentication);
                authentication.put("failureReason", "invalid_credentials");
            }
        }
    }

    // Other methods omitted
}

CometD 2 Load Testing

CometD 2 Load Testing

The CometD project comes with a load test tool that can be used to get the gist of how CometD scales.
Note however that real deployments often have a totally different behavior that depends on OS settings, TCP stack settings, other network settings, JVM settings and application settings, so the basic load test tool gives you information only up to a point.

Setup

Load testing can be very stressful to the OS, TCP stack and network, so you need to tune a few values to avoid that the OS, TCP stack or network become a bottleneck, making you think the CometD does not scale. It does scale.
The setup must be done on both client(s) and server.
A suggested setup is the following (for Linux; it may vary for other operative systems):

# ulimit -n 65536
# ifconfig eth0 txqueuelen 8192 # replace eth0 with the ethernet interface you are using
# /sbin/sysctl -w net.core.somaxconn=4096
# /sbin/sysctl -w net.core.netdev_max_backlog=16384
# /sbin/sysctl -w net.core.rmem_max=16777216
# /sbin/sysctl -w net.core.wmem_max=16777216
# /sbin/sysctl -w net.ipv4.tcp_max_syn_backlog=8192
# /sbin/sysctl -w net.ipv4.tcp_syncookies=1

Read also here for more information.

Running the server

If you have already deployed your Bayeux server, then you can use that as a load test server. The load test tool only requires to handshake, subscribe and publish to normal channels, which is what any Bayeux server can do out of the box.
If you have installed a security policy (see here), you may need to tweak the load test tool to satisfy the policy conditions (for example by adding security tokens and the like).
If you use your server, however, you will loose some important report that is available in the CometD load server (but that you can add to your server by looking at how it is done in the CometD load server).

If you don't have a server already running, then you need to build and run the CometD load server.
Follow the instructions in the build section to build the whole project (if not already done), then issue these commands in a terminal window:

$ cd cometd-java/cometd-java-examples // Use "cd cometd-java/cometd-java-client" for CometD versions prior 2.4.0  
$ mvn -Pserver install exec:exec

This command launches the CometD load server after a number of questions prompted in the terminal window; you can additionally tweak the command line by tweaking the pom.xml section related to the "server" profile.

Running the load test client

The load test client can be run on a different host than the server, and you need to build and run the tool from the sources.
Follow the instructions in the build section to build the whole project (if not already done), then issue these commands:

$ cd cometd-java/cometd-java-examples // Use "cd cometd-java/cometd-java-client" for CometD versions prior 2.4.0  
$ mvn -Pclient install exec:exec

This command launches the CometD load client after a number of questions prompted in the terminal window; you can additionally tweak the command line by tweaking the pom.xml section related to the "client" profile.

The load test tool simulates a chat client and prompts you for configuration options:

server [localhost]: 
port [8080]: 
transports:
  0 - long-polling
  1 - websocket
transport [0]: 1
use ssl [false]: 
max threads [256]: 
context [/cometd]: 
channel [/chat/demo]: 
rooms [100]: 
rooms per client [1]: 
record latency details [true]: 
-----
clients [100]: 1000
Waiting for clients to be ready...
Waiting for clients 999/1000
Clients ready
batch count [1000]: 
batch size [10]: 1
batch pause (µs) [10000]: 
message size [50]: 
randomize sends [false]: 

The default configuration connects 100 Bayeux clients to the server at http://localhost:8080/cometd, then sends 1000 batches of 10 messages (of size 50 bytes) with 10 ms pauses between each batch, choosing a random client for each batch as the sender. The client sends the messages to the chat room(s) it is subscribed to, the messages arrive to the server and the server broadcasted them back to the subscribers of the chat room(s), that receive the messages.
The latency between the sends and the receives is then measured and displayed, for example:

Outgoing: Elapsed | Rate = 10273 ms | 973 messages/s - 97 requests/s
Waiting for messages to arrive 15815/15868
All messages arrived 15868/15868
Messages - Success/Expected = 15868/15868
Incoming - Elapsed | Rate = 10667 ms | 1487 messages/s 
Messages - Latency Distribution Curve (X axis: Frequency, Y axis: Latency):
             @        _  13 ms (2436)
   @                  _  26 ms (581)
                   @  _  39 ms (3805)
                 @    _  52 ms (3301)
         @            _  65 ms (1802)
        @             _  78 ms (1501)
     @                _  91 ms (1029)
   @                  _  104 ms (537)
 @                    _  117 ms (280)
 @                    _  130 ms (163)
 @                    _  142 ms (158)
 @                    _  155 ms (107)
@                     _  168 ms (76)
@                     _  181 ms (30)
@                     _  194 ms (17)
@                     _  207 ms (33)
@                     _  220 ms (9)
@                     _  233 ms (1)
@                     _  246 ms (0)
@                     _  259 ms (2)
Messages - Latency Min/Ave/Max = 0/48/259 ms

Note how we chose a delay of 10 ms between batches, yielding a potential of 1000 messages sent/s (10 sends every 10 ms), and actually achieved 973 sends/s.

The number of messages depends on how many clients are subscribed to each chat room: sending one message to a chat room with 5 subscribers results in one messages being broadcasted back to each of the 5 subscribers.
In the example above we sent 10000 messages and received 15868, which makes sense since we have 100 clients subscribed randomly to 100 rooms so in average we have one client per room (with some room empty and some room with more than one subscriber). Had we chosen 1 chat room only, we would have had 10000 * 100 messages back (i.e. each message sent would have been broadcasted back to the 100 clients).

The rate of messages back is 1487 messages/s, and again makes sense that it is greater than the send rate if we consider that due to the random subscription, each message sent could have generated more than one message sent back (when more than one client is subscribed to the same chat room).

For the latency distribution curve, imagine to rotate it 90 degrees counter-clockwise. It shows a bell-shaped curve with the peak at around 33 ms, and reports the number of messages (in parenthesis) received in each interval of time. In the curve above, 2436 messages were received with a latency between 0 and 13 ms, 581 messages with a latency between 13 and 26 ms, and so on.

Finally, the minimum, average and maximum latency are reported.

Exiting the load test tool

To exit the load test tool by disconnecting orderly the clients, just enter 0 as the number of clients:

-----
clients [100]: 0
Waiting for clients to be ready...
Waiting for clients 68/0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45 seconds
[INFO] Finished at: Wed Mar 31 18:20:15 CEST 2010
[INFO] Final Memory: 33M/216M
[INFO] ------------------------------------------------------------------------

Warnings

There are many things that can influence the results of the load testing.
Among many the most important two are:

  • OS / network stack setup
    See the section on setting up things above for details.
  • Resolution of the sleep timer
    Pauses in the load test tool are implemented via Thread.sleep(). If you choose a 10 ms pause (equivalent to 10000 µs), it may happen that the actual time elapsed in sleep mode is way longer that what you specified.
    This is because when Thread.sleep() communicates to the JVM and then to the OS to sleep 10 ms, the OS may have a coarser sleep resolution, and actually sleep for 25 ms or more.
    To avoid this problem, the load test tool auto-tunes itself by detecting the timer resolution on startup.
    It is not uncommon, especially for virtualized servers, to have timer resolutions as high as 64 ms.

CometD 2 Strict Message Ordering

CometD 2 Strict Message Ordering

CometD does not enforce, by default, a strict order on message delivery. The reason is that there are (normally) two communication conducts between client and server: the meta connect conduct and the current request conduct (originated, for example, by a publish() performed by the client).
If the server receives two messages in different times for a client - say message1 and message2, it is possible that message1 is included in the response of a meta connect, but message2 is included in the response of a current request.
Because the messages are delivered on two different and independent communication conducts, it is possible that message2 arrives to the client before message1, for example because the thread scheduling on the server favored message2 or because the TCP communication for message1 was somehow slowed down (not to mention that browsers could as well be source of uncertainty).

CometD allows to enforce strict message ordering in two ways: using the acknowledge extension (which also provides delivery guarantee), or by specifying the long-polling.json.metaConnectDeliverOnly parameter for the long-polling server transport or, since CometD 2.4.0, the ws.metaConnectDeliverOnly parameter for the websocket server transport (see the configuration options).

The websocket transport enforces strict message ordering by returning messages via the the meta connect response instead of the current request, even if there is only one conduct.

The callback-polling server transport always enforce strict message ordering (and it is not possible to disable it).

When strict message ordering is enabled, all messages will be delivered via the meta connect response. In the example above, message1 will be delivered to the client, and message2 will wait on the server until another long poll is issued by the client (which happens immediately after message1 has been received).
It is clear that strict message ordering comes at the small price of slightly increased latencies (message2 has to wait the next long poll before being delivered), and slightly more activity for the server (since meta connects will be resumed more frequently than normal).

CometD 2 & WebSocket

Enabling/Disabling WebSocket support in CometD 2

Since CometD 2.1.0, WebSocket support has been disabled by default, since the support in browsers is still too buggy.
The JavaScript WebSocket transport is there, but it's disabled.

However, the CometD demo has WebSocket support enabled, and you can have that too in your applications simply by executing this code in your JavaScript application:

// Using dojo style; use $.cometd for jQuery
var cometd = dojox.cometd;
cometd.websocketEnabled = true;

This code must be execute before the handshake to be effective.

CometD 2 & Servlet Containers

CometD 2 Configuration in Servlet Containers

CometD relies on Jetty Continuations, a library that is portable among servlet containers.

While CometD 2 runs natively in Jetty 7 and in Servlet 3 compliant servlet containers, it requires additional configuration for other servlet containers such as Jetty 6 or Tomcat 6.

These other servlet containers need a ContinuationFilter to be configured for the paths that make use of Jetty Continuations (in CometD this path is usually "/cometd/*"), for example:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>continuation</filter-name>
        <filter-class>org.eclipse.jetty.continuation.ContinuationFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>continuation</filter-name>
        <url-pattern>/cometd/*</url-pattern>
    </filter-mapping>
</web-app>

In this example, the CometdServlet, which makes use of Jetty Continuations, is mapped to /cometd/*, and therefore the ContinuationFilter must also be mapped on that path.

CometD Resources

CometD Bayeux Implementations

The table below shows the Bayeux implementations developed and hosted by the CometD project.
Follow the links on the tables to have more information about the implementations.

Language Client Server
javascript cometd-javascript
java cometd-java cometd-java
perl cometd-perl
python cometd-twisted

Other Bayeux Implementations

Language Client Server
groovy Cometd Grails Plugin
java Atmosphere
flex flexcomet
.net websynchronize

Downloads

cometd-perl

cometd-perl

quick start

quick start

building

building

cometd-twisted

twisted python

quick start

quick start

building

building

sandbox

☄ cometd-js

A Bayeux implementation server will be released at some point that is written in pure JavaScript on top of Rhino. This is an effort to provide users with an all JavaScript stack that can be easily customized by anybody that understands JavaScript.

If you are interested in the effort (currently closed source) or are interested in obtaining a copy of the code, please send an email to bob.remeika@gmail.com

CometD FAQs

CometD Frequently Asked Questions

Is CometD production ready ?

Is CometD production ready ?

The JavaScript and Java (both client and server) are definitely production ready, based on the solid Jetty project, and already deployed all around the world from small to big sites.

The Python and Perl implementations are currently lacking active maintainers, and should not be considered production ready.
If you would like to contribute, join the mailing lists (see here) and drop a message.

Is it a good idea to put Apache httpd in front of a Bayeux server ?

Is it a good idea to put Apache httpd in front of a Bayeux server ?

The problem of putting Apache httpd in front of a Bayeux server is that it will not scale well for moderate to heavy loads.

The reason is that Apache httpd uses the thread-per-request model, which does not scale well in case of long polling connections.
Imagine 1000 Bayeux clients connected to Apache httpd; in such case, Apache httpd will have 1000 outstanding requests for the long polls only, plus any request that may be performed by the clients, so in total 1000+ threads concurrently active at any given time.
The burden of this model on memory and scheduling is quite high.

The asynchronous I/O features of a Bayeux server like Jetty allow to handle the same 1000 Bayeux clients with less than 10 concurrently active threads.
Assume 1000 Bayeux clients long polling every 20 seconds, and assume that each long poll request takes 100 ms to be processed, then we have

1000 long poll requests / 20 s = 50 requests/s
50 requests/s * 100 ms/request = 5 concurrent requests at any given time
5 concurrent requests at any given time => 5 threads active at any given time

Therefore 5 threads are needed by Jetty to handle 1000 Bayeux clients long polling where Apache httpd would need 1000 threads (and we assumed a large processing time of 100 ms/request whereas usual figures are way less than that).

If you have deployments where you expect 500+/1000+ Bayeux clients connected, then Jetty will scale a lot better than Apache httpd.

The problem is not Apache httpd, which is a great server; it is its thread-per-request model.
Therefore, any server using that model will suffer the same problems.
Here we mentioned Apache httpd because it is the most used solution used as front-end for web sites (and because it is a very frequently asked question).

Is there any IDE support ?

Is there any IDE support ?

The CometD project does not provide any direct IDE support, if you intend IDE plugins or similar components.

However, in the Primer it is explained how to setup a project with Maven, and the most used Java IDEs have a good support for Maven.

What is the BAYEUX_BROWSER cookie for ?

What is the BAYEUX_BROWSER cookie for ?

It is used by the Cometd Server implementation to detect multiple CometD clients from the same browser.
See here for a detailed explanation.

Why "IllegalStateException: !(Jetty || Servlet 3.0 || ContinuationFilter)" is thrown ?

The CometD implementation relies on Jetty Continuations.
This exception is thrown by the Jetty Continuation library when it finds that the web application configuration is not correct for the library to work.

To run properly, web applications that make use of this library needs either:

  • to run inside Jetty 6 or Jetty 7
  • to run in a Servlet 3.0 compliant servlet container
  • to have the ContinuationFilter configured properly

See here for details on configuring the ContinuationFilter.

Why "IllegalStateException: the servlet does not support async operations for this request" is thrown ?

This exception happens when CometD is run in Servlet 3 compliant containers (where the Jetty Continuations make use of the asynchronous features of Servlet 3) and web.xml is misconfigured.

The correct web.xml must have the async-supported element set to true, for example:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.CometdServlet</servlet-class>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

The async-supported element should be specified for the CometD servlet and for all filters that may be executed before the CometD servlet such as the CrossOriginFilter.

Refer to the Servlet 3 specification for further details.

Why ServletContext.getAttribute(Bayeux.ATTRIBUTE) returns null ?

The command

ServletContext.getAttribute(Bayeux.ATTRIBUTE)

returns null when it is run before the CometD servlet has had the chance to put the Bayeux object into the servlet (application) context.

This page explains how to use the load-on-startup element in web.xml to specify the order of initialization of the CometD servlet and of your code.

Why the CometD server sends messages twice ?

Why the CometD server sends messages twice ?

You have a JavaScript CometD client that subscribed to a channel and while publishing to that channel, you notice that it receives the message twice.

This is very common when you have written a Bayeux service like this:

public class MyService extends BayeuxService
{
    public MyService(Bayeux bayeux)
    {
        super(bayeux, "my-service");
        subscribe("/my/channel", "processMyMessage");
    }

    public void processMyMessage(Client remote, Message message)
    {
        getBayeux().getChannel(message.getChannel(), false).publish(getClient(), message.getData(), null);
    }
}

Let's put ourselves in the CometD Server's shoes for a moment: it sees 2 clients subscribed to channel "/my/channel": a remote client that subscribed via the JavaScript API, and a server-side client that is associated to the Bayeux service MyService and that in our example subscribed in MyService's constructor via the subscribe() call.

When the CometD server receives a message on channel "/my/channel" and notifies the clients, the remote client gets the first message, and MyService.processMyMessage() gets called.
But processMyMessage() publishes again on "/my/channel", so the remote clients gets the second message.

This raises the question of why processMyMessage() does not get called recursively ad infinitum ?
The CometD Server detects that the sending client (the first argument to the publish() call) is the client associated with the sending Bayeux service, and avoids the infinite recursion.
Had you put remote instead of getClient() as first argument of publish(), you would have infinitely recursed.

Solutions to this problem are:

  • the JavaScript client should use a service channel to communicate to the Bayeux server
  • the processMyMessage() method uses a different channel to publish the data

The most common solution is to use a service channel.

Bayeux

The primary purpose of Bayeux is to support responsive bidirectional interactions between web clients using Ajax and the web server.

Bayeux is a protocol for transporting asynchronous messages (primarily over HTTP), with low latency between a web server and a web client.
The messages are routed via named channels and can be delivered:

By default, publish/subscribe routing semantics are applied to the channels.

Delivery of asynchronous messages from the server to a web client is often described as "server-push".
The combination of server push techniques with an Ajax web application has been called "Comet".
CometD is a project by the Dojo Foundation to provide multiple implementation of the Bayeux protocol in several programming languages.

The Bayeux protocol seeks to reduce the complexity of developing Comet applications by allowing implementors to more easily interoperate, to solve common message distribution and routing problems, and to provide mechanisms for incremental improvements and extensions.

You may want to read the Bayeux Specification.

Specification