class BaseAMQPConnection(BaseConnection): """ An object which does an actual job of (re-)connecting to the the AMQP broker. Concrete subclasses implement either listening or publishing features. """ def __init__(self, conn_params, item_name, properties=None): super(BaseAMQPConnection, self).__init__() self.conn_params = conn_params self.item_name = item_name self.properties = properties self.conn = None self.channel = None self.reconnect_exceptions = (TypeError, EnvironmentError) def _start(self): self.conn = TornadoConnection(self.conn_params, self._on_connected) self.conn.ioloop.start() def _close(self): """ Actually close the connection. """ if self.conn: self.conn.close() def _conn_info(self): return '{0}:{1}{2} ({3})'.format(self.conn_params.host, self.conn_params.port, self.conn_params.virtual_host, self.item_name) def _on_channel_open(self, channel): self.channel = channel msg = 'Got a channel for {0}'.format(self._conn_info()) self.logger.debug(msg) def _keep_connecting(self, e): # We need to catch TypeError because pika will sometimes erroneously raise # it in self._start's self.conn.ioloop.start(). # Otherwise, it may one of the network errors we are hopefully able to recover from. return isinstance(e, TypeError) or (isinstance(e, EnvironmentError) and e.errno in self.reconnect_error_numbers) def _on_connected(self, conn): """ Invoked after establishing a successful connection to an AMQP broker. Will report a diagnostic message regarding how many attempts there were and how long it took if the connection hasn't been established straightaway. """ super(BaseAMQPConnection, self)._on_connected() conn.channel(self._on_channel_open)
class PikaConnection(object): QUEUE = 'test' EXCHANGE = '' PUBLISH_INTERVAL = 1 def __init__(self, io_loop, amqp_url): self.io_loop = io_loop self.url = amqp_url self.is_connected = False self.is_connecting = False self.connection = None self.channel = None self.listeners = set([]) def connect(self): if self.is_connecting: logger.info("PikaConnection: Already connecting to RabbitMQ") return logger.info("PikaConnection: Connecting to RabbitMQ") self.connecting = True self.connection = TornadoConnection( pika.URLParameters(self.url), on_open_callback=self.on_connected) self.connection.add_on_close_callback(self.on_closed) def on_connected(self, connection): logger.info("PikaConnection: connected to RabbitMQ") self.connected = True self.connection = connection self.connection.channel(self.on_channel_open) def on_closed(self, connection): logger.info("PikaConnection: connection closed") self.io_loop.stop() def on_channel_open(self, channel): self.channel = channel self.channel.add_on_close_callback(self.on_channel_closed) self.channel.queue_declare(queue=self.QUEUE, callback=self.on_queue_declared) def on_channel_closed(self, channel, reply_code, reply_text): self.connection.close() def on_queue_declared(self, frame): print "subscribe frame:", frame self.channel.basic_consume(self.on_message, self.QUEUE) def on_message(self, channel, method, header, body): logger.info(body) for listener in self.listeners: listener.emit(body) def add_event_listener(self, listener): self.listeners.add(listener) def publish_message(self, *args, **kwargs): if 'message' in kwargs: self.channel.basic_publish(exchange=self.EXCHANGE, routing_key=self.QUEUE, body=kwargs['message'])
class RabbitMQClient(BaseClient): """ Base RabbitMQ asynchronous adapter. """ def __init__(self, host=settings.HOST, port=settings.PORT, virtual_host=settings.VIRTUAL_HOST, username=settings.USERNAME, password=settings.PASSWORD, exchange_name='direct', exchange_type='direct'): """ Initialize RabbitMQ client with passed configuration or get parameters from django settings module. :param host: RabbitMQ host :type host: :class:`str` :param port: RabbitMQ port :type port: :class:`int` :param virtual_host: RabbitMQ virtual host :type virtual_host: :class:`str` :param username: RabbitMQ user :type username: :class:`str` :param password: RabbitMQ user's password :type password: :class:`str` :param exchange_name: Exchange name, see RabbitMQ docs for more info. :type exchange_name: :class:`str` :param exchange_type: Exchange type, see RabbitMQ docs for more info. :type exchange_type: :class:`str` """ self.host = host self.port = int(port) self.virtual_host = virtual_host self.username = username self.password = password self.exchange_name = exchange_name self.exchange_type = exchange_type self.connected = False self.connecting = False self.reconnecting = False self.closing = False self.connection = None self.channel = None ############################################################################ # CONNECTION METHODS ############################################################################ def connect(self): """ Asynchronous connection. Connects to RabbitMQ server and establish non-blocking connection. After connection is established on_connected callback will be executed which will try to reconnect if this function failed. """ if self.connecting and not self.reconnecting: return self.connecting = True credentials = PlainCredentials(self.username, self.password) param = ConnectionParameters(host=self.host, port=self.port, virtual_host=self.virtual_host, credentials=credentials) self.connection = TornadoConnection(param, on_open_callback=self.on_connected) self.connection.add_on_close_callback(self.on_close) def reconnect(self): """ Reconnect method. Basically you don't need to call this method manually. """ self.reconnecting = True self.connect() def disconnect(self): """ Disconnect method. Call this method after you end with messaging. """ self.closing = True self.connection.close() ############################################################################ # CALLBACKS ############################################################################ def on_connected(self, connection): """ Callback on connection. Reconnect if connection dropped. Otherwise it will open channel and on_channel_open callback will be executed. :param connection: RabbitMQ connection. :type connection: :class:`TornadoConnection` """ self.connected = True self.connection = connection self.connection.channel(self.on_channel_open) def on_channel_open(self, channel, callback=lambda frame: None): """ Callback on channel open. It will declare exchanges for messaging. See RabbitMQ docs for more information. :param channel: Opened channel. :type channel: :class:`Channel` :param callback: Callback that will be executed after channel open. :type callback: callable object """ self.channel = channel self.channel.exchange_declare(exchange=self.exchange_name, type=self.exchange_type, auto_delete=False, durable=True, callback=callback) def on_close(self, connection, *args, **kwargs): """ On close callback. You don't need to manually call this method. :param connection: Established connection. :type connection: :class:`TornadoConnection` :param args: Internal args :param kwargs: Internal kwargs """ self.channel = None if not self.closing: self.reconnect() self.connection.add_timeout(5, self.reconnect) else: connection.close()