Пример #1
0
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)
Пример #2
0
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()
Пример #3
0
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()