class ReceiptListener(Listener): """:param timeout: When a STOMP frame was sent to the broker and a **RECEIPT** frame was requested, this is the time (in seconds) to wait for **RECEIPT** frames to arrive. If :obj:`None`, we will wait indefinitely. **Example**: >>> client.add(ReceiptListener(1.0)) """ def __init__(self, timeout=None): self._timeout = timeout self._receipts = InFlightOperations('Waiting for receipt') self.log = logging.getLogger(LOG_CATEGORY) def onConnectionLost(self, connection, reason): # @UnusedVariable for waiting in list(self._receipts.values()): if waiting.called: continue waiting.errback(StompCancelledError('Receipt did not arrive (connection lost)')) @defer.inlineCallbacks def onSend(self, connection, frame): # @UnusedVariable if not frame: defer.returnValue(None) receipt = frame.headers.get(StompSpec.RECEIPT_HEADER) if receipt is None: defer.returnValue(None) with self._receipts(receipt, self.log) as receiptArrived: yield receiptArrived.wait(self._timeout, StompCancelledError('Receipt did not arrive on time: %s [timeout=%s]' % (receipt, self._timeout))) def onReceipt(self, connection, frame, receipt): # @UnusedVariable self._receipts[receipt].callback(None)
class SubscriptionListener(Listener): """Corresponds to a STOMP subscription. :param handler: A callable :obj:`f(client, frame)` which accepts a :class:`~.async.client.Stomp` connection and the received :class:`~.StompFrame`. :param ack: Check this option if you wish to automatically ack **MESSAGE** frames after they were handled (successfully or not). :param errorDestination: If a frame was not handled successfully, forward a copy of the offending frame to this destination. Example: ``errorDestination='/queue/back-to-square-one'`` :param onMessageFailed: You can specify a custom error handler which must be a callable with signature :obj:`f(connection, failure, frame, errorDestination)`. Note that a non-trivial choice of this error handler overrides the default behavior (forward frame to error destination and ack it). .. seealso :: The unit tests in the module :mod:`.tests.async_client_integration_test` cover a couple of usage scenarios. """ DEFAULT_ACK_MODE = 'client-individual' def __init__(self, handler, ack=True, errorDestination=None, onMessageFailed=None): if not callable(handler): raise ValueError('Handler is not callable: %s' % handler) self._handler = handler self._ack = ack self._errorDestination = errorDestination self._onMessageFailed = onMessageFailed or sendToErrorDestination self._headers = None self._messages = InFlightOperations('Handler for message') self.log = logging.getLogger(LOG_CATEGORY) @defer.inlineCallbacks def onDisconnect(self, connection, failure, timeout): # @UnusedVariable if not self._messages: defer.returnValue(None) self.log.info( 'Waiting for outstanding message handlers to finish ... [timeout=%s]' % timeout) yield self._waitForMessages(timeout) self.log.info('All handlers complete. Resuming disconnect ...') @defer.inlineCallbacks def onMessage(self, connection, frame, context): """onMessage(connection, frame, context) Handle a message originating from this listener's subscription.""" if context is not self: return with self._messages(frame.headers[StompSpec.MESSAGE_ID_HEADER], self.log) as waiting: try: yield self._handler(connection, frame) except Exception as e: yield self._onMessageFailed(connection, e, frame, self._errorDestination) finally: if self._ack and (self._headers[StompSpec.ACK_HEADER] in StompSpec.CLIENT_ACK_MODES): connection.ack(frame) if not waiting.called: waiting.callback(None) def onSubscribe(self, connection, frame, context): # @UnusedVariable """Set the **ack** header of the **SUBSCRIBE** frame initiating this listener's subscription to the value of the class atrribute :attr:`DEFAULT_ACK_MODE` (if it isn't set already). Keep a copy of the headers for handling messages originating from this subscription.""" if context is not self: return if self._headers is not None: # already subscribed return frame.headers.setdefault(StompSpec.ACK_HEADER, self.DEFAULT_ACK_MODE) self._headers = frame.headers @defer.inlineCallbacks def onUnsubscribe(self, connection, frame, context): # @UnusedVariable """onUnsubscribe(connection, frame, context) Forget everything about this listener's subscription and unregister from the **connection**.""" if context is not self: return yield self._waitForMessages(None) connection.remove(self) def onConnectionLost(self, connection, reason): # @UnusedVariable """onConnectionLost(connection, reason) Forget everything about this listener's subscription and unregister from the **connection**.""" connection.remove(self) def _waitForMessages(self, timeout): return task.cooperate( handler.wait( timeout, StompCancelledError( 'Handlers did not finish in time.')) for handler in self._messages.values()).whenDone()
class SubscriptionListener(Listener): """Corresponds to a STOMP subscription. :param handler: A callable :obj:`f(client, frame)` which accepts a :class:`~.async.client.Stomp` connection and the received :class:`~.StompFrame`. :param ack: Check this option if you wish to automatically ack **MESSAGE** frames after they were handled (successfully or not). :param errorDestination: If a frame was not handled successfully, forward a copy of the offending frame to this destination. Example: ``errorDestination='/queue/back-to-square-one'`` :param onMessageFailed: You can specify a custom error handler which must be a callable with signature :obj:`f(connection, failure, frame, errorDestination)`. Note that a non-trivial choice of this error handler overrides the default behavior (forward frame to error destination and ack it). .. seealso :: The unit tests in the module :mod:`.tests.async_client_integration_test` cover a couple of usage scenarios. """ DEFAULT_ACK_MODE = 'client-individual' def __init__(self, handler, ack=True, errorDestination=None, onMessageFailed=None): if not callable(handler): raise ValueError('Handler is not callable: %s' % handler) self._handler = handler self._ack = ack self._errorDestination = errorDestination self._onMessageFailed = onMessageFailed or sendToErrorDestination self._headers = None self._messages = InFlightOperations('Handler for message') self.log = logging.getLogger(LOG_CATEGORY) @defer.inlineCallbacks def onDisconnect(self, connection, reason, timeout): # @UnusedVariable connection.remove(self) if not self._messages: defer.returnValue(None) self.log.info('Waiting for outstanding message handlers to finish ... [timeout=%s]' % timeout) yield self._waitForMessages(timeout) self.log.info('All handlers complete. Resuming disconnect ...') @defer.inlineCallbacks def onMessage(self, connection, frame, context): """onMessage(connection, frame, context) Handle a message originating from this listener's subscription.""" if context is not self: return with self._messages(frame.headers[StompSpec.MESSAGE_ID_HEADER], self.log) as waiting: try: yield self._handler(connection, frame) except Exception as e: yield self._onMessageFailed(connection, e, frame, self._errorDestination) finally: if self._ack and (self._headers[StompSpec.ACK_HEADER] in StompSpec.CLIENT_ACK_MODES): yield connection.ack(frame) if not waiting.called: waiting.callback(None) def onSubscribe(self, connection, frame, context): # @UnusedVariable """Set the **ack** header of the **SUBSCRIBE** frame initiating this listener's subscription to the value of the class atrribute :attr:`DEFAULT_ACK_MODE` (if it isn't set already). Keep a copy of the headers for handling messages originating from this subscription.""" if context is not self: return if self._headers is not None: # already subscribed return frame.headers.setdefault(StompSpec.ACK_HEADER, self.DEFAULT_ACK_MODE) self._headers = frame.headers @defer.inlineCallbacks def onUnsubscribe(self, connection, frame, context): # @UnusedVariable """onUnsubscribe(connection, frame, context) Forget everything about this listener's subscription and unregister from the **connection**.""" if context is not self: return connection.remove(self) yield self._waitForMessages(None) def onConnectionLost(self, connection, reason): # @UnusedVariable """onConnectionLost(connection, reason) Forget everything about this listener's subscription and unregister from the **connection**.""" connection.remove(self) def _waitForMessages(self, timeout): return task.cooperate(handler.wait(timeout, StompCancelledError('Handlers did not finish in time.')) for handler in list(self._messages.values())).whenDone()