Package: channels-rabbitmq
A Django Channels channel layer that uses RabbitMQ as its backing store.Does not supportWorker and Background Tasks.
(SeeRationaleand useawaitget_channel_layer().current_connectionto send to job queues.)Works with Python 3.8 or 3.9.Installationpip install channels_rabbitmqUsageThen set up the channel layer in your Django settings file like so:CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_rabbitmq.core.RabbitmqChannelLayer",
        "CONFIG": {
            "host": "amqp://guest:guest@127.0.0.1/asgi",
            # "ssl_context": ... (optional)
        },
    },
}Possible options forCONFIGare listed below.hostURL of the server to connect to, adhering toRabbitMQ spec. To connect to a RabbitMQ cluster,
use a DNS server to resolve a hostname to multiple IP addresses.
channels_rabbitmq will automatically reconnect if at least one of them is
reachable in case of a disconnection.expiryMinimum number of seconds a message should wait in a RabbitMQ queue, before it
may be silently dropped.Defaults to60. You generally shouldn’t need to change this, but you may
want to turn it down if you have peaky traffic you wish to drop, or up if you
have peaky traffic you want to backlog until you get to it.local_capacityNumber of incoming messages queued in memory. Defaults to100. (A message
sent to a group with two channels counts as one message.) Whenlocal_capacitymessages are queued, the message backlog will grow on RabbitMQ.(This controls theprefetch_counton the RabbitMQ queue.)local_expiryMinimum number of seconds a message received from RabbitMQ must be held in
memory waiting forreceive(), before it may be dropped. Defaults toexpiry.A warning will be logged when a message expires locally. The warning can
indicate that a channel has more messages than it can handle; or that
messages are being sent to a channel that does not exist. (Perhaps a missing
channel was implied bygroup_add(), and a matchinggroup_discard()was never called.)Iflocal_expiry < expiry, then you can end up ignoring (and logging)
messages locally while they still exist in the RabbitMQ queue. These messages
will be acked, so RabbitMQ will behave as though they were delivered.remote_capacityNumber of messages stored on RabbitMQ for each client. Defaults to100.
(A message sent to a group with three channels on two distinct clients counts
as two messages.) Whenremote_capacitymessages are queued in RabbitMQ,
the channel will refuse new messages. Calls from any client tosend()orgroup_send()to the at-capacity client will raiseChannelFull.ssl_contextAnSSL context. Changes the
defaulthostport to 5671 (instead of 5672).For instance, to connect to an TLS RabbitMQ service that will verify your
client:import ssl
ssl_context = ssl.create_default_context(
    cafile=str(Path(__file__).parent.parent / 'ssl' / 'server.cert'),
)
ssl_context.load_cert_chain(
    certfile=str(Path(__file__).parent.parent / 'ssl' / 'client.certchain'),
    keyfile=str(Path(__file__).parent.parent / 'ssl' / 'client.key'),
)
CHANNEL_LAYERS['default']['CONFIG']['ssl_context'] = ssl_contextBy default, there is no SSL context; all messages (and passwords) are
are transmitted in cleartext.groups_exchangeGlobal direct exchange name used by channels to exchange group messages.
Defaults to"groups". See alsoDesign decisions.Accessing CarehareWe usecareharefor its thorough handling of errors.Django Channels’ specification does not account for “connecting” and
“disconnecting”. This layer does its best by constantly reconnecting, forever.Callawaitget_channel_layer().current_connectionto access an open Carehare
connection. This lets you use job queues without “Worker and Background Tasks”.
Like this:# raise asyncio.CancelledError on failure
connection = await get_channel_layer().carehare_connection

# raise carehare.ConnectionClosed or carehare.ChannelClosed on error
await connection.publish(b"task", routing_key="job_queue")(The Carehare documentation explains how to build workers.)A note on errors: a “connected” connection isn’t guaranteed tostayconnected
throughout every publish. It was merely connectedat some point in the past.
When a disconnect occurs, all pending operations on that connection will raisecarehare.ConnectionClosed. This channel layer will log the error, andget_channel_layer().carehare_connectionwill point to a new Future. (This
error+reconnect isguaranteed to happenin production.)Publish messages from CeleryManyCeleryusers want to send messages to websockets users.This is doable, though not intuitive. Don’t use any Django Channels code:
Channels layers depend on long-running connections, and Celery bans those.
Same goes for carehare: don’t use it from Celery.Instead, from a Celery-worker@taskyou can send messages to your
Django-Channels consumers using Celery’s RabbitMQ connection:from typing import Any, Dict

import msgpack

