def __init__(self, server, oauth_header=None): """Initialize the client. Args: server: The remote bayeux server to connect this client to (e.g. 'http://1.1.1.1:8080/bayeux') """ self.server = server self.timer = None self.retry_connect_count = 0 self.connect_interval = 0 self.receiver = BayeuxMessageReceiver() self.is_handshook = False self.started = False self.destroyed = False self.connected = False self.subscriptions = set() self.lock = RLock() self.oauth_header = oauth_header logging.debug("server: %s, receiver: %s, oauth header: %s", self.server, self.receiver, self.oauth_header) self.sender = BayeuxMessageSender(self.server, self.receiver, self.oauth_header) self.receiver.register(bayeux_constants.HANDSHAKE_CHANNEL, self._handshake_cb) logging.debug("registered handshake channel") self.receiver.register(bayeux_constants.CONNECT_CHANNEL, self._connect_cb) logging.debug("registered connect channel") self.receiver.register(bayeux_constants.DISCONNECT_CHANNEL, self._disconnect_cb) logging.debug("registered disconnect channel")
def __init__(self, server): """Initialize the client. Args: server: The remote bayeux server to connect this client to (e.g. 'http://1.1.1.1:8080/bayeux') """ self.server = server self.timer = None self.retry_connect_count = 0 self.connect_interval = 0 self.receiver = BayeuxMessageReceiver() self.is_handshook = False self.started = False self.destroyed = False self.connected = False self.subscriptions = set() self.lock = RLock() self.sender = BayeuxMessageSender(self.server, self.receiver) self.receiver.register(bayeux_constants.HANDSHAKE_CHANNEL, self._handshake_cb) self.receiver.register(bayeux_constants.CONNECT_CHANNEL, self._connect_cb) self.receiver.register(bayeux_constants.DISCONNECT_CHANNEL, self._disconnect_cb)
class BayeuxClient(object): zope.interface.implements(IMessengerService) """Client that implements the bayeux protocol. User of this class should call register to register for particular events and then call start to start the client. Attributes: server: The remote bayeux server to connect to receiver: The bayeux message receiver sender: The bayeux message sender timer: A timer used to retry messaging in cases of errors retry_connect_count: Counter for the number of connect retries connect_interval: Interval for the connect message in seconds is_handshook: Whether or not we have made a successful handshake request subscriptions: Set of active subscriptions lock: Concurrency lock """ def __init__(self, server): """Initialize the client. Args: server: The remote bayeux server to connect this client to (e.g. 'http://1.1.1.1:8080/bayeux') """ self.server = server self.timer = None self.retry_connect_count = 0 self.connect_interval = 0 self.receiver = BayeuxMessageReceiver() self.is_handshook = False self.started = False self.destroyed = False self.connected = False self.subscriptions = set() self.lock = RLock() self.sender = BayeuxMessageSender(self.server, self.receiver) self.receiver.register(bayeux_constants.HANDSHAKE_CHANNEL, self._handshake_cb) self.receiver.register(bayeux_constants.CONNECT_CHANNEL, self._connect_cb) self.receiver.register(bayeux_constants.DISCONNECT_CHANNEL, self._disconnect_cb) def destroy(self): """Destroys the client. This stops the Twisted Reactor. Once this is called the reactor can no longer be started. Should call this prior to exiting the application. """ with self.lock: self.destroyed = True if reactor.running: if self.started and self.connected: #Currently running and connected so issue a disconnect self.sender.disconnect(self._disconnect_error) elif not self.started and self.connected: #There is a pending disconnect so #wait for response pass else: #Not connected so just stop reactor self._stop_reactor() def start(self): #TODO Take daemon in as arg """Starts the client. This methods spawns a new thread to perform messaging tasks. The client is started by issuing a handshake request to the server. Once the response for the handshake request is received, maintains the connection to the server by issuing periodic connect requests. """ with self.lock: if not self.started: #Start up the new thread where the reactor runs self.started = True self.connected = False if not reactor.running: thread = Thread(name='BayeuxClient-Thread', target=reactor.run, args=(False,)) thread.daemon = True thread.start() self.sender.handshake(self._handshake_error) else: #Client already running logging.info('Client already running') def stop(self): """Stops the client. The client is stopped by stopping any current connect requests and issuing a disconnect request to the server. """ with self.lock: if self.started: self.is_handshook = False self.started = False self.retry_connect_count = 0 if self.timer is not None: self.timer.cancel() self.timer = None self.sender.disconnect(self._disconnect_error) else: #Client not running logging.info('Client not running') def register(self, id, callback): """Subscribe for a particular event. Args: id: The event to subscribe to (e.g. '/foo/bar') callback: The callback to trigger upon receipt of the message """ with self.lock: if self.is_handshook: if id not in self.subscriptions: #Already connected so subscribe for this new event self.subscriptions.add(id) if self.started: self.sender.subscribe(id) #TODO Handle error case else: #Event already subscribed for so don't need to do anything pass else: #Not yet connected so just add to our subscription list. #The subscription list will be used to subscribe once we are #connected. self.subscriptions.add(id) self.receiver.register(id, callback) def deregister(self, id, callback): """Unsubscribe from a particular event. Args: id: The event to unsubscribe from callback: The callback to unsubscribe """ with self.lock: if self.receiver.deregister(id, callback) == 0: #No more listeners for this event to unsubscribe from the #server self.subscriptions.remove(id) if self.started: self.sender.unsubscribe(id) def _connect_cb(self, data): """Callback for the connect message. If this connect message succeeded, then issue another connect mesasge based on the interval value in the connect response message. The connect messsage acts as a heartbeat to the bayeux server. If the connect message failed, then try and restart the client again. Args: data: The connect response data """ logging.debug('_connect_cb: %s' % data) with self.lock: if self.started: if(data['successful']): self.retry_connect_count = 0 self.connected = True if('advice' in data and 'interval' in data['advice'] and data['advice']['interval']): #The interval defines how often we need to ping on the #server with a connect message to keep the connection #alive self.connect_interval = int( data['advice']['interval']) / 1000 self.timer = Timer(self.connect_interval, self.sender.connect, [self._connect_error]) self.timer.start() def _connect_error(self, reason): """Callback if there is an error during the connect request message. If there was an error sending the connect message, retry a few times before trying to restart the client again. Args: reason: The reason that the connect failed """ logging.debug('_connect_error: %s' % reason) with self.lock: if self.started: self.retry_connect_count += 1 self.connected = False if(self.retry_connect_count < bayeux_constants.CONNECT_FAILURE_THRESHOLD): logging.warning('Trying to reconnect') self.timer = Timer(self.connect_interval, self.sender.connect, [self._connect_error]) self.timer.start() else: logging.warning('Failed trying to reconnect...resend handshake request') #Consider this a failed connection and go back to retrying #handshakes self.is_handshook = False self.retry_connect_count = 0 self.sender.handshake(self._handshake_error) def _disconnect_cb(self, data): """Callback for the disconnect message. Stops the reactor upon disconnecting from the server. Args: data: The disconnect response data """ logging.debug('_disconnect_cb: %s' % data) with self.lock: self.connected = False if self.destroyed: self._stop_reactor() def _disconnect_error(self, reason): """Callback if there is an error during the disconnect request message. Args: reason: The reason the disconnect failed """ logging.debug('_disconnect_error: %s' % reason) with self.lock: self.connected = False if self.destroyed: self._stop_reactor() def _handshake_cb(self, data): """Callback for the handshake message. If the callback succeeded, then update the client id in the sender and begin issuing connect requests. If the handshake failed, the resend the handshake request after a given timeout. Args: data: The handshake response data """ logging.debug('_handshake_cb: %s' % data) with self.lock: if self.started and not self.destroyed: if(data['successful']): self.is_handshook = True self.sender.set_client_id( data['clientId']) self.sender.connect( self._connect_error) #On a successful handshake register for pending subscriptions for event in self.subscriptions: #TODO Handle error case self.sender.subscribe(event) else: #Connect was not successful for some reason, try again self.is_handshook = False self.timer = Timer(bayeux_constants.HANDSHAKE_RETRY_INTERVAL, self.sender.handshake, [self._handshake_error]) self.timer.start() def _handshake_error(self, reason): """Callback if there is an error during the handshake request message. If there was an error sending the handshake message, then wait and retry the message until we are able to connect. Args: reason: The reason that the handshake failed """ with self.lock: if self.started and not self.destroyed: logging.warning(''.join(['Error sending handshake request', 'message...retrying in ', str(bayeux_constants.HANDSHAKE_RETRY_INTERVAL), ' secs'])) self.is_handshook = False self.timer = Timer(bayeux_constants.HANDSHAKE_RETRY_INTERVAL, self.sender.handshake, [self._handshake_error]) self.timer.start() def _stop_reactor(self): """Helper method to stop the reactor""" if reactor.running: logging.info('Stopping reactor') reactor.callFromThread(reactor.stop)
class BayeuxClient(object): zope.interface.implements(IMessengerService) """Client that implements the bayeux protocol. User of this class should call register to register for particular events and then call start to start the client. Attributes: server: The remote bayeux server to connect to oauth_header: if authorization is required, this is the full header value receiver: The bayeux message receiver sender: The bayeux message sender timer: A timer used to retry messaging in cases of errors retry_connect_count: Counter for the number of connect retries connect_interval: Interval for the connect message in seconds is_handshook: Whether or not we have made a successful handshake request subscriptions: Set of active subscriptions lock: Concurrency lock """ def __init__(self, server, oauth_header=None): """Initialize the client. Args: server: The remote bayeux server to connect this client to (e.g. 'http://1.1.1.1:8080/bayeux') """ self.server = server self.timer = None self.retry_connect_count = 0 self.connect_interval = 0 self.receiver = BayeuxMessageReceiver() self.is_handshook = False self.started = False self.destroyed = False self.connected = False self.subscriptions = set() self.lock = RLock() self.oauth_header = oauth_header logging.debug("server: %s, receiver: %s, oauth header: %s", self.server, self.receiver, self.oauth_header) self.sender = BayeuxMessageSender(self.server, self.receiver, self.oauth_header) self.receiver.register(bayeux_constants.HANDSHAKE_CHANNEL, self._handshake_cb) logging.debug("registered handshake channel") self.receiver.register(bayeux_constants.CONNECT_CHANNEL, self._connect_cb) logging.debug("registered connect channel") self.receiver.register(bayeux_constants.DISCONNECT_CHANNEL, self._disconnect_cb) logging.debug("registered disconnect channel") def destroy(self): """Destroys the client. This stops the Twisted Reactor. Once this is called the reactor can no longer be started. Should call this prior to exiting the application. """ with self.lock: self.destroyed = True if reactor.running: if self.started and self.connected: #Currently running and connected so issue a disconnect self.sender.disconnect(self._disconnect_error) elif not self.started and self.connected: #There is a pending disconnect so #wait for response pass else: #Not connected so just stop reactor self._stop_reactor() def start(self): #TODO Take daemon in as arg """Starts the client. This methods spawns a new thread to perform messaging tasks. The client is started by issuing a handshake request to the server. Once the response for the handshake request is received, maintains the connection to the server by issuing periodic connect requests. """ with self.lock: if not self.started: #Start up the new thread where the reactor runs self.started = True self.connected = False if not reactor.running: thread = Thread(name='BayeuxClient-Thread', target=reactor.run, args=(False, )) thread.daemon = True thread.start() self.sender.handshake(self._handshake_error) #else: # #Client already running # logging.info('Client already running') def stop(self): """Stops the client. The client is stopped by stopping any current connect requests and issuing a disconnect request to the server. """ with self.lock: if self.started: self.is_handshook = False self.started = False self.retry_connect_count = 0 if self.timer is not None: self.timer.cancel() self.timer = None self.sender.disconnect(self._disconnect_error) #else: # #Client not running # logging.info('Client not running') def register(self, id, callback): """Subscribe for a particular event. Args: id: The event to subscribe to (e.g. '/foo/bar') callback: The callback to trigger upon receipt of the message """ with self.lock: if self.is_handshook: if id not in self.subscriptions: #Already connected so subscribe for this new event self.subscriptions.add(id) if self.started: self.sender.subscribe(id) #TODO Handle error case else: #Event already subscribed for so don't need to do anything pass else: #Not yet connected so just add to our subscription list. #The subscription list will be used to subscribe once we are #connected. self.subscriptions.add(id) self.receiver.register(id, callback) def deregister(self, id, callback): """Unsubscribe from a particular event. Args: id: The event to unsubscribe from callback: The callback to unsubscribe """ with self.lock: if self.receiver.deregister(id, callback) == 0: #No more listeners for this event to unsubscribe from the #server self.subscriptions.remove(id) if self.started: self.sender.unsubscribe(id) def _connect_cb(self, data): """Callback for the connect message. If this connect message succeeded, then issue another connect mesasge based on the interval value in the connect response message. The connect messsage acts as a heartbeat to the bayeux server. If the connect message failed, then try and restart the client again. Args: data: The connect response data """ logging.debug('_connect_cb: %s' % data) with self.lock: if self.started: if (data['successful']): self.retry_connect_count = 0 self.connected = True if ('advice' in data and 'interval' in data['advice'] and data['advice']['interval']): #The interval defines how often we need to ping on the #server with a connect message to keep the connection #alive self.connect_interval = int( data['advice']['interval']) / 1000 self.timer = Timer(self.connect_interval, self.sender.connect, [self._connect_error]) self.timer.start() def _connect_error(self, reason): """Callback if there is an error during the connect request message. If there was an error sending the connect message, retry a few times before trying to restart the client again. Args: reason: The reason that the connect failed """ logging.debug('_connect_error: %s' % reason) with self.lock: if self.started: self.retry_connect_count += 1 self.connected = False if (self.retry_connect_count < bayeux_constants.CONNECT_FAILURE_THRESHOLD): logging.warning('Trying to reconnect') self.timer = Timer(self.connect_interval, self.sender.connect, [self._connect_error]) self.timer.start() else: logging.warning( 'Failed trying to reconnect...resend handshake request' ) #Consider this a failed connection and go back to retrying #handshakes self.is_handshook = False self.retry_connect_count = 0 self.sender.handshake(self._handshake_error) def _disconnect_cb(self, data): """Callback for the disconnect message. Stops the reactor upon disconnecting from the server. Args: data: The disconnect response data """ logging.debug('_disconnect_cb: %s' % data) with self.lock: self.connected = False if self.destroyed: self._stop_reactor() def _disconnect_error(self, reason): """Callback if there is an error during the disconnect request message. Args: reason: The reason the disconnect failed """ logging.debug('_disconnect_error: %s' % reason) with self.lock: self.connected = False if self.destroyed: self._stop_reactor() def _handshake_cb(self, data): """Callback for the handshake message. If the callback succeeded, then update the client id in the sender and begin issuing connect requests. If the handshake failed, the resend the handshake request after a given timeout. Args: data: The handshake response data """ logging.debug('_handshake_cb: %s' % data) with self.lock: if self.started and not self.destroyed: if (data['successful']): self.is_handshook = True self.sender.set_client_id(data['clientId']) self.sender.connect(self._connect_error) #On a successful handshake register for pending subscriptions for event in self.subscriptions: #TODO Handle error case self.sender.subscribe(event) else: #Connect was not successful for some reason, try again self.is_handshook = False self.timer = Timer( bayeux_constants.HANDSHAKE_RETRY_INTERVAL, self.sender.handshake, [self._handshake_error]) self.timer.start() def _handshake_error(self, reason): """Callback if there is an error during the handshake request message. If there was an error sending the handshake message, then wait and retry the message until we are able to connect. Args: reason: The reason that the handshake failed """ with self.lock: if self.started and not self.destroyed: logging.warning(''.join([ 'Error sending handshake request', 'message...retrying in ', str(bayeux_constants.HANDSHAKE_RETRY_INTERVAL), ' secs' ])) self.is_handshook = False self.timer = Timer(bayeux_constants.HANDSHAKE_RETRY_INTERVAL, self.sender.handshake, [self._handshake_error]) self.timer.start() def _stop_reactor(self): """Helper method to stop the reactor""" if reactor.running: logging.info('Stopping reactor') reactor.callFromThread(reactor.stop)