Commits

Chris Perl committed 7a3eec7

Moving all message stuff out into separate file

Comments (0)

Files changed (2)

-import uuid
-import json
 import threading
 import urlparse
 import time
-import random
-import string
 
 from httplib import HTTPConnection
 
-# Bayeux Spec
-# http://svn.cometd.com/trunk/bayeux/bayeux.html
-
-# Exception classes
-class InvalidBayeuxMessage(Exception):
-	pass
-
-class BayeuxProtocolError(Exception):
-	pass
-
-class BayeuxServerError(Exception):
-	pass
-
-class BayeuxError(Exception):
-	pass
-
-# Bayeux Message Classes
-class RequestMessageFactory(object):
-	"""
-	A factory class to generate a request BayeuxMessage based on type.
-
-	This Factory can be used for one of two things.  It can either generate
-	messages suitable for use by a client, or it can take a dictionary and
-	generate a message from that dictionary, having the appropriate class
-	validate all then necessary fields are there (as might be used by a
-	server).
-	"""
-	def __new__(cls, type=None, *args, **kwargs):
-		if   type == BayeuxRequestMessage.HANDSHAKE:
-			return HandshakeRequest()
-		elif type == BayeuxRequestMessage.CONNECT:
-			return ConnectRequest(*args, **kwargs)
-		elif type == BayeuxRequestMessage.DISCONNECT:
-			return DisconnectRequest(*args, **kwargs)
-		elif type == BayeuxRequestMessage.SUBSCRIBE:
-			return SubscribeRequest(*args, **kwargs)
-		elif type == BayeuxRequestMessage.UNSUBSCRIBE:
-			return UnsubscribeRequest(*args, **kwargs)
-		elif type == BayeuxRequestMessage.PUBLISH:
-			return PublishRequest(*args, **kwargs)
-		elif type == BayeuxRequestMessage.FROMDICT:
-			d = args[0]
-			channel = d.get('channel')
-			if   channel == "/meta/handshake":
-				return HandshakeRequest(d)
-			elif channel == "/meta/connect":
-				return ConnectRequest(d)
-			elif channel == "/meta/disconnect":
-				return DisconnectRequest(d)
-			elif channel == "/meta/subscribe":
-				return SubscribeRequest(d)
-			elif channel == "/meta/unsubscribe":
-				return UnsubscribeRequest(d)
-			else:
-				return PublishRequest(d)
-		else:
-			raise InvalidBayeuxMessage()
-
-class ResponseMessageFactory(object):
-	"""
-	A factory class to generate a response BayeuxMessage from a dict.
-
-	This factory can be used for one of two things.  It can create response
-	messages or it can instantiate response messages from a dictionary.
-	"""
-	def __new__(cls, type=None, *args, **kwargs):
-		if   type == BayeuxResponseMessage.HANDSHAKE:
-			return HandshakeResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.CONNECT:
-			return ConnectResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.DISCONNECT:
-			return DisconnectResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.SUBSCRIBE:
-			return SubscribeResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.UNSUBSCRIBE:
-			return UnsubscribeResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.PUBLISH:
-			return PublishResponse(*args, **kwargs)
-		elif type == BayeuxResponseMessage.EVENT:
-			return EventMessage(*args, **kwargs)
-		elif type == BayeuxResponseMessage.FROMDICT:
-			d = args[0]
-			channel = d.get('channel')
-			data = d.get('data')
-			if   channel == "/meta/handshake":
-				return HandshakeResponse(d)
-			elif channel == "/meta/connect":
-				return ConnectResponse(d)
-			elif channel == "/meta/disconnect":
-				return DisconnectResponse(d)
-			elif channel == "/meta/subscribe":
-				return SubscribeResponse(d)
-			elif channel == "/meta/unsubscribe":
-				return UnsubscribeResponse(d)
-			elif data is None:
-				# publish response messages look kinda like event messages, but have no "data"
-				return PublishResponse(d)
-			else:
-				# a message for a channel we have subscribed to
-				return EventMessage(d)
-		else:
-			raise InvalidBayeuxMessage()
-
-class BayeuxMessage(object):
-	"""
-	A general representation of a bayeux message.
-	"""
-	def __init__(self):
-		self._message = None
-
-	def _init_from_dict(self, d):
-		"""
-		Initialize the BayeuxMessage from dictionary d.
-
-		In addition, check that we have all required fields for this message, if we
-		dont, someone is not following the protocol.
-		"""
-		self._message = d
-		for k, v in self._message.items():
-			setattr(self, k, v)
-		for key in self.required_fields:
-			if not self._message.has_key(key):
-				raise BayeuxProtocolError("%s missing required field: '%s'; %s" % (self.__class__.__name__, key, self))
-
-	def __getattr__(self, name):
-		"""
-		Override __getattr__ so that accessing a non-existent attribute returns None
-		and doesn't raise an exception.
-		"""
-		if not self.__dict__.has_key(name):
-			return None
-
-	def __str__(self):
-		"""
-		Return a string representation of a bayeux message.
-		"""
-		assert self._message
-		return "Bayeux Message: %s" % (json.dumps(self._message))
-
-	def as_json(self):
-		"""
-		Return the message as a json encoded string.
-		"""
-		return json.dumps(self._message)
-
-	def as_dict(self):
-		"""
-		Return the message as a python dictionary.
-		"""
-		return self._message
-
-
-# Request Messages
-class BayeuxRequestMessage(BayeuxMessage):
-	"""
-	Base class for request messages.
-	"""
-	HANDSHAKE   = 0
-	CONNECT     = 1
-	DISCONNECT  = 2
-	SUBSCRIBE   = 3
-	UNSUBSCRIBE = 4
-	PUBLISH     = 5
-	FROMDICT    = 6
-
-class HandshakeRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message used to start the protocol with the server.
-	"""
-	def __init__(self, d=None):
-		"""
-		Initialize a handshake request.
-
-		If d is None, create a fresh HandshakeRequest for use.  If d is
-		a not None, then it will be a dictionary that should be used to
-		create this HandshakeRequest.
-
-		Required Fields:
-		  channel: Must have a value of "/meta/handshake"
-		  version: The version of the protocol that we are using
-		  supportedConnectionTypes: An array of the connection types that we support
-
-		Optional Fields:
-		  minimumVersion: The oldes protocol that we support
-		  ext: Used for extensions and such, not part of the protocol proper
-		  id:  A unique id that may be included with bayeux messages
-		"""
-		self.required_fields = ( 'channel', 'version', 'supportedConnectionTypes' )
-		if d is None:
-			# Camel case for attributes violates PEP 8, but it really makes
-			# life much easier if the attribute names match those name that
-			# are actually being passed around in the protocol.
-			d = {
-				'channel': "/meta/handshake",
-				'id'     : str(uuid.uuid4()),
-				'version': "1.0",
-				'supportedConnectionTypes': [ "long-polling" ],
-			}
-		self._init_from_dict(d)
-
-class ConnectRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message sent to the server after a successful handshake.  In
-	addition, this message is sent when we are polling for messages.
-	"""
-	def __init__(self, d=None, clientId=None):
-		"""
-		Initialzie the connect message.
-
-		Required Fields:
-		  * channel: Must have a value of "/meta/connect"
-		  * clientId: The value returned from a successful HandshakeRequest
-		  * connectionType: The type of connection we are using for this request
-
-		Optional Fields:
-		  * ext: Used for protocol extensions
-		  * id: A unique identifier for the message
-
-		@clientId (string) : The clientId returned from a successful HandshakeRequest
-		"""
-		self.required_fields = ( 'channel', 'clientId', 'connectionType' )
-		if d is None:
-			assert clientId, "You must supply a clientId for a ConnectRequest message"
-			d = {
-				'channel': "/meta/connect",
-				'connectionType': "long-polling",
-				'id': str(uuid.uuid4()),
-				'clientId': clientId,
-			}
-		self._init_from_dict(d)
-
-class DisconnectRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message sent to terminate a connection.
-	"""
-	def __init__(self, d=None, clientId=None):
-		"""
-		Initialize the DisconnectRequest.
-
-		Required Fields:
-		  * channel: Must have a value of "/meta/disconnect"
-		  * clientId: The clientId returned from the handshake
-
-		Optional Fields:
-		  * ext: Used for protocol extensions
-		  * id: A unique identifier for the message
-		"""
-		self.required_fields = ( 'channel', 'clientId' )
-		if d is None:
-			assert clientId, "You must supply a clientId for a DisconnectRequest message"
-			d = {
-				'id':  str(uuid.uuid4()),
-				'channel': "/meta/disconnect",
-				'clientId': clientId,
-			}
-		self._init_from_dict(d)
-
-class SubscribeRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message to subscribe to a channel.
-	"""
-	def __init__(self, d=None, clientId=None, subscription=None):
-		"""
-		A connected Bayeux client may send subscribe messages to register
-		interest in a channel and to request that messages published to that
-		channel are delivered to itself.
-
-		Required Fields:
-		  * channel: Must have a value of "/meta/subscribe"
-		  * clientId: The clientId from the handshake
-		  * subscription: channel name or a channel pattern or an array
-			          of channel names and channel patterns
-
-		Optional Fields:
-		  * ext: Same as above
-		  * id: A unique identifier for this message
-		"""
-		self.required_fields = ( 'channel', 'clientId', 'subscription' )
-		if d is None:
-			assert clientId, "You must supply a clientId for a SubscribeRequest message"
-			assert subscription, "You must supply a channel name or pattern to subscribe to"
-			# TODO: Validate subscription is a valid channel, channel pattern or array of either
-			d = {
-				'id': str(uuid.uuid4()),
-				'channel': "/meta/subscribe",
-				'clientId': clientId,
-				'subscription': subscription,
-			}
-		self._init_from_dict(d)
-
-class UnsubscribeRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message to unsubscribe from a channel.
-	"""
-	def __init__(self, d=None, clientId=None, subscription=None):
-		"""
-		A connected Bayeux client may send unsubscribe messages to
-		cancel interest in a channel and to request that messages published to that
-		channel are not delivered to itself.
-
-		Required Fields:
-		  * channel: Must have a value of "/meta/unsubscribe"
-		  * clientId: The clientId from the handshake
-		  * subscription: The channel name to unsubscribe from
-
-		Optional Fields:
-		  * ext: Same as above
-		  * id: A unique identifier for this message
-		"""
-		self.required_fields = ( 'channel', 'clientId', 'subscription' )
-		if d is None:
-			assert clientId, "You must supply a clientId for an UnsubscribeRequest message"
-			assert subscription, "You must supply a channel name or pattern to unsubscribe from"
-			d = {
-				'id': str(uuid.uuid4()),
-				'channel': "/meta/unsubscribe",
-				'clientId': clientId,
-				'subscription': subscription,
-			}
-		self._init_from_dict(d)
-
-class PublishRequest(BayeuxRequestMessage):
-	"""
-	The bayeux message to publish data to a channel.
-	"""
-	def __init__(self, d=None, clientId=None, channel=None, data=None):
-		"""
-		A Bayeux client can publish events on a channel by sending
-		event messages. An event message MAY be sent in new HTTP request or it MAY be
-		sent in the same HTTP request as any message other than a handshake meta
-		message.
-
-		A publish message MAY be sent from an unconnected client (that has not
-		performed handshaking and thus does not have a client ID). It is OPTIONAL for a
-		server to accept unconnected publish requests and they should apply server
-		specific authentication and authorization before doing so.
-
-		Required Fields:
-		  * channel: The channel to publish the data to
-		  * data: Some application string or json encoded object
-
-		Optional Fields:
-		  * clientId: The client ID returned in the handshake response
-		  * id: A unique ID for the message generated by the client
-		  * ext: Same as above
-		"""
-		self.required_fields = ( 'channel', 'data' )
-		if d is None:
-			assert clientId, "You must supply a clientId for a PublishRequest message"
-			assert channel, "You must supply a channel for a PublishRequest message"
-			assert data, "You must supply the data for a PublishRequest message"
-			d = {
-				'id': str(uuid.uuid4()),
-				'channel': channel,
-				'data': data,
-				'clientId': clientId,
-			}
-		self._init_from_dict(d)
-
-# Response Messages
-class BayeuxResponseMessage(BayeuxMessage):
-	"""
-	Base class for response messages.
-	"""
-	HANDSHAKE   = 0
-	CONNECT     = 1
-	DISCONNECT  = 2
-	SUBSCRIBE   = 3
-	UNSUBSCRIBE = 4
-	PUBLISH     = 5
-	EVENT       = 6
-	FROMDICT    = 7
-
-class HandshakeResponse(BayeuxResponseMessage):
-	"""
-	The bayeux message sent in reply to a HandshakeRequest.
-
-	Required Fields:
-	  * channel: Must have a value of "/meta/handshake"
-	  * version: The version of the protocol that we are using
-	  * supportedConnectionTypes: The connection types supported by the server.  This
-		may be a subset of the types we sent in our HandshakeRequest, but it MUST
-		contian at least one element from our list OR it MUST be unsuccessful
-	  * clientId: A newly generated unique id for our client
-	  * successful: A field indicating success or failure of the HandshakeRequest
-
-	Optional Fields:
-	  * minimumVersion: The minimum version of the protocol that this server supports
-	  * advice: Various bits of advice about reconnecting, timeouts and such
-	  * ext: Used for protocol extensions
-	  * id: This will be the exact same unique value from the HandshakeRequest,
-		assuming we sent one.
-	  * authSuccessful: Used with implementations that use authentication?
-
-	@d (dict) : A dictionary created by loading some json
-	"""
-	def __init__(self, d=None, successful=True, id=None, advice=None):
-		self.required_fields = ('channel', 'version', 'supportedConnectionTypes', 'clientId', 'successful')
-		if d is None:
-			d = {
-				'channel': "/meta/handshake",
-				'version': "1.0",
-				'supportedConnectionTypes': [ "long-polling" ],
-				'clientId': ''.join([random.choice(string.ascii_letters + string.digits) for i in range(30)]),
-				'successful': True,
-			}
-			if id is not None:
-				d['id'] = id
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
-
-class ConnectResponse(BayeuxResponseMessage):
-	"""
-	The bayeux message sent in response to a ConnectRequest.
-
-	Required Fields:
-	  * channel: Must have a value of "/meta/connect"
-	  * clientId: The clientId we passed in the ConnectRequest
-	  * successful: Boolean indicating success or failure of the connection
-
-	Optional Fields:
-	  * error: An error if the request was unsuccessful
-	  * advice: Various bits of advice about reconnecting etc
-	  * ext: Used for protocol extensions
-	  * id: The same unique value sent in the ConnectRequest
-
-	@d (dict) : A dictonary created by loading some json returned from the server
-	"""
-	def __init__(self, d=None, clientId=None, successful=True, id=None, advice=None):
-		# self.required_fields = ('channel', 'clientId', 'successful')
-		# cometd within jetty doesn't appear to be doing to the protocol correctly
-		self.required_fields = ('channel', 'successful')
-		if d is None:
-			assert clientId, "You must supply a clientId for a ConnectResponse message"
-			d = {
-				'channel': "/meta/connect",
-				'clientId': clientId,
-				'successful': successful,
-			}
-			if id is not None:
-				d['id'] = id
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
-
-class DisconnectResponse(BayeuxResponseMessage):
-	"""
-	The bayuex message sent in response to DisconnectRequest.
-
-	Required Fields:
-	  * channel: Must have a value of "/meta/disconnect"
-	  * clientId: The clientId from the handshake
-	  * successful: Boolean indicating success or failure of the disconnect
-
-	Optional Fields:
-	  * error: An error if the disconnect was unsuccessful
-	  * ext: Used for protocol extensions
-	  * id: Unique identifier from the DisconnectRequest
-
-	@d (dict) : A dictionary representation of a bayeux message
-	"""
-	def __init__(self, d=None, clientId=None, successful=None, id=None):
-		# self.required_fields = ('channel', 'clientId', 'successful')
-		# cometd within jetty doesn't appear to be doing to the protocol correctly
-		self.required_fields = ('channel', 'successful')
-		if d is None:
-			assert clientId, "You must supply a clientId for a DisconnectResponse message"
-			assert successful, "You must supply whether the message indicates success or failure"
-			d = {
-				'channel': "/meta/disconnect",
-				'clientId': clientId,
-				'successful': successful,
-			}
-			if id is not None:
-				d['id'] = id
-		self._init_from_dict(d)
-
-class SubscribeResponse(BayeuxResponseMessage):
-	"""
-	The bayeux message sent in response to a subscribe request.
-
-	Required Fields:
-	  * channel: Must have a value of "/meta/subscribe"
-	  * clientId: The clientId from the handshake
-	  * successful: Boolean indicating success or failure of the subscribe
-	  * subscription: Channel name, pattern or array thereof
-
-	Optional Fields:
-	  * error: An error if the subscribe was unsuccessful
-	  * ext: Used for protocol extensions
-	  * id: The same unique id passed in the subscribe request
-	  * advice: Hints on timeouts and retrys
-	  * timestamp: ISO 8601 (all times SHOULD be sent in GMT time) i.e. YYYY-MM-DDThh:mm:ss.ss
-
-	@d (dict) : A dictionary representation of a bayeux message
-	"""
-	def __init__(self, d=None, clientId=None, successful=None, subscription=None, id=None, advice=None):
-		# self.required_fields = ('channel', 'clientId', 'successful', 'subscription')
-		# cometd within jetty doesn't appear to be doing to the protocol correctly
-		self.required_fields = ('channel', 'successful', 'subscription')
-		if d is None:
-			assert clientId, "You must supply a clientId for a SubscribeResponse message"
-			assert successful, "You must supply whether the message indicates success or failure"
-			assert subscription, "You must supply the subscription for a SubscribeResponse"
-			d = {
-				'channel': "/meta/subscribe",
-				'clientId': clientId,
-				'successful': successful,
-				'subscription': subscription,
-			}
-			if id is not None:
-				d['id'] = id
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
-
-class UnsubscribeResponse(BayeuxResponseMessage):
-	"""
-	The bayeux message sent in response to an unsubscribe request.
-
-	Required Fields:
-	  * channel: Must have a value of "/meta/unsubscribe"
-	  * successful: Boolean indicating success or failure
-	  * clientId: The clientId from the handshake
-	  * subscription: A channel name or a channel pattern or an array of channel names and channel patterns
-
-	Optional Fields:
-	  * error
-	  * advice
-	  * ext
-	  * id
-	  * timestamp
-
-	@d (dict) : A dictionary representation of a bayeux message
-	"""
-	def __init__(self, d=None, clientId=None, successful=None, subscription=None, id=None, advice=None):
-		# self.required_fields = ('channel', 'successful', 'clientId', 'subscription')
-		# cometd within jetty doesn't appear to be doing to the protocol correctly
-		self.required_fields = ('channel', 'successful', 'subscription')
-		if d is None:
-			assert clientId, "You must supply a clientId for an UnsubscribeResponse message"
-			assert successful, "You must supply whether the message indicates success or failure"
-			assert subscription, "You must supply the subscription for a UnsubscribeResponse"
-			d = {
-				'channel': "/meta/unsubscribe",
-				'clientId': clientId,
-				'successful': successful,
-				'subscription': subscription,
-			}
-			if id is not None:
-				d['id'] = id
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
-
-class PublishResponse(BayeuxResponseMessage):
-	"""
-	The bayeux message sent in response to a publish request.
-
-	A Bayeux server MAY respond to a publish event message with a publish
-	event acknowlegement.
-
-	Required Fields:
-	  * channel: The same channel as the publish request
-	  * successful: boolean indicating the success or failure
-
-	Optional Fields:
-	  * id
-	  * error
-	  * ext
-
-	@d (dict) : A dictionary representation of a bayeux message
-	"""
-	def __init__(self, d=None, channel=None, successful=None, id=None, advice=None):
-		self.required_fields = ('channel', 'successful')
-		if d is None:
-			assert channel, "You must supply the channel for a PublishResponse"
-			assert successful, "You must supply whether the message indicates success or failure"
-			d = {
-				'channel': channel,
-				'successful': successful,
-			}
-			if id is not None:
-				d['id'] = id
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
-
-# Remote event message (still treated like a response though, since it
-# is in response to our polling "connect" messages)
-class EventMessage(BayeuxResponseMessage):
-	"""
-	This class represents an event message (delivered from the server to the client).
-
-	Event messages MUST be delivered to clients if the client is
-	subscribed to the channel of the event message. Event messages
-	MAY be sent to the client in the same HTTP response as any
-	other message other than a meta handshake response.  If a
-	Bayeux server has multiple HTTP requests from the same client,
-	the server SHOULD deliver all available messages in the HTTP
-	response that will be sent immediately in preference to waking
-	a waiting connect meta message request.  Event message delivery
-	MAY not acknowledged by the client.
-
-	Required Fields:
-	  * channel: The channel you are subscribed to that this message is for
-	  * data: An arbitrary json encoded object that is the payload of the message
-
-	Optional Fields:
-	  * id: Unique message id from the publisher
-	  * clientId: The clientId from the handshake
-	  * advice: Same as above
-	  * ext: Same as above
-
-	@d (dict) : A dictionary representation of a bayeux message
-	"""
-	def __init__(self, d=None, channel=None, data=None, id=None, clientId=None, advice=None):
-		self.required_fields = ('channel', 'data')
-		if d is None:
-			assert channel, "You must supply the channel for an EventMessage"
-			assert data, "You must supply the data for an EventMessage"
-			d = {
-				'channel': channel,
-				'data': data,
-			}
-			if id is not None:
-				d['id'] = id
-			if clientId is not None:
-				d['clientId'] = clientId
-			if advice is not None:
-				assert isinstance(advice, dict), "advice must be a dictionary"
-				d['advice'] = advice
-		self._init_from_dict(d)
+from bayeuxmessage import *
 
 class BayeuxReceiver(threading.Thread):
 	"""
