Using REST Channels with cometD

By on June 15, 2009 1:02 am

REST Channels provides a mechanism for receiving notifications of data changes and integrates Comet-style asynchronous server sent messages with a RESTful data-oriented architecture. Dojo includes a REST Channels client module which integrates completely with Dojo’s JsonRestStore, allowing messages to be delivered through the Dojo Data API seamlessly to consuming widgets, with minimal effort. The REST Channels module will automatically connect to a REST Channels server, like Persevere (which offers REST Channels out of the box). However, existing infrastructure may necessitate the use of an alternate Comet server like Jetty’s cometD server. REST Channels can be used on top of another Comet protocol like Bayeux’s long-polling protocol and with a little bit of reconfiguration, you can use Dojo’s REST Channels with a cometD server to achieve Comet-REST integration.

Receiving Data Notifications

REST Channels is designed to unite the concept of topics with resource locators. Therefore, REST Channels can leverage Bayeux’s topic names to indicate the resource that is being targeted. We can then subscribe to Bayeux’s channels and then delegate all the data notification messages to the restListener which will delegate the proper data events through the data stores:

// first initialize cometD
dojo.require("dojox.cometd");
dojo.require("dojox.cometd.RestChannels");

dojox.cometd.init("/cometd");

dojo.subscribe("/cometd/**", function(msg){
   dojox.data.restListener({
      channel: msg.channel,
      event: msg.data.event,
      result: msg.data.result
   });
});

This could be used in conjunction with a JsonRestStore, and messages would be delegated to the store’s events:

weatherStore = new dojox.data.JsonRestStore({target:"/weather/"});

Now it is possible to publish data change notification messages such that clients will receive and interpret the messages properly. To publish a data notification message on Jetty’s cometD hub, we can call the doPublish with something like:

bayeux.doPublish("/weather/lax", null, laxWeatherUpdate, null);

Which should produce a Bayeux notification message like:

{
  "channel": "/weather/lax",
  "clientId": "23ab8a887baaa",
  "data": {
     "event":"PUT",
     "result":{
       "low": 65,
       "high": 78,
       "skies": "clear"
    }
  }
}

Now this message will be routed through the restListener and onSet events will be fired for any attributes that were updated in the local cache of the “/weather/lax” item. If the lax item was being displayed in a data-aware widget, it will automatically be updated. For example, the DataGrid supports data notification events, and if this item appears in a DataGrid, the corresponding row will automatically display the new value.

Subscribing to Resources

One of the key integration points that REST Channels provides between data access and notification routing is in auto-subscribing to resources when they are retrieved. The primary use case for REST Channels is in providing a real-time view of data, and therefore when data is retrieved from the server, REST Channels can automatically subscribe to the resource to receive all future notifications of changes to the object. With a true REST Channels implementation, subscription information is included in GET requests, so that a single request can be made to a server that both requests a resource and subscribes to it at the same time. If you are just using a cometD server, such an integration is not automatic. There are a couple of different approaches for subscribing to resources.

On the client-side, we can send subscription requests when we make GET requests. This can be done by intercepting the REST GET request handler, and creating subscriptions.

dojo.connect(dojo, "xhrGet", function(args){
  dojox.cometd.subscribe(args.url, function(){
    // nothing needed, handled by the catch-all subscription
  });
});

All GET requests will then trigger a subscription automatically. One could easily add some conditional logic that only subscribes on a subset of requests.

The biggest disadvantage of client generated subscription requests is that they generate two requests for each resource access, one GET and one subscription request (as a POST). If the REST handler for a server can be modified to understand the subscription header that REST Channels adds to GET requests, the server can subscribe the client without requiring additional requests. The key to making this work, is to be able to connect cometD’s client connections with the request handler. One way to do this is to define a Bayeux handler that will put the current client object in the HTTP session, and then retrieve it from the session in the REST handler. The REST handler can then subscribe the client to the resource locator topic.

One tricky aspect of this approach is that it is possible for multiple client connections to exist in a single session. Each page will have its own cometD client connection, but will be in the same HTTP session. Therefore, the client connections need to be stored and accessed by their client id. In order for the REST handler to determine the correct client id for a client, you can have the Client-Id header set on the requests, and then this id can be used to find the correct Bayeux client connection. RestChannels automatically sets the Client-Id header, but the correct client id from the cometD handler must be assigned to the RestChannels clientId variable:

dojox.cometd.init("/cometd").addCallback(function(){
  dojox.rpc.Client.clientId = dojox.cometd.clientId;
});

Together these techniques can be used to combine a REST architecture with a cometD server for data notifications with Dojo’s REST capabilities in JsonRestStore, and complete integration into Dojo’s data event notification system.

Comments