def publish_message_to_group(message: Dict[str, Any], group: str) -> None:
    with current_app.producer_pool.acquire(block=True) as producer:
        producer.publish(
            msgpack.packb({
              "__asgi_group__": group,
              **message,
            }),
            exchange="groups",  # groups_exchange
            content_encoding="binary",
            routing_key=group,
            retry=False,  # Channel Layer at-most once semantics
        )To call it, from a Celery-worker@task…:publish_message_to_group({ "type": "chat.message", "text": "hi" }, "a-group")… and a Django-Channels consumer like this will receive it:class WebsocketConnectionConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.channel_layer.group_add("a-group", self.channel_name)

    async def disconnect(self):
        await self.channel_layer.group_discard("a-group", self.channel_name)

    async def chat_message(self, event):
        assert event["text"] == "hi"Alternatively, write your workers asynchronously, directly incarehare.
It’s more lightweight and faster than Celery, and the error handling is
simpler.Design decisionsTo scale enormously, this layer only creates one RabbitMQ queue per instance.
That means one web server gets one RabbitMQ queue, no matter how many
websocket connections are open. For each message being sent, the client-side
layer determines the RabbitMQ queue name and uses it as the routing key.Groups are implemented using a single, global RabbitMQ direct exchange called
“groups” by default. To send a message to a group, the layer sends the message
to the “groups” exchange with the group name as the routing key. The client
binds and unbinds duringgroup_add()andgroup_remove()to ensure
messages for any of its groups will reach it. See also thegroups_exchangeoption.RabbitMQ queues areexclusive: when a client disconnects (through close or
crash), RabbitMQ will delete the queue and unbind the groups.Once a connection has been created, it pollutes the event loop so thatasync_to_sync()will destroy the connection if it was created withinasync_to_sync(). Each connection starts a background async loop that pulls
messages from RabbitMQ and routes them to receiver queues; eachreceive()queries receiver queues. Empty queues with no connections are deleted.Deviations from the Channel Layer SpecificationTheChannel Layer Specificationbends to
Redis-related restrictions. RabbitMQ cannot emulate Redis. Here are the
differences:No ``flush`` extension: To flush all state, simply disconnect all clients.
(RabbitMQ won’t allow one client to delete another client’s data structures.)No ``group_expiry`` option: Thegroup_expiry optionrecovers when agroup_add()has no matchinggroup_discard(). But the
“group membership expiry” logic has a fatal flaw: it disconnects legitimate
members.channels_rabbitmqaddresses each root problem instead:Web-server crash: RabbitMQ wipes all state related to a web server when
the web server disconnects. There’s no problem here forgroup_expiryto solve.Programming errors: You may err and callgroup_add()without
eventually callinggroup_discard(). Redis can’t detect this
programming error (because it can’t detect web-server crashes). RabbitMQ
can. Thelocal_expiryoption keeps your site running after you
erroneously miss agroup_discard(). The channel layer warns when
discarding expired messages. Monitor your server logs to detect your
errors.No “normal channels”:Normal channelsare job queues. In most projects, “normal channel” readers are worker
processes, ideally divorced from Websockets and Django.If you want an async, RabbitMQ-based job queue, investigatecarehare.If you’re using Celery with the same RabbitMQ server, you canpublish
messages from Celery, too.DependenciesYou’ll need Python 3.8+ and a RabbitMQ server.If you have Docker, here’s how to start a development server:ssl/prepare-certs.sh  # Create SSL certificates used in tests
docker run --rm -it \
     -p 5671:5671 \
     -p 5672:5672 \
     -p 15672:15672 \
     -v "/$(pwd)"/ssl:/ssl \
     -e RABBITMQ_SSL_CACERTFILE=/ssl/ca.cert \
     -e RABBITMQ_SSL_CERTFILE=/ssl/server.cert \
     -e RABBITMQ_SSL_KEYFILE=/ssl/server.key \
     -e RABBITMQ_SSL_VERIFY=verify_peer \
     -e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=true \
     rabbitmq:3.7.8-management-alpineYou can access the RabbitMQ management interface athttp://localhost:15672.ContributingTo add features and fix bugsFirst, start a development RabbitMQ server:ssl/prepare-certs.sh  # Create SSL certificates used in tests
docker run --rm -it \
     -p 5671:5671 \
     -p 5672:5672 \
     -p 15672:15672 \
     -v "/$(pwd)"/ssl:/ssl \
     -e RABBITMQ_SSL_CACERTFILE=/ssl/ca.cert \
     -e RABBITMQ_SSL_CERTFILE=/ssl/server.cert \
     -e RABBITMQ_SSL_KEYFILE=/ssl/server.key \
     -e RABBITMQ_SSL_VERIFY=verify_peer \
     -e RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT=true \
     rabbitmq:3.8.11-management-alpineNow take on the development cycle:tox# to ensure tests pass.Write new tests intests/and make sure they fail.Write new code inchannels_rabbitmq/to make the tests pass.Submit a pull request.To deployUsesemver.git pushand make sure Travis tests all pass.git tag vX.X.Xgit push--tagsTravisCI will push to PyPi.