+import json
+import uuid
+import random
+import string
+
+# Bayeux Spec
+# http://svn.cometd.com/trunk/bayeux/bayeux.html
+
+# Exception classes
+class InvalidBayeuxMessage(Exception):
+	pass
+
+class BayeuxProtocolError(Exception):
+	pass
+
+class BayeuxServerError(Exception):
+	pass
+
+class BayeuxError(Exception):
+	pass
+
+# Bayeux Message Classes
+class RequestMessageFactory(object):
+	"""
+	A factory class to generate a request BayeuxMessage based on type.
+
+	This Factory can be used for one of two things.  It can either generate
+	messages suitable for use by a client, or it can take a dictionary and
+	generate a message from that dictionary, having the appropriate class
+	validate all then necessary fields are there (as might be used by a
+	server).
+	"""
+	def __new__(cls, type=None, *args, **kwargs):
+		if   type == BayeuxRequestMessage.HANDSHAKE:
+			return HandshakeRequest()
+		elif type == BayeuxRequestMessage.CONNECT:
+			return ConnectRequest(*args, **kwargs)
+		elif type == BayeuxRequestMessage.DISCONNECT:
+			return DisconnectRequest(*args, **kwargs)
+		elif type == BayeuxRequestMessage.SUBSCRIBE:
+			return SubscribeRequest(*args, **kwargs)
+		elif type == BayeuxRequestMessage.UNSUBSCRIBE:
+			return UnsubscribeRequest(*args, **kwargs)
+		elif type == BayeuxRequestMessage.PUBLISH:
+			return PublishRequest(*args, **kwargs)
+		elif type == BayeuxRequestMessage.FROMDICT:
+			d = args[0]
+			channel = d.get('channel')
+			if   channel == "/meta/handshake":
+				return HandshakeRequest(d)
+			elif channel == "/meta/connect":
+				return ConnectRequest(d)
+			elif channel == "/meta/disconnect":
+				return DisconnectRequest(d)
+			elif channel == "/meta/subscribe":
+				return SubscribeRequest(d)
+			elif channel == "/meta/unsubscribe":
+				return UnsubscribeRequest(d)
+			else:
+				return PublishRequest(d)
+		else:
+			raise InvalidBayeuxMessage()
+
+class ResponseMessageFactory(object):
+	"""
+	A factory class to generate a response BayeuxMessage from a dict.
+
+	This factory can be used for one of two things.  It can create response
+	messages or it can instantiate response messages from a dictionary.
+	"""
+	def __new__(cls, type=None, *args, **kwargs):
+		if   type == BayeuxResponseMessage.HANDSHAKE:
+			return HandshakeResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.CONNECT:
+			return ConnectResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.DISCONNECT:
+			return DisconnectResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.SUBSCRIBE:
+			return SubscribeResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.UNSUBSCRIBE:
+			return UnsubscribeResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.PUBLISH:
+			return PublishResponse(*args, **kwargs)
+		elif type == BayeuxResponseMessage.EVENT:
+			return EventMessage(*args, **kwargs)
+		elif type == BayeuxResponseMessage.FROMDICT:
+			d = args[0]
+			channel = d.get('channel')
+			data = d.get('data')
+			if   channel == "/meta/handshake":
+				return HandshakeResponse(d)
+			elif channel == "/meta/connect":
+				return ConnectResponse(d)
+			elif channel == "/meta/disconnect":
+				return DisconnectResponse(d)
+			elif channel == "/meta/subscribe":
+				return SubscribeResponse(d)
+			elif channel == "/meta/unsubscribe":
+				return UnsubscribeResponse(d)
+			elif data is None:
+				# publish response messages look kinda like event messages, but have no "data"
+				return PublishResponse(d)
+			else:
+				# a message for a channel we have subscribed to
+				return EventMessage(d)
+		else:
+			raise InvalidBayeuxMessage()
+
+class BayeuxMessage(object):
+	"""
+	A general representation of a bayeux message.
+	"""
+	def __init__(self):
+		self._message = None
+
+	def _init_from_dict(self, d):
+		"""
+		Initialize the BayeuxMessage from dictionary d.
+
+		In addition, check that we have all required fields for this message, if we
+		dont, someone is not following the protocol.
+		"""
+		self._message = d
+		for k, v in self._message.items():
+			setattr(self, k, v)
+		for key in self.required_fields:
+			if not self._message.has_key(key):
+				raise BayeuxProtocolError("%s missing required field: '%s'; %s" % (self.__class__.__name__, key, self))
+
+	def __getattr__(self, name):
+		"""
+		Override __getattr__ so that accessing a non-existent attribute returns None
+		and doesn't raise an exception.
+		"""
+		if not self.__dict__.has_key(name):
+			return None
+
+	def __str__(self):
+		"""
+		Return a string representation of a bayeux message.
+		"""
+		assert self._message
+		return "Bayeux Message: %s" % (json.dumps(self._message))
+
+	def as_json(self):
+		"""
+		Return the message as a json encoded string.
+		"""
+		return json.dumps(self._message)
+
+	def as_dict(self):
+		"""
+		Return the message as a python dictionary.
+		"""
+		return self._message
+
+
+# Request Messages
+class BayeuxRequestMessage(BayeuxMessage):
+	"""
+	Base class for request messages.
+	"""
+	HANDSHAKE   = 0
+	CONNECT     = 1
+	DISCONNECT  = 2
+	SUBSCRIBE   = 3
+	UNSUBSCRIBE = 4
+	PUBLISH     = 5
+	FROMDICT    = 6
+
+class HandshakeRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message used to start the protocol with the server.
+	"""
+	def __init__(self, d=None):
+		"""
+		Initialize a handshake request.
+
+		If d is None, create a fresh HandshakeRequest for use.  If d is
+		a not None, then it will be a dictionary that should be used to
+		create this HandshakeRequest.
+
+		Required Fields:
+		  channel: Must have a value of "/meta/handshake"
+		  version: The version of the protocol that we are using
+		  supportedConnectionTypes: An array of the connection types that we support
+
+		Optional Fields:
+		  minimumVersion: The oldes protocol that we support
+		  ext: Used for extensions and such, not part of the protocol proper
+		  id:  A unique id that may be included with bayeux messages
+		"""
+		self.required_fields = ( 'channel', 'version', 'supportedConnectionTypes' )
+		if d is None:
+			# Camel case for attributes violates PEP 8, but it really makes
+			# life much easier if the attribute names match those name that
+			# are actually being passed around in the protocol.
+			d = {
+				'channel': "/meta/handshake",
+				'id'     : str(uuid.uuid4()),
+				'version': "1.0",
+				'supportedConnectionTypes': [ "long-polling" ],
+			}
+		self._init_from_dict(d)
+
+class ConnectRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message sent to the server after a successful handshake.  In
+	addition, this message is sent when we are polling for messages.
+	"""
+	def __init__(self, d=None, clientId=None):
+		"""
+		Initialzie the connect message.
+
+		Required Fields:
+		  * channel: Must have a value of "/meta/connect"
+		  * clientId: The value returned from a successful HandshakeRequest
+		  * connectionType: The type of connection we are using for this request
+
+		Optional Fields:
+		  * ext: Used for protocol extensions
+		  * id: A unique identifier for the message
+
+		@clientId (string) : The clientId returned from a successful HandshakeRequest
+		"""
+		self.required_fields = ( 'channel', 'clientId', 'connectionType' )
+		if d is None:
+			assert clientId, "You must supply a clientId for a ConnectRequest message"
+			d = {
+				'channel': "/meta/connect",
+				'connectionType': "long-polling",
+				'id': str(uuid.uuid4()),
+				'clientId': clientId,
+			}
+		self._init_from_dict(d)
+
+class DisconnectRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message sent to terminate a connection.
+	"""
+	def __init__(self, d=None, clientId=None):
+		"""
+		Initialize the DisconnectRequest.
+
+		Required Fields:
+		  * channel: Must have a value of "/meta/disconnect"
+		  * clientId: The clientId returned from the handshake
+
+		Optional Fields:
+		  * ext: Used for protocol extensions
+		  * id: A unique identifier for the message
+		"""
+		self.required_fields = ( 'channel', 'clientId' )
+		if d is None:
+			assert clientId, "You must supply a clientId for a DisconnectRequest message"
+			d = {
+				'id':  str(uuid.uuid4()),
+				'channel': "/meta/disconnect",
+				'clientId': clientId,
+			}
+		self._init_from_dict(d)
+
+class SubscribeRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message to subscribe to a channel.
+	"""
+	def __init__(self, d=None, clientId=None, subscription=None):
+		"""
+		A connected Bayeux client may send subscribe messages to register
+		interest in a channel and to request that messages published to that
+		channel are delivered to itself.
+
+		Required Fields:
+		  * channel: Must have a value of "/meta/subscribe"
+		  * clientId: The clientId from the handshake
+		  * subscription: channel name or a channel pattern or an array
+			          of channel names and channel patterns
+
+		Optional Fields:
+		  * ext: Same as above
+		  * id: A unique identifier for this message
+		"""
+		self.required_fields = ( 'channel', 'clientId', 'subscription' )
+		if d is None:
+			assert clientId, "You must supply a clientId for a SubscribeRequest message"
+			assert subscription, "You must supply a channel name or pattern to subscribe to"
+			# TODO: Validate subscription is a valid channel, channel pattern or array of either
+			d = {
+				'id': str(uuid.uuid4()),
+				'channel': "/meta/subscribe",
+				'clientId': clientId,
+				'subscription': subscription,
+			}
+		self._init_from_dict(d)
+
+class UnsubscribeRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message to unsubscribe from a channel.
+	"""
+	def __init__(self, d=None, clientId=None, subscription=None):
+		"""
+		A connected Bayeux client may send unsubscribe messages to
+		cancel interest in a channel and to request that messages published to that
+		channel are not delivered to itself.
+
+		Required Fields:
+		  * channel: Must have a value of "/meta/unsubscribe"
+		  * clientId: The clientId from the handshake
+		  * subscription: The channel name to unsubscribe from
+
+		Optional Fields:
+		  * ext: Same as above
+		  * id: A unique identifier for this message
+		"""
+		self.required_fields = ( 'channel', 'clientId', 'subscription' )
+		if d is None:
+			assert clientId, "You must supply a clientId for an UnsubscribeRequest message"
+			assert subscription, "You must supply a channel name or pattern to unsubscribe from"
+			d = {
+				'id': str(uuid.uuid4()),
+				'channel': "/meta/unsubscribe",
+				'clientId': clientId,
+				'subscription': subscription,
+			}
+		self._init_from_dict(d)
+
+class PublishRequest(BayeuxRequestMessage):
+	"""
+	The bayeux message to publish data to a channel.
+	"""
+	def __init__(self, d=None, clientId=None, channel=None, data=None):
+		"""
+		A Bayeux client can publish events on a channel by sending
+		event messages. An event message MAY be sent in new HTTP request or it MAY be
+		sent in the same HTTP request as any message other than a handshake meta
+		message.
+
+		A publish message MAY be sent from an unconnected client (that has not
+		performed handshaking and thus does not have a client ID). It is OPTIONAL for a
+		server to accept unconnected publish requests and they should apply server
+		specific authentication and authorization before doing so.
+
+		Required Fields:
+		  * channel: The channel to publish the data to
+		  * data: Some application string or json encoded object
+
+		Optional Fields:
+		  * clientId: The client ID returned in the handshake response
+		  * id: A unique ID for the message generated by the client
+		  * ext: Same as above
+		"""
+		self.required_fields = ( 'channel', 'data' )
+		if d is None:
+			assert clientId, "You must supply a clientId for a PublishRequest message"
+			assert channel, "You must supply a channel for a PublishRequest message"
+			assert data, "You must supply the data for a PublishRequest message"
+			d = {
+				'id': str(uuid.uuid4()),
+				'channel': channel,
+				'data': data,
+				'clientId': clientId,
+			}
+		self._init_from_dict(d)
+
+# Response Messages
+class BayeuxResponseMessage(BayeuxMessage):
+	"""
+	Base class for response messages.
+	"""
+	HANDSHAKE   = 0
+	CONNECT     = 1
+	DISCONNECT  = 2
+	SUBSCRIBE   = 3
+	UNSUBSCRIBE = 4
+	PUBLISH     = 5
+	EVENT       = 6
+	FROMDICT    = 7
+
+class HandshakeResponse(BayeuxResponseMessage):
+	"""
+	The bayeux message sent in reply to a HandshakeRequest.
+
+	Required Fields:
+	  * channel: Must have a value of "/meta/handshake"
+	  * version: The version of the protocol that we are using
+	  * supportedConnectionTypes: The connection types supported by the server.  This
+		may be a subset of the types we sent in our HandshakeRequest, but it MUST
+		contian at least one element from our list OR it MUST be unsuccessful
+	  * clientId: A newly generated unique id for our client
+	  * successful: A field indicating success or failure of the HandshakeRequest
+
+	Optional Fields:
+	  * minimumVersion: The minimum version of the protocol that this server supports
+	  * advice: Various bits of advice about reconnecting, timeouts and such
+	  * ext: Used for protocol extensions
+	  * id: This will be the exact same unique value from the HandshakeRequest,
+		assuming we sent one.
+	  * authSuccessful: Used with implementations that use authentication?
+
+	@d (dict) : A dictionary created by loading some json
+	"""
+	def __init__(self, d=None, successful=True, id=None, advice=None):
+		self.required_fields = ('channel', 'version', 'supportedConnectionTypes', 'clientId', 'successful')
+		if d is None:
+			d = {
+				'channel': "/meta/handshake",
+				'version': "1.0",
+				'supportedConnectionTypes': [ "long-polling" ],
+				'clientId': ''.join([random.choice(string.ascii_letters + string.digits) for i in range(30)]),
+				'successful': True,
+			}
+			if id is not None:
+				d['id'] = id
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+class ConnectResponse(BayeuxResponseMessage):
+	"""
+	The bayeux message sent in response to a ConnectRequest.
+
+	Required Fields:
+	  * channel: Must have a value of "/meta/connect"
+	  * clientId: The clientId we passed in the ConnectRequest
+	  * successful: Boolean indicating success or failure of the connection
+
+	Optional Fields:
+	  * error: An error if the request was unsuccessful
+	  * advice: Various bits of advice about reconnecting etc
+	  * ext: Used for protocol extensions
+	  * id: The same unique value sent in the ConnectRequest
+
+	@d (dict) : A dictonary created by loading some json returned from the server
+	"""
+	def __init__(self, d=None, clientId=None, successful=True, id=None, advice=None):
+		# self.required_fields = ('channel', 'clientId', 'successful')
+		# cometd within jetty doesn't appear to be doing to the protocol correctly
+		self.required_fields = ('channel', 'successful')
+		if d is None:
+			assert clientId, "You must supply a clientId for a ConnectResponse message"
+			d = {
+				'channel': "/meta/connect",
+				'clientId': clientId,
+				'successful': successful,
+			}
+			if id is not None:
+				d['id'] = id
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+class DisconnectResponse(BayeuxResponseMessage):
+	"""
+	The bayuex message sent in response to DisconnectRequest.
+
+	Required Fields:
+	  * channel: Must have a value of "/meta/disconnect"
+	  * clientId: The clientId from the handshake
+	  * successful: Boolean indicating success or failure of the disconnect
+
+	Optional Fields:
+	  * error: An error if the disconnect was unsuccessful
+	  * ext: Used for protocol extensions
+	  * id: Unique identifier from the DisconnectRequest
+
+	@d (dict) : A dictionary representation of a bayeux message
+	"""
+	def __init__(self, d=None, clientId=None, successful=None, id=None):
+		# self.required_fields = ('channel', 'clientId', 'successful')
+		# cometd within jetty doesn't appear to be doing to the protocol correctly
+		self.required_fields = ('channel', 'successful')
+		if d is None:
+			assert clientId, "You must supply a clientId for a DisconnectResponse message"
+			assert successful, "You must supply whether the message indicates success or failure"
+			d = {
+				'channel': "/meta/disconnect",
+				'clientId': clientId,
+				'successful': successful,
+			}
+			if id is not None:
+				d['id'] = id
+		self._init_from_dict(d)
+
+class SubscribeResponse(BayeuxResponseMessage):
+	"""
+	The bayeux message sent in response to a subscribe request.
+
+	Required Fields:
+	  * channel: Must have a value of "/meta/subscribe"
+	  * clientId: The clientId from the handshake
+	  * successful: Boolean indicating success or failure of the subscribe
+	  * subscription: Channel name, pattern or array thereof
+
+	Optional Fields:
+	  * error: An error if the subscribe was unsuccessful
+	  * ext: Used for protocol extensions
+	  * id: The same unique id passed in the subscribe request
+	  * advice: Hints on timeouts and retrys
+	  * timestamp: ISO 8601 (all times SHOULD be sent in GMT time) i.e. YYYY-MM-DDThh:mm:ss.ss
+
+	@d (dict) : A dictionary representation of a bayeux message
+	"""
+	def __init__(self, d=None, clientId=None, successful=None, subscription=None, id=None, advice=None):
+		# self.required_fields = ('channel', 'clientId', 'successful', 'subscription')
+		# cometd within jetty doesn't appear to be doing to the protocol correctly
+		self.required_fields = ('channel', 'successful', 'subscription')
+		if d is None:
+			assert clientId, "You must supply a clientId for a SubscribeResponse message"
+			assert successful, "You must supply whether the message indicates success or failure"
+			assert subscription, "You must supply the subscription for a SubscribeResponse"
+			d = {
+				'channel': "/meta/subscribe",
+				'clientId': clientId,
+				'successful': successful,
+				'subscription': subscription,
+			}
+			if id is not None:
+				d['id'] = id
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+class UnsubscribeResponse(BayeuxResponseMessage):
+	"""
+	The bayeux message sent in response to an unsubscribe request.
+
+	Required Fields:
+	  * channel: Must have a value of "/meta/unsubscribe"
+	  * successful: Boolean indicating success or failure
+	  * clientId: The clientId from the handshake
+	  * subscription: A channel name or a channel pattern or an array of channel names and channel patterns
+
+	Optional Fields:
+	  * error
+	  * advice
+	  * ext
+	  * id
+	  * timestamp
+
+	@d (dict) : A dictionary representation of a bayeux message
+	"""
+	def __init__(self, d=None, clientId=None, successful=None, subscription=None, id=None, advice=None):
+		# self.required_fields = ('channel', 'successful', 'clientId', 'subscription')
+		# cometd within jetty doesn't appear to be doing to the protocol correctly
+		self.required_fields = ('channel', 'successful', 'subscription')
+		if d is None:
+			assert clientId, "You must supply a clientId for an UnsubscribeResponse message"
+			assert successful, "You must supply whether the message indicates success or failure"
+			assert subscription, "You must supply the subscription for a UnsubscribeResponse"
+			d = {
+				'channel': "/meta/unsubscribe",
+				'clientId': clientId,
+				'successful': successful,
+				'subscription': subscription,
+			}
+			if id is not None:
+				d['id'] = id
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+class PublishResponse(BayeuxResponseMessage):
+	"""
+	The bayeux message sent in response to a publish request.
+
+	A Bayeux server MAY respond to a publish event message with a publish
+	event acknowlegement.
+
+	Required Fields:
+	  * channel: The same channel as the publish request
+	  * successful: boolean indicating the success or failure
+
+	Optional Fields:
+	  * id
+	  * error
+	  * ext
+
+	@d (dict) : A dictionary representation of a bayeux message
+	"""
+	def __init__(self, d=None, channel=None, successful=None, id=None, advice=None):
+		self.required_fields = ('channel', 'successful')
+		if d is None:
+			assert channel, "You must supply the channel for a PublishResponse"
+			assert successful, "You must supply whether the message indicates success or failure"
+			d = {
+				'channel': channel,
+				'successful': successful,
+			}
+			if id is not None:
+				d['id'] = id
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+# Remote event message (still treated like a response though, since it
+# is in response to our polling "connect" messages)
+class EventMessage(BayeuxResponseMessage):
+	"""
+	This class represents an event message (delivered from the server to the client).
+
+	Event messages MUST be delivered to clients if the client is
+	subscribed to the channel of the event message. Event messages
+	MAY be sent to the client in the same HTTP response as any
+	other message other than a meta handshake response.  If a
+	Bayeux server has multiple HTTP requests from the same client,
+	the server SHOULD deliver all available messages in the HTTP
+	response that will be sent immediately in preference to waking
+	a waiting connect meta message request.  Event message delivery
+	MAY not acknowledged by the client.
+
+	Required Fields:
+	  * channel: The channel you are subscribed to that this message is for
+	  * data: An arbitrary json encoded object that is the payload of the message
+
+	Optional Fields:
+	  * id: Unique message id from the publisher
+	  * clientId: The clientId from the handshake
+	  * advice: Same as above
+	  * ext: Same as above
+
+	@d (dict) : A dictionary representation of a bayeux message
+	"""
+	def __init__(self, d=None, channel=None, data=None, id=None, clientId=None, advice=None):
+		self.required_fields = ('channel', 'data')
+		if d is None:
+			assert channel, "You must supply the channel for an EventMessage"
+			assert data, "You must supply the data for an EventMessage"
+			d = {
+				'channel': channel,
+				'data': data,
+			}
+			if id is not None:
+				d['id'] = id
+			if clientId is not None:
+				d['clientId'] = clientId
+			if advice is not None:
+				assert isinstance(advice, dict), "advice must be a dictionary"
+				d['advice'] = advice
+		self._init_from_dict(d)
+
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.