The Many Shades of Bayeux/Cometd

by Greg WilkinsMay 15th, 2008

Cometd Bayeux is a publish/subscribe messaging system, and this has drawn some criticism that PubSub is not the most flexible paradigm for an Ajax/Comet transport. This article attempts to address that by showing the details of the Bayeux PubSub mechanism and the extensions that makes it easy and efficient to transport many styles of messaging.

PubSub

The classic Bayeux/Cometd example is chat, which is a perfect match for publish subscribe. Clients may join a chat room with a subscribe:

dojox.cometd.subscribe("/chat/room",myObject,myHandler);

and anybody can publish chat with:

dojox.cometd.publish("/chat/room",{from: "gregw", chat: "Hello cruel world"});

Request/Response

This communication paradigm is probably the most frequent paradigm used and the most familiar to developers. However it is not always the best choice for Ajax Comet applications, nor does it have a simple and efficient mapping to pure PubSub.

I say that request/response is not always the best choice, because developers can use it to create very chatty UIs that send many separate message to the server: getThis, getThat, doSomething, setAValue, etc. Batching can help reduce some of the chatter, but adds complexity. Often it is better to design your application communications as exchange of state message rather than RPC style request/response. However, even state message often needs to be done in a request/response style and pure PubSub still does not well support this.

The issue is that a client sending a request to the server does not want that request and/or published to all other clients that are subscribe to the channel. The pure PubSub response to this approach is to create per client channels with random names only know to those clients: the client subscribes for a response on the secret channel name and then publishes the request. While this works well enough, it is very wasteful of server resources and data structures and queues need to be created on the server side for each service for each channel, resulting in huge numbers of channels.

