import pickle import uuid try: import kombu except ImportError: kombu = None from .pubsub_manager import PubSubManager class KombuManager(PubSubManager): # pragma: no cover """Client manager that uses kombu for inter-process messaging. This class implements a client manager backend for event sharing across multiple processes, using RabbitMQ, Redis or any other messaging mechanism supported by `kombu `_. To use a kombu backend, initialize the :class:`Server` instance as follows:: url = 'amqp://user:password@hostname:port//' server = socketio.Server(client_manager=socketio.KombuManager(url)) :param url: The connection URL for the backend messaging queue. Example connection URLs are ``'amqp://guest:guest@localhost:5672//'`` and ``'redis://localhost:6379/'`` for RabbitMQ and Redis respectively. Consult the `kombu documentation `_ for more on how to construct connection URLs. :param channel: The channel name on which the server sends and receives notifications. Must be the same in all the servers. :param write_only: If set ot ``True``, only initialize to emit events. The default of ``False`` initializes the class for emitting and receiving. :param connection_options: additional keyword arguments to be passed to ``kombu.Connection()``. :param exchange_options: additional keyword arguments to be passed to ``kombu.Exchange()``. :param queue_options: additional keyword arguments to be passed to ``kombu.Queue()``. :param producer_options: additional keyword arguments to be passed to ``kombu.Producer()``. """ name = 'kombu' def __init__(self, url='amqp://guest:guest@localhost:5672//', channel='socketio', write_only=False, logger=None, connection_options=None, exchange_options=None, queue_options=None, producer_options=None): if kombu is None: raise RuntimeError('Kombu package is not installed ' '(Run "pip install kombu" in your ' 'virtualenv).') super(KombuManager, self).__init__(channel=channel, write_only=write_only, logger=logger) self.url = url self.connection_options = connection_options or {} self.exchange_options = exchange_options or {} self.queue_options = queue_options or {} self.producer_options = producer_options or {} self.producer = self._producer() def initialize(self): super(KombuManager, self).initialize() monkey_patched = True if self.server.async_mode == 'eventlet': from eventlet.patcher import is_monkey_patched monkey_patched = is_monkey_patched('socket') elif 'gevent' in self.server.async_mode: from gevent.monkey import is_module_patched monkey_patched = is_module_patched('socket') if not monkey_patched: raise RuntimeError( 'Kombu requires a monkey patched socket library to work ' 'with ' + self.server.async_mode) def _connection(self): return kombu.Connection(self.url, **self.connection_options) def _exchange(self): options = {'type': 'fanout', 'durable': False} options.update(self.exchange_options) return kombu.Exchange(self.channel, **options) def _queue(self): queue_name = 'flask-socketio.' + str(uuid.uuid4()) options = {'durable': False, 'queue_arguments': {'x-expires': 300000}} options.update(self.queue_options) return kombu.Queue(queue_name, self._exchange(), **options) def _producer(self): return self._connection().Producer(exchange=self._exchange(), **self.producer_options) def __error_callback(self, exception, interval): self._get_logger().exception('Sleeping {}s'.format(interval)) def _publish(self, data): connection = self._connection() publish = connection.ensure(self.producer, self.producer.publish, errback=self.__error_callback) publish(pickle.dumps(data)) def _listen(self): reader_queue = self._queue() while True: connection = self._connection().ensure_connection( errback=self.__error_callback) try: with connection.SimpleQueue(reader_queue) as queue: while True: message = queue.get(block=True) message.ack() yield message.payload except connection.connection_errors: self._get_logger().exception("Connection error " "while reading from queue")