Bayeux: A JSON Protocol For Publish/Subscribe Event Delivery
Protocol Version: 0.1draft3
-------------------------------------------------------------------------------

THIS DOCUMENT IS NOW OUT OF DATE AND WILL SOON BE DELETED.
PLEASE SEE bayeux.html 

-------------------------------------------------------------------------------

Initial connection setup specifies to the system a message envelope type for
all communications on a particular channel, but inside this envelope a
normalized JSON structure is used to communicate with all endpoints.

The event router is channel based, with rules based on longest-prefix match
dispatching for registered listeners. The "/meta/*" channel is reserved for
communications with the event router itself (including connection setup,
tear-down, and re-connection), and all conformant clients and servers must
implement the following meta-channel  verbs:
	
	* handshake
	* connect
	* reconnect
	* disconnect
	* status
	* subscribe
	* unsubscribe
	* ping

All cometd /meta/ channel messages contain a protocol version number and all
messages generated from (or routed through) an event router contain a message
ID which is unique to the router. No randomness is required in these
identifiers although routers are expected to drop messages which they have
previously "seen".

// FIXME: is message dropping enforceable? Are we asking routers to keep a
// long-lived data structure of, potentially sparse, message IDs throught the
// router lifetime?

All cometd messages are encoded in JSON format. Individual message objects make
up the elements of the top-level array that comprises the root object of all
protocol transmissions. This is an example of the messages exchanged durring a
connection setup process:

	// from client, to server
	[
		{
			"channel":			"/meta/handshake",
			// all meta channel messages MUST contain the protocol version the
			// client expects
			"version":			0.1,
			// the oldest version of the protocol that this client will support
			"minimumVersion":	0.1,
			"supportedConnectionTypes":	["iframe", "flash", "http-polling"],
			// the authScheme is outside the realm of this specification and
			// provided here for illustration only. It's also optional.
			"authScheme":		"SHA1",
			// the authUser and authToken are optional and authScheme dependent
			"authUser":			"alex",
			"authToken":		"HASHJIBBERISH"
		}
		// servers MUST ignore other messages in the envelope should the first
		// be a handshake request
	]

	// NOTE: data should be POSTed with an encoding of
	// application/x-www-form-urlencoded, and the preceeding payload is
	// expected to be stored in the "message" parameter

	// from server, to client
	[
		{
			"channel":					"/meta/handshake",
			// preferred protocol version
			"version":					0.1,
			// the oldest version of the protocol that this server will support
			"minimumVersion":			0.1,
			"supportedConnectionTypes":	["iframe", "flash", "http-polling"],
			"clientId":					"SOME_UNIQUE_CLIENT_ID",
			"authSuccessful":			true,
			// authToken is auth scheme dependent and entirely optional
			"authToken":				"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY",
			// advice determines the client behavior on errors
			"advice":	{
				"reconnect": "retry", // one of "none", "retry", "handshake", "recover"

				// transport specializations of the top-level generalized
				// advice
				"transport": {
					"iframe": { },
					"flash": { },
					"http-polling": {
						// delay before reconnecting
						"interval": 5000 // ms
					}
				}
			}
		}
		// servers MUST send only a handshake message in response to a handshake request
	]

The "advice" property MUST be persisted by clients for the course of a
connection's lifetime and until reconnection failure or until being reset by
the server in a subsequent message. Advice properties that are
transport-specific over-ride those that are part of the top-level advice
object. If the server sends new advice to clients, old advice MUST be discarded
and only provisions in the new advice SHOULD be used for subsequent actions.