Thus Bayeux supports channels that are not published to non-server clients. The /meta/subscribe and /meta/unsubscribe channels are examples of such non publishing channels and messages sent by a client to these channels are never delivered to other non-server clients, even if they attempted to subscribe to those channels. All /meta/* channels are like this, but these are reserved for the use of the protocol. Bayeux also defines the /service/* channel space to be a non-publishing space. Messages (requests) published on /service/channels are only delivered to server side handlers and/or clients. Non-server clients may subscribe to these channels, but will only receive messages (responses) delivered explicitly to the client them by the server.

For example, if a chat room wanted the ability to show some chat history as soon as a member joined, then this could be implemented by the chat client subscribing to /service/chat and publishing a request message with some query (chat room name, time range of message wanted, max chats to send etc). e.g.:

dojox.cometd.subscribe("/service/chat",myObject,myHandler);
dojox.cometd.publish("/service/chat",{room: "/chat/room", max: 100});

A server side client would be subscribed to the /service/chat client to handle all such requests, and would handle them by running the query and preparing a response method that they would deliver to the requesting client (see dojox.cometd.Client.deliver(…) ).

YUCK! you say? Well I agree, there is still some issues and ugliness:

  • The subscribe is persistent and needs either to be unsubscribed or able to handle multiple responses
  • The responses for multiple request must be associated with the correct response and potentially a different callback
  • It doesn’t look like request/response! which is not a trivial matter.

But these are issues of the client API and not issues of the protocol. Bayeux service channels are capable of efficiently transporting the request and response and Bayeux has the ID mechanism required to correlate responses to requests. Client libraries should be able to present this mechanism as request/response and manage the short term callback and request/response ID matching required. This may be done either as a standardized API, or in an API style best suited to a particular framework.

This API facade over the services mechanism has already been done to some extend on the server side with the BayeuxService class (although that is not yet part of the “standard” dojox.cometd java server API). A similar packaging of service channels on the clients could well present this mechanism as pure request/response.

Request/Multiple-Response

The same /service mechanism can be used to support request/multi-response, where a single request message may result in multiple response messages sent to the client. The way the protocol transports the messages does not change, with the main difference is that the callback should not be unsubscribed after the first response. There may be more that can be done in the client API to support this mode.

Private Messages

Often with PubSub, developers feel the need to create a channel per user in order to deliver private messages to a client. For example, if a trading system wants to notify a user of completed trades, the temptation is to create a channel like /trades/a_user_id and each user will subscribe to their own channel. This approach works, but is not the most resource sensible way of solving this issue and requires security code to prevent unauthorized clients subscribing to other users channels.

Instead, the services channel space can be used together with the deliver API on the server. All clients subscribe to /service/trades and the server delivers and individual users trades to them with code like:

bayeux.getClient(clientId).deliver(serviceClient,"/service/trade",trades,null);

Analogy with IP

There have been calls for Bayeux to be simplified and/or replaced with a layered protocol suit. However, I would maintain that Bayeux is already layered and represents a minimal protocol suite analogous to the IP (as in TCP/IP or UDP/IP):

IP Bayeux
IP is a protocol that multiplexes packets over a common transport using IP addresses and ports numbers to route to the destination handler Bayeux is a protocol that multiplexes messages over a common transport using a channel name to route to the destination handler
IP has some broadcast addresses (eg 192.196.0.255) and some point to point addresses Bayeux has some broadcast channels and some non-broadcast channels (eg /service/foo)
IP uses DHCP protocol to allocate an IP address to a host Bayeux uses a /meta/handshake exchange to allocate a clientId to a client
IP uses routing protocols (eg RIP) to configure how packets are forwarded link to link Bayeux uses a /meta/subscribe exchange to configure how messages are routed to clients
IP can be extended with higher level protocols to support multiple communication paradigms: (eg TCP, RADIUS) Bayeux can be extended with APIs and protocols to support multiple communications paradigms

Conclusion

The basic communication paradigm of Bayeux is flexible and efficient. pub/sub can be viewed as the default routing algorithm for bayeux, but by use of the Client.deliver API and service channel, arbitrary message forwarding strategies can be developed. The current client APIs are very much written to be PubSub, so there is almost certainly more work that can be done in those APIs to support the commonly used communication paradigms.

More importantly, the cometd needs to do a much better job of documenting and advocating the features that it already has, so that the conversation about how it can be improved can be conducted from a knowledgeable basis. I believe the specification document needs to be refactored to breakout specific transports and to make it clear that PubSub is just the default routing mechanism. I have also discussed some extended routing features with Alex Russell that I think can be added to a cleaned up specification.

3 Responses to “The Many Shades of Bayeux/Cometd”

  1. anjan Says:

    Hi greg,

    I had worked on a CRM applicaiton that gets delivered using Java Webstart.
    Since, Webstart applications are just the same as normal desktop applicaitons capable of persistent connection to the server.
    IF webstart applicaitons could run on the browser wouldn’t it solve all the problems that comet is trying to solve??

    Well, I don’t expect swing applications to simply run on the browser like web applications- which would be ideal, but surely if we could just use the webstart technology as communication mechanism- like I used flash client for persistent socket connection, it would solve all the mighty problems that comet is trying to solve. On the serverside, even apache server can be configured to recognize JNLP extensions.

    I guess webstart technology as a replacement or a complimentary technology for comet misght be wishful thinking as I don’t possess enough depth to investigate into it and reach a conclusion.
    Of course the implication is that the client technology will be tied to Java and most probably the server side too. But surely there must be ways to work around this issue if indeed webstart is a viable option.

  2. GregWilkins Says:

    Anjan,

    running from the desktop avoids the 2 connection problem. But you would still need to write the comms layer in the same way as comet, so you can tunnel through firewalls.

    But then you are limited to devices that have a desktop you can write to (forget internet kiosks etc.) with java.

    The web became more popular that thick clients for internet applications even without 2 way comms. Comet builds on that success.

  3. Comet Daily » Blog Archive » Implementing a Bayeux to JMS Bridge Says:

    [...] that the ‘publish’ method supports request/response style interactions. Internally, the JMSClient object will add a randomly generated [...]


Copyright 2015 Comet Daily, LLC. All Rights Reserved