NOTE to implementers: servers SHOULD support publishing to topics without
explicit handshake. If authToken, authScheme, and authUser are provided and
valid, publishing to topics should be allowed from client that have not
previously connected or authenticated. A clientId may be generated through this
style of interaction but it has no semantic meaning and providing this clientId
for future transactions may result in failure.

	// finally, the client opens up a tunnel by creating a new iframe whose
	// initial contents were retreived from the following URL:
	//		http://blah.endpoint.com/cometd/?tunnelInit=iframe&domain=endpoint.com
	// this is an unfortunate workaround for some cross-domain intialization
	// issues with iframes. Ideally the "tunnelInit" block will never need to
	// be sent for most transports.
	//
	// here's what the client then POST's:

	//-----------------
	// CLIENT -> SERVER
	//-----------------
	[
		{
			"channel":	"/meta/connect",
			"clientId":	"SOME_UNIQUE_CLIENT_ID",
			"connectionType": "iframe",
			// optional
			"authToken":"SOME_NONCE_PREVIOUSLY_PROVIDED_BY_SERVER"
		}
		// , ...
	]

	// NOTE: data should be POSTed with an encoding of
	// application/x-www-form-urlencoded, and the preceeding payload is
	// expected to be stored in the "message" parameter

	// the server now replies with the preamble followed by any number of
	// messages encoded in the tunnel-specific envelope:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	<!-- begin preamble -->
	<html>
		<head>
			<title>Comet -- cleaning up web development</title>
			<script type="text/javascript">
				window.parent.cometd.deliver([
					{
						// user-sent data
						"channel":		"/meta/connect",
						"successful":	true,
						"error":		"",
						"connectionId":	"/meta/connections/26",
						"clientId":		"SOME_UNIQUE_CLIENT_ID",
						"timestamp":	"TimeAtServer",
						// optional
						"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY"
					}
				]);
			</script>
		</head>
		<body>
	<!-- end preamble -->
	<!-- begin envelope -->
			<script type="text/javascript">
				window.parent.cometd.deliver([
					{
						// user-sent data
						"data": {
							"someField":	["some", "random", "values"],
						},
						// the usual message meta-data
						"channel":		"/originating/channel",
						// event ID
						"id":			"slkjdlkj32",
						"timestamp":	"TimeAtServer",
						// optional meta-data
						"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY"
					},
					{
						"data": {
							"blah blah":	["more", "random", "values"],
						},
						// the usual message meta-data
						"channel":		"/originating/channel",
						// event ID
						"id":			"slkjdlkj31",
						"timestamp":	"TimeAtServer",
						"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY"
					}
					// , ...
				]);
			</script>
			<br><!-- insert 2K of whitespace here -->
	<!-- end envelope -->
	...
	<!-- begin signoff -->
			<script type="text/javascript">
				window.parent.cometd.tunnelCollapse();
			</script>
		</body>
	</html>
	<!-- end signoff -->
	
Upon connection, clients are implicitly subscribed to a connection-specific
channel located at:

	/meta/clients/[SOME_UNIQUE_CLIENT_ID]

The client ID is used in reconnection. The reconnect verb allows occasionally
connected clients and the posting of messages for pre-authenticated systems.

	// reconnection is very similar to initial connection:

	//-----------------
	// CLIENT -> SERVER
	//-----------------

	[
		{
			"channel":		"/meta/reconnect",
			"clientId":		"SOME_UNIQUE_CLIENT_ID",
			"timestamp":	"LastReceivedTimeAtServer",
			"id":			"LastReceivedMessageId"
			"connectionId":	"/meta/connections/26",
			"connectionType": "iframe", // FIXME: is this necessaray?
			// optional
			"authToken":	"SOME_NONCE_PREVIOUSLY_PROVIDED_BY_SERVER"
		}
		// , ...
	]


	// generally, the first message in the array of responded messages will
	// begin with:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/reconnect",
			"connectionId":	"/meta/connections/26",
			"successful":	true,
			// optional
			"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY"
		}
		// , ...
	]

	// if the server doesn't know anything about the client or is trying to
	// signal to clients that they need to somehow try harder before
	// reconnecting will work (e.g., in the case of server failover), it may
	// respond with:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/reconnect",
			"successful":	false,
			"advice": {
				"reconnect": "recover", // one of "none", "retry", "handshake", "recover"
			}
		}
	]

	// if, on the other hand, the reconnection succeeds, the client may not
	// return immediately, but the first message in the response array may look
	// like:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/reconnect",
			"successful":	true,
			// optional
			"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY"
		}
		// , ...
	]

	// the server might respond with advice noting some delay in reconnecting.
	// This is particularly important to prevent "reconnect floods". The above
	// message agumented with this advice might be:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/reconnect",
			"successful":	true,
			// optional
			"authToken":	"SOME_NONCE_THAT_NEEDS_TO_BE_PROVIDED_SUBSEQUENTLY",
			"advice": {
				"interval": 4000 // ms, or 4 seconds between reconnect attempts
			}
		}
		// , ...
	]

	// The rest of a conformant response looks almost identical to normal
	// preamble/envelope/signoff event delivery except that queued messages may
	// be delivered en-masse. We don't cover it here for the sake of brevity.

	// Subscribing and unsubscribing to channel is straightforward. Subscribing:

	//-----------------
	// CLIENT -> SERVER
	//-----------------

	[
		{
			"channel":		"/meta/subscribe",
			"subscription":	"/some/other/channel",
			// optional
			"authToken":	"SOME_NONCE_PREVIOUSLY_PROVIDED_BY_SERVER"
		}
		// , ...
	]

	// response to subscription:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/subscribe",
			"subscription":	"/some/other/channel",
			"successful":	true,
			"advice":		{
				"transport":	{
					retry: true, // or false
				}
			},
			"clientId":		"SOME_UNIQUE_CLIENT_ID",
			"error":		"",
			// optional
			"authToken":	"SOME_NONCE"
		}
		// , ...
	]

	// unsubscription for same:

	//-----------------
	// CLIENT -> SERVER
	//-----------------

	[
		{
			"channel":		"/meta/unsubscribe",
			"subscription":	"/some/other/channel",
			// optional
			"authToken":	"SOME_NONCE_PREVIOUSLY_PROVIDED_BY_SERVER"
		}
		// , ...
	]

	// and the server response:

	//-----------------
	// SERVER -> CLIENT
	//-----------------

	[
		{
			"channel":		"/meta/unsubscribe",
			"subscription":	"/some/other/channel",
			"successful":	true,
			"clientId":		"SOME_UNIQUE_CLIENT_ID",
			"error":		"",
			// optional
			"authToken":	"SOME_NONCE"
		}
		// , ...
	]
	
	// you should expect to receive sub / unsub events _at any time_,
	// an outside source could subscribe or unsubscribe you from channels

	// NOTE: the "status" message type may be used for introspection and channel
	// subscription queries

	// NOTE: the "/meta/ping" channel may be safely ignored by servers and
	// clients alike. It may be used as a keepalive mechanism.

	// FIXME: note which meta channel events require clientId

Conformat clients SHOULD implement a backoff algorithm should reconnect
attempts fail. While this algorithm is not specified here, an initial delay of
one second (1000ms) with an exponential increase is recommended.

The 0.1 version of the protocol does NOT acknowledge passing messages
beyond a single router connected to multiple clients. Future protocol versions
may include provisions for this.

Servers MAY deliver messages to a client in any successful response other than
the response to a handshake. For efficiency servers SHOULD use responses to 
send message requests in preference to a response to a Connect or Reconnect
that may be pending.

Servers SHOULD maintain event delivery queues for intermittently disconnected
clients. The protocol does not currently specify a way for clients to specify
their per-conection "age limit" nor does it specify a policy for server event
expiration, but server authors should be aware of the potential for resoruce
exhaustion if limits are not placed. Clients should not be written in such a
way as to expect significant backlogs of events will be available.

TODOC: when subscription happens, it's *clients* that are subscribed, not the
acting tunnels or connections.

FIXME: outline server timeout strategies and why it's not part of the protocol
FIXME: provide addendum for to define APIs of conformant clients
FIXME: define /meta/disconnect, /meta/status, and /meta/ping

-------------------------------------------------------------------------------
Appendix 1: Advices

The advice system provides a way for servers to inform clients of their
preferred mode of client operation. In conjunction with server-enforced limits,
Bayeux implementations can prevent resource exhaustion and inelegant failure in
several important edge cases. Since transport evenlopes are "pluggable" in
order to support differing "on the wire" behaviors, advice values may also vary
by transport type, even for the same conditions. What follows is a breif
description of the enumerations that are used in advices and their canonical
meanings.

	* reconnect
		type: string
		values: "none", "retry", "handshake", "recover"
		description:
			* none
				hard failure for the reconnect attempt. Do not attempt to
				reconnect at all. 
			* retry
				attempt to reconnect again later (as defined by "interval"
				advice or client-default backoff) with the same credentials
			* handshake
				the server doesn't know anything about the client or connection
				that were passed, so instead of attempting to reconnect, throw
				away the client's authentication information and attempt to
				reauthorize with a /meta/handshake
			* recover
				a superset of the "handshake" advice, this tells clients that
				not only should they assume that they need to re-authentiate,
				they also need to resubscribe to all the topics they are
				interested in. This advice may be provided to servers who
				completely fail to locate any matching authentication or
				authorization data regarding the client, e.g. in the case of
				hard failover between a bayeux server and a cold spare.
	
	* interval
		type: integer
		values: no default
		description: 
			a number, in milliseconds, for a client to delay subsequent
			reconnect attempts

-------------------------------------------------------------------------------
Authors:
	Alex Russell <alex@dojotoolkit.org>, SitePen Inc.
	Greg Wilkins <gregw@webtide.com>, Webtide
	David Davis <xantus@cometd.com>, Six Apart


# vim:ts=4:noet:
