Includes/Asgi Example
.. code-block:: python
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
from django.urls import path
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
from chat.consumers import AdminChatConsumer, PublicChatConsumer
application = ProtocolTypeRouter({
# Django's ASGI application to handle traditional HTTP requests
"http": django_asgi_app,
# WebSocket chat handler
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter([
path("chat/admin/", AdminChatConsumer.as_asgi()),
path("chat/", PublicChatConsumer.as_asgi()),
])
)
),
})
---
Releases/1.0.0
1.0.0 Release Notes
===================
Channels 1.0.0 brings together a number of design changes, including some
breaking changes, into our first fully stable release, and also brings the
databinding code out of alpha phase. It was released on 2017/01/08.
The result is a faster, easier to use, and safer Channels, including one major
change that will fix almost all problems with sessions and connect/receive
ordering in a way that needs no persistent storage.
It was unfortunately not possible to make all of the changes backwards
compatible, though most code should not be too affected and the fixes are
generally quite easy.
You must also update Daphne to at least 1.0.0 to have this release of
Channels work correctly.
Major Features
--------------
Channels 1.0 introduces a couple of new major features.
WebSocket accept/reject flow
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rather than be immediately accepted, WebSockets now pause during the handshake
while they send over a message on `websocket.connect, and your application
must either accept or reject the connection before the handshake is completed
and messages can be received.
You must update Daphne to at least 1.0.0 to make this work correctly.
This has several advantages:
* You can now reject WebSockets before they even finish connecting, giving
appropriate error codes to browsers and not letting the browser-side socket
ever get into a connected state and send messages.
* Combined with Consumer Atomicity (below), it means there is no longer any need
for the old "slight ordering" mode, as the connect consumer must run to
completion and accept the socket before any messages can be received and
forwarded onto websocket.receive.
* Any send message sent to the WebSocket will implicitly accept the connection,
meaning only a limited set of connect consumers need changes (see
Backwards Incompatible Changes below)
Consumer Atomicity
~~~~~~~~~~~~~~~~~~
Consumers will now buffer messages you try to send until the consumer completes
and then send them once it exits and the outbound part of any decorators have
been run (even if an exception is raised).
This makes the flow of messages much easier to reason about - consumers can now
be reasoned about as atomic blocks that run and then send messages, meaning that
if you send a message to start another consumer you're guaranteed that the
sending consumer has finished running by the time it's acted upon.
If you want to send messages immediately rather than at the end of the consumer,
you can still do that by passing the immediately argument:
.. code-block:: python
Channel("thumbnailing-tasks").send({"id": 34245}, immediately=True)
This should be mostly backwards compatible, and may actually fix race
conditions in some apps that were pre-existing.
Databinding Group/Action Overhaul
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, databinding subclasses had to implementgroup_names(instance, action) to return what groups to send an instance's
change to of the type action. This had flaws, most notably when what was
actually just a modification to the instance in question changed its
permission status so more clients could see it; to those clients, it should
instead have been "created".
Now, Channels just calls group_names(instance), and you should return what
groups can see the instance at the current point in time given the instance
you were passed. Channels will actually call the method before and after changes,
comparing the groups you gave, and sending out create, update or delete messages
to clients appropriately.
Existing databinding code will need to be adapted; see the
"Backwards Incompatible Changes" section for more.
Demultiplexer Overhaul
~~~~~~~~~~~~~~~~~~~~~~
Demuliplexers have changed to remove the behaviour where they re-sent messages
onto new channels without special headers, and instead now correctly split out
incoming messages into sub-messages that still look like websocket.receive
messages, and directly dispatch these to the relevant consumer.
They also now forward all websocket.connect and websocket.disconnect
messages to all of their sub-consumers, so it's much easier to compose things
together from code that also works outside the context of multiplexing.
For more, read the updated /generic docs.
Delay Server
~~~~~~~~~~~~
A built-in delay server, launched with manage.py rundelay, now ships if you
wish to use it. It needs some extra initial setup and uses a database for
persistence; see /delay for more information.
Minor Changes
-------------
* Serializers can now specify fields as __all__ to auto-include all fields,
and exclude to remove certain unwanted fields.
* runserver respects FORCE_SCRIPT_NAME
* Websockets can now be closed with a specific code by calling close(status=4000)
* enforce_ordering no longer has a slight mode (because of the accept
flow changes), and is more efficient with session saving.
* runserver respects --nothreading and only launches one worker, takes
a --http-timeout option if you want to override it from the default 60,
* A new @channel_and_http_session decorator rehydrates the HTTP session out
of the channel session if you want to access it inside receive consumers.
* Streaming responses no longer have a chance of being cached.
* request.META['SERVER_PORT'] is now always a string.
* http.disconnect now has a path key so you can route it.
* Test client now has a send_and_consume method.
Backwards Incompatible Changes
------------------------------
Connect Consumers
~~~~~~~~~~~~~~~~~
If you have a custom consumer for websocket.connect, you must ensure that
it either:
* Sends at least one message onto the reply_channel that generates a
WebSocket frame (either bytes or text is set), either directly
or via a group.
* Sends a message onto the reply_channel that is {"accept": True},
to accept a connection without sending data.
* Sends a message onto the reply_channel that is {"close": True},
to reject a connection mid-handshake.
Many consumers already do the former, but if your connect consumer does not
send anything you MUST now send an accept message or the socket will remain
in the handshaking phase forever and you'll never get any messages.
All built-in Channels consumers (e.g. in the generic consumers) have been
upgraded to do this.
You must update Daphne to at least 1.0.0 to make this work correctly.
Databinding group_names
~~~~~~~~~~~~~~~~~~~~~~~
If you have databinding subclasses, you will have implementedgroup_names(instance, action), which returns the groups to use based on the
instance and action provided.
Now, instead, you must implement group_names(instance), which returns the
groups that can see the instance as it is presented for you; the action
results will be worked out for you. For example, if you want to only show
objects marked as "admin_only" to admins, and objects without it to everyone,
previously you would have done:
.. code-block:: python
def group_names(self, instance, action):
if instance.admin_only:
return ["admins"]
else:
return ["admins", "non-admins"]
Because you did nothing based on the action (and if you did, you would
have got incomplete messages, hence this design change), you can just change
the signature of the method like this:
.. code-block:: python
def group_names(self, instance):
if instance.admin_only:
return ["admins"]
else:
return ["admins", "non-admins"]
Now, when an object is updated to have admin_only = True, the clients
in the non-admins group will get a delete message, while those in
the admins group will get an update message.
Demultiplexers
~~~~~~~~~~~~~~
Demultiplexers have changed from using a mapping dict, which mapped stream
names to channels, to using a consumers dict which maps stream names
directly to consumer classes.
You will have to convert over to using direct references to consumers, change
the name of the dict, and then you can remove any channel routing for the old
channels that were in mapping from your routes.
Additionally, the Demultiplexer now forwards messages as they would look from
a direct connection, meaning that where you previously got a decoded object
through you will now get a correctly-formatted websocket.receive message
through with the content as a text key, JSON-encoded. You will also
now have to handle websocket.connect and websocket.disconnect messages.
Both of these issues can be solved using the JsonWebsocketConsumer generic
consumer, which will decode for you and correctly separate connection and
disconnection handling into their own methods.
---
Releases/1.0.1
1.0.1 Release Notes
===================
Channels 1.0.1 is a minor bugfix release, released on 2017/01/09.
Changes
-------
* WebSocket generic views now accept connections by default in their connect
handler for better backwards compatibility.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.0.2
1.0.2 Release Notes
===================
Channels 1.0.2 is a minor bugfix release, released on 2017/01/12.
Changes
-------
* Websockets can now be closed from anywhere using the new WebsocketCloseException,
available as channels.exceptions.WebsocketCloseException(code=None). There is
also a generic ChannelSocketException you can base any exceptions on that,
if it is caught, gets handed the current message in a run method, so you
can do custom behaviours.
* Calling Channel.send or Group.send from outside a consumer context
(i.e. in tests or management commands) will once again send the message immediately,
rather than putting it into the consumer message buffer to be flushed when the
consumer ends (which never happens)
* The base implementation of databinding now correctly only calls group_names(instance),
as documented.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.0.3
1.0.3 Release Notes
===================
Channels 1.0.3 is a minor bugfix release, released on 2017/02/01.
Changes
-------
* Database connections are no longer force-closed after each test is run.
* Channel sessions are not re-saved if they're empty even if they're marked as
modified, allowing logout to work correctly.
* WebsocketDemultiplexer now correctly does sessions for the second/third/etc.
connect and disconnect handlers.
* Request reading timeouts now correctly return 408 rather than erroring out.
* The rundelay delay server now only polls the database once per second,
and this interval is configurable with the --sleep option.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.0
1.1.0 Release Notes
===================
Channels 1.1.0 introduces a couple of major but backwards-compatible changes,
including most notably the inclusion of a standard, framework-agnostic JavaScript
library for easier integration with your site.
Major Changes
-------------
* Channels now includes a JavaScript wrapper that wraps reconnection and
multiplexing for you on the client side. For more on how to use it, see the
javascript documentation.
* Test classes have been moved from channels.tests to channels.test
to better match Django. Old imports from channels.tests will continue to
work but will trigger a deprecation warning, and channels.tests will be
removed completely in version 1.3.
Minor Changes & Bugfixes
------------------------
* Bindings now support non-integer fields for primary keys on models.
* The enforce_ordering decorator no longer suffers a race condition where
it would drop messages under high load.
* runserver no longer errors if the staticfiles app is not enabled in Django.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.1
1.1.1 Release Notes
===================
Channels 1.1.1 is a bugfix release that fixes a packaging issue with the JavaScript files.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* The JavaScript binding introduced in 1.1.0 is now correctly packaged and
included in builds.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.2
1.1.2 Release Notes
===================
Channels 1.1.2 is a bugfix release for the 1.1 series, released on
April 1st, 2017.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* Session name hash changed to SHA-1 to satisfy FIPS-140-2.
* scheme key in ASGI-HTTP messages now translates into request.is_secure()
correctly.
* WebsocketBridge now exposes the underlying WebSocket as .socket.
Backwards Incompatible Changes
------------------------------
* When you upgrade all current channel sessions will be invalidated; you
should make sure you disconnect all WebSockets during upgrade.
---
Releases/1.1.3
1.1.3 Release Notes
===================
Channels 1.1.3 is a bugfix release for the 1.1 series, released on
April 5th, 2017.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* enforce_ordering now works correctly with the new-style process-specific
channels
* ASGI channel layer versions are now explicitly checked for version compatibility
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.4
1.1.4 Release Notes
===================
Channels 1.1.4 is a bugfix release for the 1.1 series, released on
June 15th, 2017.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* Pending messages correctly handle retries in backlog situations
* Workers in threading mode now respond to ctrl-C and gracefully exit.
* request.meta['QUERY_STRING'] is now correctly encoded at all times.
* Test client improvements
* ChannelServerLiveTestCase added, allows an equivalent of the Django
LiveTestCase.
* Decorator added to check Origin headers (allowed_hosts_only)
* New TEST_CONFIG setting in CHANNEL_LAYERS that allows varying of
the channel layer for tests (e.g. using a different Redis install)
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.5
1.1.5 Release Notes
===================
Channels 1.1.5 is a packaging release for the 1.1 series, released on
June 16th, 2017.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* The Daphne dependency requirement was bumped to 1.3.0.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/1.1.6
1.1.6 Release Notes
===================
Channels 1.1.5 is a packaging release for the 1.1 series, released on
June 28th, 2017.
Major Changes
-------------
None.
Minor Changes & Bugfixes
------------------------
* The runserver server_cls override no longer fails with more modern
Django versions that pass an ipv6 parameter.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.0.0
2.0.0 Release Notes
===================
Channels 2.0 is a major rewrite of Channels, introducing a large amount of
changes to the fundamental design and architecture of Channels. Notably:
* Data is no longer transported over a channel layer between protocol server
and application; instead, applications run inside their protocol servers
(like with WSGI).
* To achieve this, the entire core of channels is now built around Python's
asyncio framework and runs async-native down until it hits either a
Django view or a synchronous consumer.
* Python 2.7 and 3.4 are no longer supported.
More detailed information on the changes and tips on how to port your
applications can be found in our /one-to-two documentation in the 2.x
docs version.
Backwards Incompatible Changes
------------------------------
Channels 2 is regrettably not backwards-compatible at all with Channels 1
applications due to the large amount of re-architecting done to the code and
the switch from synchronous to asynchronous runtimes.
A migration guide is available in the 2.x docs version, and a lot of the basic
concepts are the same, but the basic class structure and imports have changed.
Our apologies for having to make a breaking change like this, but it was the
only way to fix some of the fundamental design issues in Channels 1. Channels 1
will continue to receive security and data-loss fixes for the foreseeable
future, but no new features will be added.
---
Releases/2.0.1
2.0.1 Release Notes
===================
Channels 2.0.1 is a patch release of channels, adding a couple of small
new features and fixing one bug in URL resolution.
As always, when updating Channels make sure to also update its dependencies
(asgiref and daphne) as these also get their own bugfix updates, and
some bugs that may appear to be part of Channels are actually in those packages.
New Features
------------
* There are new async versions of the Websocket generic consumers,
AsyncWebsocketConsumer and AsyncJsonWebsocketConsumer. Read more/topics/consumers
about them in :doc:.
* The old allowed_hosts_only decorator has been removed (it was
accidentally included in the 2.0 release but didn't work) and replaced with
a new OriginValidator and AllowedHostsOriginValidator set of/topics/security
ASGI middleware. Read more in :doc:.
Bugfixes
--------
* A bug in URLRouter which didn't allow you to match beyond the first
URL in some situations has been resolved, and a test suite was added for
URL resolution to prevent it happening again.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.0.2
2.0.2 Release Notes
===================
Channels 2.0.2 is a patch release of Channels, fixing a bug in the database
connection handling.
As always, when updating Channels make sure to also update its dependencies
(asgiref and daphne) as these also get their own bugfix updates, and
some bugs that may appear to be part of Channels are actually in those packages.
New Features
------------
* There is a new channels.db.database_sync_to_async wrapper that is like
sync_to_async but also closes database connections for you. You can/topics/databases
read more about usage in :doc:.
Bugfixes
--------
* SyncConsumer and all its descendant classes now close database connections
when they exit.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.0
2.1.0 Release Notes
===================
Channels 2.1 brings a few new major changes to Channels as well as some more
minor fixes. In addition, if you've not yet seen it, we now have a long-form
:doc:tutorial </tutorial/index> to better introduce some of the concepts
and sync versus async styles of coding.
Major Changes
-------------
Async HTTP Consumer
~~~~~~~~~~~~~~~~~~~
There is a new native-async HTTP consumer class,channels.generic.http.AsyncHttpConsumer. This allows much easier writing
of long-poll endpoints or other long-lived HTTP connection handling that
benefits from native async support.
You can read more about it in the :doc:/topics/consumers documentation.
WebSocket Consumers
~~~~~~~~~~~~~~~~~~~
These consumer classes now all have built-in group join and leave functionality,
which will make a consumer join all group names that are in the iterablegroups on the consumer class (this can be a static list or a @property
method).
In addition, the accept methods on both variants now take an optionalsubprotocol argument, which will be sent back to the WebSocket client as
the subprotocol the server has selected. The client's advertised subprotocols
can, as always, be found in the scope as scope["subprotocols"].
Nested URL Routing
~~~~~~~~~~~~~~~~~~
URLRouter instances can now be nested inside each other and, like Django's
URL handling and include, will strip off the matched part of the URL in the
outer router and leave only the unmatched portion for the inner router, allowing
reusable routing files.
Note that you cannot use the Django include function inside of theURLRouter as it assumes a bit too much about what it is given as its
left-hand side and will terminate your regular expression/URL pattern wrongly.
Login and Logout
~~~~~~~~~~~~~~~~
As well as overhauling the internals of the AuthMiddleware, there are now
also login and logout async functions you can call in consumers to
log users in and out of the current session.
Due to the way cookies are sent back to clients, these come with some caveats;
read more about them and how to use them properly in :doc:/topics/authentication.
In-Memory Channel Layer
~~~~~~~~~~~~~~~~~~~~~~~
The in-memory channel layer has been extended to have full expiry and group
support so it should now be suitable for drop-in replacement for most
test scenarios.
Testing
~~~~~~~
The ChannelsLiveServerTestCase has been rewritten to use a new method for
launching Daphne that should be more resilient (and faster), and now shares
code with the Daphne test suite itself.
Ports are now left up to the operating
system to decide rather than being picked from within a set range. It also now
supports static files when the Django staticfiles app is enabled.
In addition, the Communicator classes have gained a receive_nothing method/topics/testing
that allows you to assert that the application didn't send anything, rather
than writing this yourself using exception handling. See more in the
:doc: documentation.
Origin header validation
~~~~~~~~~~~~~~~~~~~~~~~~
As well as removing the print statements that accidentally got into the
last release, this has been overhauled to more correctly match against headers
according to the Origin header spec and align with Django's ALLOWED_HOSTS
setting.
It can now also enforce protocol (http versus https) and port, both
optionally.
Bugfixes & Small Changes
------------------------
* print statements that accidentally got left in the Origin validation
code were removed.
* The runserver command now shows the version of Channels you are running.
* Orphaned tasks that may have caused warnings during test runs or occasionally
live site traffic are now correctly killed off rather than letting them die
later on and print warning messages.
* WebsocketCommunicator now accepts a query string passed into the
constructor and adds it to the scope rather than just ignoring it.
* Test handlers will correctly handle changing the CHANNEL_LAYERS setting
via decorators and wipe the internal channel layer cache.
* SessionMiddleware can be safely nested inside itself rather than causing
a runtime error.
Backwards Incompatible Changes
------------------------------
* The format taken by the OriginValidator for its domains has changed and
*.example.com is no longer allowed; instead, use .example.com to match
a domain and all its subdomains.
* If you previously nested URLRouter instances inside each other both would
have been matching on the full URL before, whereas now they will match on the
unmatched portion of the URL, meaning your URL routes would break if you had
intended this usage.
---
Releases/2.1.1
2.1.1 Release Notes
===================
Channels 2.1.1 is a bugfix release for an important bug in the new async
authentication code.
Major Changes
-------------
None.
Bugfixes & Small Changes
------------------------
Previously, the object in scope["user"] was one of Django's
SimpleLazyObjects, which then called our get_user async function viaasync_to_sync.
This worked fine when called from SyncConsumers, but because
async environments do not run attribute access in an async fashion, when
the body of an async consumer tried to call it, the asgiref library
flagged an error where the code was trying to call a synchronous function
during a async context.
To fix this, the User object is now loaded non-lazily on application startup.
This introduces a blocking call during the synchronous application
constructor, so the ASGI spec has been updated to recommend that constructors
for ASGI apps are called in a threadpool and Daphne 2.1.1 implements this
and is recommended for use with this release.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.2
2.1.2 Release Notes
===================
Channels 2.1.2 is another bugfix release in the 2.1 series.
Special thanks to people at the DjangoCon Europe sprints who helped out with
several of these fixes.
Major Changes
-------------
Session and authentication middleware has been overhauled to be non-blocking.
Previously, these middlewares potentially did database or session store access
in the synchronous ASGI constructor, meaning they would block the entire event
loop while doing so.
Instead, they have now been modified to add LazyObjects into the scope in the
places where the session or user will be, and then when the processing goes
through their asynchronous portion, those stores are accessed in a non-blocking
fashion.
This should be an un-noticeable change for end users, but if you see weird
behaviour or an unresolved LazyObject, let us know.
Bugfixes & Small Changes
------------------------
* AsyncHttpConsumer now has a disconnect() method you can override if you
want to perform actions (such as leaving groups) when a long-running HTTP
request disconnects.
* URL routing context now includes default arguments from the URLconf in the
context's url_route key, alongside captured arguments/groups from the
URL pattern.
* The FORCE_SCRIPT_NAME setting is now respected in ASGI mode, and lets you
override where Django thinks the root URL of your application is mounted.
* ALLOWED_HOSTS is now set correctly during LiveServerTests, meaning you will
no longer get 400 Bad Request errors during these test runs.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.3
2.1.3 Release Notes
===================
Channels 2.1.3 is another bugfix release in the 2.1 series.
Bugfixes & Small Changes
------------------------
An ALLOWED_ORIGINS value of "" will now also allow requests without a Host
header at all (especially important for tests)
* The request.path value is now correct in cases when a server has SCRIPT_NAME
set.
* Errors that happen inside channel listeners inside a runworker or Worker
class are now raised rather than suppressed.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.4
2.1.4 Release Notes
===================
Channels 2.1.4 is another bugfix release in the 2.1 series.
Bugfixes & Small Changes
------------------------
* Django middleware is now cached rather than instantiated per request
resulting in a significant speed improvement. Some middleware took seconds to
load and as a result Channels was unusable for HTTP serving before.
* ChannelServerLiveTestCase now serves static files again.
* Improved error message resulting from bad Origin headers.
* runserver logging now goes through the Django logging framework to match
modern Django.
* Generic consumers can now have non-default channel layers - set the
channel_layer_alias property on the consumer class
* Improved error when accessing scope['user'] before it's ready - the user
is not accessible in the constructor of ASGI apps as it needs an async
environment to load in. Previously it raised a generic error when you tried to
access it early; now it tells you more clearly what's happening.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.5
2.1.5 Release Notes
===================
Channels 2.1.5 is another bugfix release in the 2.1 series.
Bugfixes & Small Changes
------------------------
* Django middleware caching now works on Django 1.11 and Django 2.0.
The previous release only ran on 2.1.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.6
2.1.6 Release Notes
===================
Channels 2.1.6 is another bugfix release in the 2.1 series.
Bugfixes & Small Changes
------------------------
* HttpCommunicator now extracts query strings correctly from its provided
arguments
* AsyncHttpConsumer provides channel layer attributes following the same
conventions as other consumer classes
* Prevent late-Daphne import errors where importing daphne.server didn't
work due to a bad linter fix.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.1.7
2.1.7 Release Notes
===================
Channels 2.1.7 is another bugfix release in the 2.1 series, and the last
release (at least for a long while) with Andrew Godwin as the primary
maintainer.
Thanks to everyone who has used, supported, and contributed to Channels over
the years, and I hope we can keep it going with community support for a good
while longer.
Bugfixes & Small Changes
------------------------
* HTTP request body size limit is now enforced (the one set by the
DATA_UPLOAD_MAX_MEMORY_SIZE setting)
* database_sync_to_async now closes old connections before it runs code,
which should prevent some connection errors in long-running pages or tests.
* The auth middleware closes old connections before it runs, to solve similar
old-connection issues.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.2.0
2.2.0 Release Notes
===================
Channels 2.2.0 updates the requirements for ASGI version 3, and the supporting
Daphne v2.3 release.
Backwards Incompatible Changes
------------------------------
None.
---
Releases/2.3.0
2.3.0 Release Notes
===================
Channels 2.3.0 updates the AsgiHandler HTTP request body handling to use a
spooled temporary file, rather than reading the whole request body into memory.
This significantly reduces the maximum memory requirements when serving Django
views, and protects from DoS attacks, whilst still allowing large file
uploads — a combination that had previously been difficult.
Many thanks to Ivan Ergunov for his work on the improvements! 🎩
Backwards Incompatible Changes
------------------------------
As a result of the reworked body handling, AsgiRequest.__init__() is
adjusted to expect a file-like stream, rather than the whole body as
bytes.
Test cases instantiating requests directly will likely need to be updated to
wrap the provided body in, e.g., io.BytesIO.
Next Up...
----------
We're looking to address a few issues around AsyncHttpConsumer. Any
human-power available to help on that, truly appreciated. 🙂
---
Releases/2.4.0
2.4.0 Release Notes
===================
Channels 2.4 brings compatibility with Django 3.0s async_unsafe() checks.
(Specifically we ensure session save calls are made inside an asgirefdatabase_sync_to_async().)
If you are using Daphne, it is recommended that you install Daphne version
2.4.1 or later for full compatibility with Django 3.0.
Backwards Incompatible Changes
------------------------------
In line with the guidance provided by Django's supported versions policy we now
also drop support for all Django versions before 2.2, which is the current LTS.
---
Releases/3.0.0
3.0.0 Release Notes
===================
The Channels 3 update brings Channels into line with Django's own async ASGI
support, introduced with Django 3.0.
Channels now integrates with Django's async HTTP handling, whilst continuing to
support WebSockets and other exciting consumer types.
Channels 3 supports Django 3.x and beyond, as well continuing to support the
Django 2.2 LTS. We will support Django 2.2 at least until the Django 3.2 LTS is
released, yet may drop support after that, but before Django 2.2 is officially
end-of-life.
Likewise, we support Python 3.6+ but we strongly advise you to update to
the latest Python versions, so 3.9 at the time of release.
In both our Django and Python support, we reflect the reality that async Python
and async Django are still both evolving rapidly. Many issues we see simply
disappear if you update. Whatever you are doing with async, you should make
sure you're on the latest versions.
The highlight of this release is the upgrade to ASGI v3, which allows integration
with Django's ASGI support. There are also two additional deprecations that you
will need to deal with if you are updating an existing application.
Update to ASGI 3
----------------
Consumers are now ASGI 3 single-callables* with the signature::
application(scope, receive, send)
For generic consumers this change should be largely transparent, but you will
need to update __init__() (no longer taking the scope) and __call__()
(now taking the scope) if you implemented these yourself.
* Consumers now have an as_asgi() class method you need to call when
setting up your routing::
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
This returns an ASGI application that will instantiate the consumer
per-request. It's similar to Django's as_view(), which serves the same purpose. You
can pass in keyword arguments for initialization if your consumer requires them.
* Middleware will also need to be updated to the ASGI v3 signature. The
channels.middleware.BaseMiddleware class is simplified, and available as
an example. You probably don't need to actually subclass it under ASGI 3.
Deprecations
------------
* Using ProtocolTypeRouter without an explicit "http" key is now
deprecated.
Following Django conventions, your entry point script should be named
asgi.py, and you should use Django's get_asgi_application(), that is
used by Django's default asgi.py template to route the "http"
handler::
from django.core.asgi import get_asgi_application
application = ProtocolTypeRouter({
"http": get_asgi_application(),
# Other protocols here.
})
Once the deprecation is removed, when we drop support for Django 2.2, not
specifying an "http" key will mean that your application will not handle
HTTP requests.
* The Channels built-in HTTP protocol AsgiHandler is also deprecated. You
should update to Django 3.0 or higher and use Django's
get_asgi_application(). Channel's AsgiHandler will be removed when we
drop support for Django 2.2.
---
Releases/3.0.1
3.0.1 Release Notes
===================
Channels 3.0.1 fixes a bug in Channels 3.0.
Bugfixes
--------
* Fixes a bug in Channels 3.0 where SessionMiddleware would not correctly
isolate per-instance scopes.
---
Releases/3.0.2
3.0.2 Release Notes
===================
Channels 3.0.2 fixes a bug in Channels 3.0.1
Bugfixes
--------
* Fixes a bug in Channels 3.0 where StaticFilesWrapper was not updated to
the ASGI 3 single-callable interface.
* Users of the runworker command should ensure to update asgiref to
version 3.3.1 or later, where an issue in asgiref.server.StatelessServer
was addressed.
---
Releases/3.0.3
3.0.3 Release Notes
===================
Channels 3.0.3 fixes a security issue in Channels 3.0.2
CVE-2020-35681: Potential leakage of session identifiers using legacy AsgiHandler
-------------------------------------------------------------------------------------
The legacy channels.http.AsgiHandler class, used for handling HTTP type
requests in an ASGI environment prior to Django 3.0, did not correctly separate
request scopes in Channels 3.0. In many cases this would result in a crash but,
with correct timing responses could be sent to the wrong client, resulting in
potential leakage of session identifiers and other sensitive data.
This issue affects Channels 3.0.x before 3.0.3, and is resolved in Channels
3.0.3.
Users of ProtocolTypeRouter not explicitly specifying the handler for the'http' key, or those explicitly using channels.http.AsgiHandler, likely
to support Django v2.2, are affected and should update immediately.
Note that both an unspecified handler for the 'http' key and usingchannels.http.AsgiHandler are deprecated, and will raise a warning, from
Channels v3.0.0
This issue affects only the legacy channels provided class, and not Django's
similar ASGIHandler, available from Django 3.0. It is recommended to update
to Django 3.0+ and use the Django provided ASGIHandler.
A simplified asgi.py script will look like this:
.. code-block:: python
import os
from django.core.asgi import get_asgi_application
# Fetch Django ASGI application early to ensure AppRegistry is populated
# before importing consumers and AuthMiddlewareStack that may import ORM
# models.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django_asgi_app = get_asgi_application()
# Import other Channels classes and consumers here.
from channels.routing import ProtocolTypeRouter, URLRouter
application = ProtocolTypeRouter({
# Explicitly set 'http' key using Django's ASGI application.
"http": django_asgi_app,
),
})
Please see :doc:/deploying for a more complete example.
---
Releases/3.0.4
3.0.4 Release Notes
===================
Channels 3.0.4 is a bugfix release in the 3.0 series.
Bugfixes & Small Changes
------------------------
* Usage of urlparse in OriginValidator is corrected to maintain
compatibility with recent point-releases of Python.
* The import of django.contrib.auth.models.AnonymousUser in
channels.auth is deferred until runtime, in order to avoid errors if
AuthMiddleware or AuthMiddlewareStack were imported before
django.setup() was run.
* CookieMiddleware adds support for the samesite flag.
* WebsocketConsumer.init() and AsyncWebsocketConsumer.init() no longersuper()
make a bad call to object.init().
Backwards Incompatible Changes
------------------------------
None.
---
Releases/3.0.5
3.0.5 Release Notes
===================
Channels 3.0.5 is a bugfix release in the 3.0 series.
Bugfixes & Small Changes
------------------------
* Removed use of providing_args keyword argument to consumer started
signal, as support for this was removed in Django 4.0.
Backwards Incompatible Changes
------------------------------
* Drops support for end-of-life Python 3.6 and Django 3.0 and 3.1.
---
Releases/4.0.0
4.0.0 Release Notes
===================
Channels 4 is the next major version of the Channels package. Together with the
matching Daphne v4 and channels-redis v4 releases, it updates dependencies,
fixes issues, and removes outdated code. It so provides the foundation for
Channels development going forward.
In most cases, you can update now by updating channels, daphne, andchannels-redis as appropriate, with pip, and by adding daphne at
the top of your INSTALLED_APPS setting.
First pip::
pip install -U 'channels[daphne]' channels-redis
Then in your Django settings file::
INSTALLED_APPS = [
"daphne",
...
]
Read on for the details.
Updated Python and Django support
---------------------------------
In general Channels will try to follow Python and Django supported versions.
As of release, that means Python 3.7, 3.8, 3.9, and 3.10, as well as Django
3.2, 4.0, and 4.1 are currently supported.
As a note, we reserve the right to drop older Python versions, or the older
Django LTS, once the newer one is released, before their official end-of-life
if this is necessary to ease development.
Dropping older Python and Django versions will be done in minor version
releases, and will not be considered to require a major version change.
The async support in both Python and Django continues to evolve rapidly. We
advise you to always upgrade to the latest versions in order to avoid issues in
older versions if you're building an async application.
* Dropped support for Python 3.6.
* Minimum Django version is now Django 3.2.
* Added compatibility with Django 4.1.
Decoupling of the Daphne application server
-------------------------------------------
In order to allow users of other ASGI servers to use Channels without the
overhead of Daphne and Twisted, the Daphne application server is now an
optional dependency, installable either directly or with the daphne extra,
as per the pip example above.
* Where Daphne is used daphne>=4.0.0 is required. The channels[daphne] extra assures this.
* The runserver command is moved to the daphne package.
In order to use the runserver command, add daphne to your
INSTALLED_APPS, before django.contrib.staticfiles::
INSTALLED_APPS = [
"daphne",
...
]
There is a new system check to ensure this ordering.
Note, the runworker command remains a part of the channels app.
* Use of ChannelsLiveServerTestCase still requires Daphne.
Removal of the Django application wrappers
------------------------------------------
In order to add initial ASGI support to Django, Channels originally provided
tools for wrapping your Django application and serving it under ASGI. This
included an ASGI handler class, an ASGI HTTP request object, and an ASGI
compatible version of the staticfiles handler for use with runserver
Improved equivalents to all of these are what has been added to Django since
Django version 3.0. As such serving of Django HTTP applications (whether using
sync or async views) under ASGI is now Django's responsibility, and the
matching Channels classes have been removed.
Use of these classes was deprecated in Channels v3 and, if you've already moved
to the Django equivalents there is nothing further to do.
* Removed deprecated static files handling in favor of
django.contrib.staticfiles.
* Removed the deprecated AsgiHandler, which wrapped Django views, in favour of
Django's own ASGI support. You should use Django's get_asgi_application
to provide the http handler for ProtocolTypeRouter, or an appropriate
path for URLRouter, in order to route your Django application.
* The supporting AsgiRequest is also removed, as it was only used for
AsgiHandler.
* Removed deprecated automatic routing of http protocol handler in
ProtocolTypeRouter. You must explicitly register the http handler in
your application if using ProtocolTypeRouter.
The minimal asgi.py file routing the Django ASGI application under aProtocolTypeRouter will now look something like this::
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
})
i.e. We use Django's get_asgi_application(), and explicitly route anhttp handler for ProtocolTypeRouter. This is merely for illustration of
the changes. Please see the docs for more complete examples.
Other changes
-------------
* The use of the guarantee_single_callable() compatibility shim is removed.
All applications must be ASGI v3 single-callables.
* Removed the consumer_started and consumer_finished signals, unused
since the 2.0 rewrite.
* Fixed ChannelsLiveServerTestCase when running on systems using the
spawn multiprocessing start method, such as macOS and Windows.
---
Releases/4.1.0
4.1.0 Release Notes
===================
Channels 4.1 is maintenance release in the 4.x series.
Python and Django support
-------------------------
* A Python version of 3.8 or higher is required.
* Django 4.2 is now the minimum supported version.
Bugfixes & Small Changes
------------------------
* Exceptions in HttpConsumer are now correctly propagated.
Thanks to Adam Johnson.
* URLRouter is updated for compatibility with in-development changes in Django.
Thanks to Adam Johnson.
* URLRouter is updated to correctly handle root_path.
Thanks to Alejandro R. Sedeño.
* Websocket consumers are updated for newer ASGI spec versions, adding the
headers parameter for the accept event, and reason for the
close event.
Thanks to Kristján Valur Jónsson.
---
Releases/4.2.0
4.2.0 Release Notes
===================
Channels 4.2 introduces a couple of major but backwards-compatible
changes, including most notably enhanced async support and fixing
a long-standing bug where tests would try and close db connections
and erroneously fail.
Additionally, support has been added for Django 5.1.
Enhanced Async Support
----------------------
Support for asynchronous consumers has been greatly improved.
The documentation has been updated to reflect the async ORM
features added in Django 4.2. A new channels.db.aclose_old_connections
function has been added to easily close old database connections
in async consumers.
Warning: Channels now automatically closes connections in async
consumers before a new connection, after receiving message (but
before dispatching to consumer code), and after disconnecting.
This change has been made to more closely align with Django's
request/response cycle, and to help users avoid attempting
to use stale/broken connections.
Notably, Channels does NOT close connections before or after
a consumer sends data. This is to avoid database churn and
more closely align with user expectations. Instead, users are
expected to call aclose_old_connections occasionally during
long-lived async connections.
Additionally, channels will automatically use the new async
interface for sessions if Django 5.1 or greater is installed.
This new interface can be slightly faster in certain cases
as it does not always need to context-switch into synchronous
execution. This does require a backwards-incompatible change to
channels.sessions.InstanceSessionWrapper: the save_sessionasync
function is now . If InstanceSessionWrapper was beingsave_session
subclassed in some way (note that this class is an implementation
detail and not documented) and was being calledawait
or overridden, it will need to be updated to be called with async
or defined as , respectively.
Bugfixes & Small Changes
------------------------
* InMemoryChannelLayer has been greatly improved: it now honors
expiry times and per-channel capacities, has parallel sending
and a safer internal implementation. Note: queue capacities
can no longer be changed after a channel has been created.
Thanks to @devkral (Alexander)
* Database connections are no longer closed inside tests, which
prevents erroneous "Cannot operate on a closed database" errors
when running tets.
Thanks to Jon Janzen.
* An old import override and an unused deprecation message were removed
Thanks to @sevdog (Devid) and Jon Janzen.
* WebsocketCommunicator now has informative assert error messages
Thanks to Karel Hovorka.
* WebsocketConsumer now checks that "text" is not None before attempting
to use it. This improves support for Hypercorn.
Thanks to Joaquín Ossandon.
* BaseChannelLayer now has prototypes on all its methods to improve
the hit-rate for smart autocompleters when users need to author
their own channel layer and need to implement all required methods.
Thanks to Jophy Ye.
---
Releases/4.2.1
4.2.1 Release Notes
===================
Channels 4.2.1 is a bugfix release in the 4.2 series.
Bugfixes & Small Changes
------------------------
* Added official support for Django 5.2 LTS.
* Added official support for Python 3.13.
* Added a warning for the length of the channel layer group names.
---
Releases/4.2.2
4.2.2 Release Notes
===================
Channels 4.2.2 is a bugfix release in the 4.2 series.
Bugfixes & Small Changes
------------------------
* Added fallbacks for old valid channel/group name checks.
These (internal) methods were renamed in v4.2.1 without deprecation. This
release adds (deprecated) fallback aliases to allow time for channel layers
to update.
---
Releases/4.3.0
4.3.0 Release Notes
===================
Channels 4.3 is a maintenance release in the 4.x series.
Bugfixes & Small Changes
------------------------
* Updated asgiref dependency to v3.9+.
The ApplicationCommunicator testing utility will now return its result if
the application is finished when sending input. Assert the
CancelledError` rather than allowing a timeout in your tests if you're
affected by this change.
* Dropped support for EOL Python and Django versions. Python 3.9 is now the
minimum supported version.
* Fixed compatibility of ChannelsLiveServerTestCase with Django 5.2.
* Fixed DB setup for spawned testing subprocess, typically on Windows and macOS.
---
Releases/4.3.1
4.3.1 Release Notes
===================
Channels 4.3.1 is a bugfix release in the 4.3 series.
Bugfixes & Small Changes
------------------------
* Fixed testing live server setup when test DB name was not set.
---
Releases/4.3.2
4.3.2 Release Notes
===================
Channels 4.3.1 is a bugfix release in the 4.3 series.
Bugfixes & Small Changes
------------------------
* Confirmed support for Django 6.0.
* Confirmed support for Python 3.14.
* Added types extra for types-channels stubs. See installation docs.
---
Releases/Index
Release Notes
=============
.. toctree::
:maxdepth: 1
4.3.2
4.3.1
4.3.0
4.2.2
4.2.1
4.2.0
4.1.0
4.0.0
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.4.0
2.3.0
2.2.0
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.2
2.0.1
2.0.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
---
Topics/Authentication
Authentication
==============
Channels supports standard Django authentication out-of-the-box for HTTP and
WebSocket consumers, and you can write your own middleware or handling code
if you want to support a different authentication scheme (for example,
tokens in the URL).
Django authentication
---------------------
The AuthMiddleware in Channels supports standard Django authentication,
where the user details are stored in the session. It allows read-only access
to a user object in the scope.
AuthMiddleware requires SessionMiddleware to function, which itself
requires CookieMiddleware. For convenience, these are also provided
as a combined callable called AuthMiddlewareStack that includes all three.
To use the middleware, wrap it around the appropriate level of consumer
in your asgi.py:
.. code-block:: python
from django.urls import re_path
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
from myapp import consumers
application = ProtocolTypeRouter({
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter([
re_path(r"^front(end)/$", consumers.AsyncChatConsumer.as_asgi()),
])
)
),
})
While you can wrap the middleware around each consumer individually,
it's recommended you wrap it around a higher-level application component,
like in this case the URLRouter.
Note that the AuthMiddleware will only work on protocols that provide
HTTP headers in their scope - by default, this is HTTP and WebSocket.
To access the user, just use self.scope["user"] in your consumer code:
.. code-block:: python
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.user = self.scope["user"]
self.accept()
Custom Authentication
---------------------
If you have a custom authentication scheme, you can write a custom middleware
to parse the details and put a user object (or whatever other object you need)
into your scope.
Middleware is written as a callable that takes an ASGI application and wraps
it to return another ASGI application. Most authentication can just be done
on the scope, so all you need to do is override the initial constructor
that takes a scope, rather than the event-running coroutine.
Here's a simple example of a middleware that just takes a user ID out of the
query string and uses that:
.. code-block:: python
from channels.db import database_sync_to_async
@database_sync_to_async
def get_user(user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return AnonymousUser()
class QueryAuthMiddleware:
"""
Custom middleware (insecure) that takes user IDs from the query string.
"""
def __init__(self, app):
# Store the ASGI application we were passed
self.app = app
async def __call__(self, scope, receive, send):
# Look up user from query string (you should also do things like
# checking if it is a valid user ID, or if scope["user"] is already
# populated).
scope['user'] = await get_user(int(scope["query_string"]))
return await self.app(scope, receive, send)
The same principles can be applied to authenticate over non-HTTP protocols;
for example, you might want to use someone's chat username from a chat protocol
to turn it into a user.
How to log a user in/out
------------------------
Channels provides direct login and logout functions (much like Django'scontrib.auth package does) as channels.auth.login andchannels.auth.logout.
Within your consumer you can await login(scope, user, backend=None)
to log a user in. This requires that your scope has a session object;
the best way to do this is to ensure your consumer is wrapped in aSessionMiddlewareStack or a AuthMiddlewareStack.
You can logout a user with the logout(scope) async function.
If you are in a WebSocket consumer, or logging-in after the first response
has been sent in a http consumer, the session is populated
but will not be saved automatically - you must callscope["session"].save() after login in your consumer code:
.. code-block:: python
from channels.auth import login
class ChatConsumer(AsyncWebsocketConsumer):
...
async def receive(self, text_data):
...
# login the user to this session.
await login(self.scope, user)
# save the session (if the session backend does not access the db you can use sync_to_async)
await database_sync_to_async(self.scope["session"].save)()
When calling login(scope, user), logout(scope) or get_user(scope)
from a synchronous function you will need to wrap them in async_to_sync,
as we only provide async versions:
.. code-block:: python
from asgiref.sync import async_to_sync
from channels.auth import login
class SyncChatConsumer(WebsocketConsumer):
...
def receive(self, text_data):
...
async_to_sync(login)(self.scope, user)
self.scope["session"].save()
.. note::
If you are using a long running consumer, websocket or long-polling
HTTP it is possible that the user will be logged out of their session
elsewhere while your consumer is running. You can periodically use
get_user(scope) to be sure that the user is still logged in.
---
Topics/Channel Layers
Channel Layers
==============
Channel layers allow you to talk between different instances of an application.
They're a useful part of making a distributed realtime application if you don't
want to have to shuttle all of your messages or events through a database.
Additionally, they can also be used in combination with a worker process
to make a basic task queue or to offload tasks - read more in
:doc:/topics/worker.
.. note::
Channel layers are an entirely optional part of Channels. If you don't want
to use them, just leave CHANNEL_LAYERS unset, or set it to the empty
dict {}.
.. warning::
Channel layers have a purely async interface (for both send and receive);
you will need to wrap them in a converter if you want to call them from
synchronous code (see below).
Configuration
-------------
Channel layers are configured via the CHANNEL_LAYERS Django setting.
You can get the default channel layer from a project withchannels.layers.get_channel_layer(), but if you are using consumers, then a copy
is automatically provided for you on the consumer as self.channel_layer.
Redis Channel Layer
*
channels_redis_ is the only official Django-maintained channel layerchannels_redis
supported for production use. The layer uses Redis as its backing store,
and it supports both a single-server and sharded configurations as well as
group support. To use this layer you'll need to install the _
package.
.. _channels_redis: https://pypi.org/project/channels-redis/
In this example, Redis is running on localhost (127.0.0.1) port 6379:
.. code-block:: python
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
In-Memory Channel Layer
*
Channels also comes packaged with an in-memory Channels Layer. This layer can
be helpful in :doc:/topics/testing or for local-development purposes:
.. code-block:: python
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
.. warning::
Do Not Use In Production
In-memory channel layers operate with each process as a separate layer.
This means that no cross-process messaging is possible. As the core value
of channel layers is to provide distributed messaging, in-memory usage will
result in sub-optimal performance, and ultimately data-loss in a
multi-instance environment.
Synchronous Functions
---------------------
By default the send(), group_send(), group_add() and other
functions are async functions, meaning you have to await them. If you need
to call them from synchronous code, you'll need to use the handyasgiref.sync.async_to_sync wrapper:
.. code-block:: python
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.send)("channel_name", {...})
What To Send Over The Channel Layer
-----------------------------------
The channel layer is for high-level application-to-application communication.
When you send a message, it is received by the consumers listening to the group
or channel on the other end. What this means is that you should send high-level
events over the channel layer, and then have consumers handle those events, and
do appropriate low-level networking to their attached client.
For example, a chat application could send events like this over the channel
layer:
.. code-block:: python
await self.channel_layer.group_send(
room.group_name,
{
"type": "chat.message",
"room_id": room_id,
"username": self.scope["user"].username,
"message": message,
}
)
And then the consumers define a handling function to receive those events
and turn them into WebSocket frames:
.. code-block:: python
async def chat_message(self, event):
"""
Called when someone has messaged our chat.
"""
# Send a message down to the client
await self.send_json(
{
"msg_type": settings.MSG_TYPE_MESSAGE,
"room": event["room_id"],
"username": event["username"],
"message": event["message"],
},
)
Any consumer based on Channels' SyncConsumer or AsyncConsumer will
automatically provide you a self.channel_layer and self.channel_name
attribute, which contains a pointer to the channel layer instance and the
channel name that will reach the consumer respectively.
Any message sent to that channel name - or to a group the channel name was
added to - will be received by the consumer much like an event from its
connected client, and dispatched to a named method on the consumer. The name
of the method will be the type of the event with periods replaced by
underscores - so, for example, an event coming in over the channel layer
with a type of chat.join will be handled by the method chat_join.
.. note::
If you are inheriting from the AsyncConsumer class tree, all your
event handlers, including ones for events over the channel layer, must
be asynchronous (async def). If you are in the SyncConsumer class
tree instead, they must all be synchronous (def).
Single Channels
---------------
Each application instance - so, for example, each long-running HTTP request
or open WebSocket - results in a single Consumer instance, and if you have
channel layers enabled, Consumers will generate a unique channel name for
themselves, and start listening on it for events.
This means you can send those consumers events from outside the process -
from other consumers, maybe, or from management commands - and they will react
to them and run code just like they would events from their client connection.
The channel name is available on a consumer as self.channel_name. Here's
an example of writing the channel name into a database upon connection,
and then specifying a handler method for events on it:
.. code-block:: python
class ChatConsumer(WebsocketConsumer):
def connect(self):
# Make a database row with our channel name
Clients.objects.create(channel_name=self.channel_name)
def disconnect(self, close_code):
# Note that in some rare cases (power loss, etc) disconnect may fail
# to run; this naive example would leave zombie channel names around.
Clients.objects.filter(channel_name=self.channel_name).delete()
def chat_message(self, event):
# Handles the "chat.message" event when it's sent to us.
self.send(text_data=event["text"])
Note that, because you're mixing event handling from the channel layer and
from the protocol connection, you need to make sure that your type names do not
clash. It's recommended you prefix type names (like we did here with chat.)
to avoid clashes.
To send to a single channel, just find its channel name (for the example above,
we could crawl the database), and use channel_layer.send:
.. code-block:: python
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
await channel_layer.send("channel_name", {
"type": "chat.message",
"text": "Hello there!",
})
.. _groups:
Groups
------
Obviously, sending to individual channels isn't particularly useful - in most
cases you'll want to send to multiple channels/consumers at once as a broadcast.
Not only for cases like a chat where you want to send incoming messages to
everyone in the room, but even for sending to an individual user who might have
more than one browser tab or device connected.
You can construct your own solution for this if you like using your existing
datastores, or you can use the Groups system built-in to some channel layers. Groups
is a broadcast system that:
* Allows you to add and remove channel names from named groups, and send to
those named groups.
* Provides group expiry for clean-up of connections whose disconnect handler
didn't get to run (e.g. power failure)
They do not allow you to enumerate or list the channels in a group; it's a
pure broadcast system. If you need more precise control or need to know who
is connected, you should build your own system or use a suitable third-party
one.
You use groups by adding a channel to them during connection, and removing it
during disconnection, illustrated here on the WebSocket generic consumer:
.. code-block:: python
# This example uses WebSocket consumer, which is synchronous, and so
# needs the async channel layer functions to be converted.
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def connect(self):
async_to_sync(self.channel_layer.group_add)("chat", self.channel_name)
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)("chat", self.channel_name)
.. note::
Group names are restricted to ASCII alphanumerics, hyphens, and periods
only and are limited to a maximum length of 100 in the default backend.
Then, to send to a group, use group_send, like in this small example
which broadcasts chat messages to every connected socket when combined with
the code above:
.. code-block:: python
class ChatConsumer(WebsocketConsumer):
...
def receive(self, text_data):
async_to_sync(self.channel_layer.group_send)(
"chat",
{
"type": "chat.message",
"text": text_data,
},
)
def chat_message(self, event):
self.send(text_data=event["text"])
Using Outside Of Consumers
--------------------------
You'll often want to send to the channel layer from outside of the scope of
a consumer, and so you won't have self.channel_layer. In this case, you
should use the get_channel_layer function to retrieve it:
.. code-block:: python
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
Then, once you have it, you can just call methods on it. Remember that
channel layers only support async methods, so you can either call it
from your own asynchronous context:
.. code-block:: python
for chat_name in chats:
await channel_layer.group_send(
chat_name,
{"type": "chat.system_message", "text": announcement_text},
)
Or you'll need to use async_to_sync:
.. code-block:: python
from asgiref.sync import async_to_sync
async_to_sync(channel_layer.group_send)("chat", {"type": "chat.force_disconnect"})
---
Topics/Consumers
Consumers
=========
Channels is built around a basic low-level spec called :doc:ASGI </asgi>.
ASGI is more designed for interoperability than for writing complex
applications in. So, Channels provides you with Consumers, a rich abstraction
that allows you to create ASGI applications easily.
Consumers do a couple of things in particular:
* Structure your code as a series of functions to be called whenever an
event happens, rather than making you write an event loop.
* Allow you to write synchronous or async code, and deal with handoffs
and threading for you.
Of course, you are free to ignore consumers and use the other parts of
Channels - like routing, session handling and authentication - with any
ASGI app, but they're generally the best way to write your application code.
.. _sync_to_async:
Basic Layout
------------
A consumer is a subclass of either channels.consumer.AsyncConsumer orchannels.consumer.SyncConsumer. As these names suggest, one will expect
you to write async-capable code, while the other will run your code
synchronously in a threadpool.
Let's look at a basic example of a SyncConsumer:
.. code-block:: python
from channels.consumer import SyncConsumer
class EchoConsumer(SyncConsumer):
def websocket_connect(self, event):
self.send({
"type": "websocket.accept",
})
def websocket_receive(self, event):
self.send({
"type": "websocket.send",
"text": event["text"],
})
This is a very simple WebSocket echo server - it will accept all incoming
WebSocket connections, and then reply to all incoming WebSocket text frames
with the same text.
Consumers are structured around a series of named methods corresponding to thetype value of the messages they are going to receive, with any .
replaced by _. The two handlers above are handling websocket.connect
and websocket.receive messages respectively.
How did we know what event types we were going to get and what would be
in them (like websocket.receive having a text) key? That's because weASGI </asgi>
designed this against the ASGI WebSocket specification, which tells us how
WebSockets are presented - read more about it in :doc: - and
protected this application with a router that checks for a scope type ofwebsocket - see more about that in :doc:/topics/routing.
Apart from that, the only other basic API is self.send(event). This lets
you send events back to the client or protocol server as defined by the
protocol - if you read the WebSocket protocol, you'll see that the dict we
send above is how you send a text frame to the client.
The AsyncConsumer is laid out very similarly, but all the handler methods
must be coroutines, and self.send is a coroutine:
.. code-block:: python
from channels.consumer import AsyncConsumer
class EchoConsumer(AsyncConsumer):
async def websocket_connect(self, event):
await self.send({
"type": "websocket.accept",
})
async def websocket_receive(self, event):
await self.send({
"type": "websocket.send",
"text": event["text"],
})
When should you use AsyncConsumer and when should you use SyncConsumer?
The main thing to consider is what you're talking to. If you call a slow
synchronous function from inside an AsyncConsumer you're going to hold up
the entire event loop, so they're only useful if you're also calling async
code (for example, using HTTPX to fetch 20 pages in parallel).
If you're calling any part of Django's ORM or other synchronous code, you
should use a SyncConsumer, as this will run the whole consumer in a thread
and stop your ORM queries blocking the entire server.
We recommend that you write SyncConsumers by default, and only use
AsyncConsumers in cases where you know you are doing something that would
be improved by async handling (long-running tasks that could be done in
parallel) and you are only using async-native libraries.
If you really want to call a synchronous function from an AsyncConsumer,
take a look at asgiref.sync.sync_to_async, which is the utility that Channels
uses to run SyncConsumers in threadpools, and can turn any synchronous
callable into an asynchronous coroutine.
.. important::
If you want to call the Django ORM from an AsyncConsumer (or any other
asynchronous code), you should use the database_sync_to_async adapter
or use the async versions of the methods (prefixed with a, like aget)./topics/databases
See :doc: for more.
Closing Consumers
~~~~~~~~~~~~~~~~~
When the socket or connection attached to your consumer is closed - either by
you or the client - you will likely get an event sent to you (for example,http.disconnect or websocket.disconnect), and your application instance
will be given a short amount of time to act on it.
Once you have finished doing your post-disconnect cleanup, you need to raisechannels.exceptions.StopConsumer to halt the ASGI application cleanly and
let the server clean it up. If you leave it running - by not raising this
exception - the server will reach its application close timeout (which is
10 seconds by default in Daphne) and then kill your application and raise
a warning.
The generic consumers below do this for you, so this is only needed if you
are writing your own consumer class based on AsyncConsumer orSyncConsumer. However, if you override their __call__ method, or
block the handling methods that it calls from returning, you may still run into
this; take a look at their source code if you want more information.
Additionally, if you launch your own background coroutines, make sure to also
shut them down when the connection is finished, or you'll leak coroutines into
the server.
Channel Layers
~~~~~~~~~~~~~~
Consumers also let you deal with Channel's channel layers, to let them
send messages between each other either one-to-one or via a broadcast system
called groups.
Consumers will use the channel layer default unless
the channel_layer_alias attribute is set when subclassing any
of the provided Consumer classes.
To use the channel layer echo_alias we would set it like so:
.. code-block:: python
from channels.consumer import SyncConsumer
class EchoConsumer(SyncConsumer):
channel_layer_alias = "echo_alias"
You can read more in :doc:/topics/channel_layers.
.. _scope:
Scope
-----
Consumers receive the connection's scope when they are called, which
contains a lot of the information you'd find on the request object in a
Django view. It's available as self.scope inside the consumer's methods.
Scopes are part of the :doc:ASGI specification </asgi>, but here are
some common things you might want to use:
scope["path"], the path on the request. (HTTP and WebSocket)*
scope["headers"], raw name/value header pairs from the request (HTTP and WebSocket)*
scope["method"], the method name used for the request. (HTTP)*
If you enable things like :doc:authentication, you'll also be able to access
the user object as scope["user"], and the URLRouter, for example, will
put captured groups from the URL into scope["url_route"].
In general, the scope is the place to get connection information and where
middleware will put attributes it wants to let you access (in the same way
that Django's middleware adds things to request).
For a full list of what can occur in a connection scope, look at the basic
ASGI spec for the protocol you are terminating, plus any middleware or routing
code you are using. The web (HTTP and WebSocket) scopes are available in
the Web ASGI spec <https://github.com/django/asgiref/blob/master/specs/www.rst>_.
Generic Consumers
-----------------
What you see above is the basic layout of a consumer that works for any
protocol. Much like Django's generic views, Channels ships with
generic consumers that wrap common functionality up so you don't need to
rewrite it, specifically for HTTP and WebSocket handling.
WebsocketConsumer
~~~~~~~~~~~~~~~~~
Available as channels.generic.websocket.WebsocketConsumer, this
wraps the verbose plain-ASGI message sending and receiving into handling that
just deals with text and binary frames:
.. code-block:: python
from channels.generic.websocket import WebsocketConsumer
class MyConsumer(WebsocketConsumer):
groups = ["broadcast"]
def connect(self):
# Called on connection.
# To accept the connection call:
self.accept()
# Or accept the connection and specify a chosen subprotocol.
# A list of subprotocols specified by the connecting client
# will be available in self.scope['subprotocols']
self.accept("subprotocol")
# To reject the connection, call:
self.close()
def receive(self, text_data=None, bytes_data=None):
# Called with either text_data or bytes_data for each frame
# You can call:
self.send(text_data="Hello world!")
# Or, to send a binary frame:
self.send(bytes_data="Hello world!")
# Want to force-close the connection? Call:
self.close()
# Or add a custom WebSocket error code!
self.close(code=4123)
def disconnect(self, close_code):
# Called when the socket closes
You can also raise channels.exceptions.AcceptConnection orchannels.exceptions.DenyConnection from anywhere inside the connect
method in order to accept or reject a connection, if you want reusable
authentication or rate-limiting code that doesn't need to use mixins.
A WebsocketConsumer's channel will automatically be added to (on connect)
and removed from (on disconnect) any groups whose names appear in the
consumer's groups class attribute. groups must be an iterable, and a
channel layer with support for groups must be set as the channel backend
(channels.layers.InMemoryChannelLayer andchannels_redis.core.RedisChannelLayer both support groups). If no channel
layer is configured or the channel layer doesn't support groups, connecting
to a WebsocketConsumer with a non-empty groups attribute will raisechannels.exceptions.InvalidChannelLayerError. See :ref:groups for more.
AsyncWebsocketConsumer
~~~~~~~~~~~~~~~~~~~~~~
Available as channels.generic.websocket.AsyncWebsocketConsumer, this has
the exact same methods and signature as WebsocketConsumer but everything
is async, and the functions you need to write have to be as well:
.. code-block:: python
from channels.generic.websocket import AsyncWebsocketConsumer
class MyConsumer(AsyncWebsocketConsumer):
groups = ["broadcast"]
async def connect(self):
# Called on connection.
# To accept the connection call:
await self.accept()
# Or accept the connection and specify a chosen subprotocol.
# A list of subprotocols specified by the connecting client
# will be available in self.scope['subprotocols']
await self.accept("subprotocol")
# To reject the connection, call:
await self.close()
async def receive(self, text_data=None, bytes_data=None):
# Called with either text_data or bytes_data for each frame
# You can call:
await self.send(text_data="Hello world!")
# Or, to send a binary frame:
await self.send(bytes_data="Hello world!")
# Want to force-close the connection? Call:
await self.close()
# Or add a custom WebSocket error code!
await self.close(code=4123)
async def disconnect(self, close_code):
# Called when the socket closes
JsonWebsocketConsumer
~~~~~~~~~~~~~~~~~~~~~
Available as channels.generic.websocket.JsonWebsocketConsumer, this
works like WebsocketConsumer, except it will auto-encode and decode
to JSON sent as WebSocket text frames.
The only API differences are:
* Your receive_json method must take a single argument, content, that
is the decoded JSON object.
* self.send_json takes only a single argument, content, which will be
encoded to JSON for you.
If you want to customise the JSON encoding and decoding, you can override
the encode_json and decode_json classmethods.
AsyncJsonWebsocketConsumer
~~~~~~~~~~~~~~~~~~~~~~~~~~
An async version of JsonWebsocketConsumer, available aschannels.generic.websocket.AsyncJsonWebsocketConsumer. Note that evenencode_json and decode_json are async functions.
AsyncHttpConsumer
~~~~~~~~~~~~~~~~~
Available as channels.generic.http.AsyncHttpConsumer, this offers basic
primitives to implement a HTTP endpoint:
.. code-block:: python
from channels.generic.http import AsyncHttpConsumer
class BasicHttpConsumer(AsyncHttpConsumer):
async def handle(self, body):
await asyncio.sleep(10)
await self.send_response(200, b"Your response bytes", headers=[
(b"Content-Type", b"text/plain"),
])
You are expected to implement your own handle method. The
method receives the whole request body as a single bytestring. Headers
may either be passed as a list of tuples or as a dictionary. The
response body content is expected to be a bytestring.
You can also implement a disconnect method if you want to run code on
disconnect - for example, to shut down any coroutines you launched. This will
run even on an unclean disconnection, so don't expect that handle has
finished running cleanly.
If you need more control over the response, e.g. for implementing long
polling, use the lower level self.send_headers and self.send_body
methods instead. This example already mentions channel layers which will
be explained in detail later:
.. code-block:: python
import json
from channels.generic.http import AsyncHttpConsumer
class LongPollConsumer(AsyncHttpConsumer):
async def handle(self, body):
await self.send_headers(headers=[
(b"Content-Type", b"application/json"),
])
# Headers are only sent after the first body event.
# Set "more_body" to tell the interface server to not
# finish the response yet:
await self.send_body(b"", more_body=True)
async def chat_message(self, event):
# Send JSON and finish the response:
await self.send_body(json.dumps(event).encode("utf-8"))
Of course you can also use those primitives to implement a HTTP endpoint for
Server-sent events <https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events>_:
.. code-block:: python
from datetime import datetime
from channels.generic.http import AsyncHttpConsumer
class ServerSentEventsConsumer(AsyncHttpConsumer):
async def handle(self, body):
await self.send_headers(headers=[
(b"Cache-Control", b"no-cache"),
(b"Content-Type", b"text/event-stream"),
(b"Transfer-Encoding", b"chunked"),
])
while True:
payload = "data: %s\n\n" % datetime.now().isoformat()
await self.send_body(payload.encode("utf-8"), more_body=True)
await asyncio.sleep(1)
---
Topics/Databases
Database Access
===============
The Django ORM is a synchronous piece of code, and so if you want to access
it from asynchronous code you need to do special handling to make sure its
connections are closed properly.
If you're using SyncConsumer, or anything based on it - likeJsonWebsocketConsumer - you don't need to do anything special, as all your
code is already run in a synchronous mode and Channels will do the cleanup
for you as part of the SyncConsumer code.
If you are writing asynchronous code, however, you will need to call
database methods in a safe, synchronous context, using database_sync_to_async
or by using the asynchronous methods prefixed with a like Model.objects.aget().
Database Connections
--------------------
Channels can potentially open a lot more database connections than you may be used to if you are using threaded consumers (synchronous ones) - it can open up to one connection per thread.
If you wish to control the maximum number of threads used, set theASGI_THREADS environment variable to the maximum number you wish to allow.min(32, os.cpu_count() + 4)
By default, the number of threads is set to "the number of CPUs * 5" for
Python 3.7 and below, and for Python 3.8+.
To avoid having too many threads idling in connections, you can instead rewrite your code to use async consumers and only dip into threads when you need to use Django's ORM (using database_sync_to_async).
When using async consumers Channels will automatically call Django's close_old_connections method when a new connection is started, when a connection is closed, and whenever anything is received from the client.
This mirrors Django's logic for closing old connections at the start and end of a request, to the extent possible. Connections are not automatically closed when sending data from a consumer since Channels has no way
to determine if this is a one-off send (and connections could be closed) or a series of sends (in which closing connections would kill performance). Instead, if you have a long-lived async consumer you should
periodically call aclose_old_connections (see below).
database_sync_to_async
----------------------
channels.db.database_sync_to_async is a version of asgiref.sync.sync_to_async
that also cleans up database connections on exit.
To use it, write your ORM queries in a separate function or method, and then
call it with database_sync_to_async like so:
.. code-block:: python
from channels.db import database_sync_to_async
async def connect(self):
self.username = await database_sync_to_async(get_name)()
def get_name(self):
return User.objects.all()[0].name
You can also use it as a decorator:
.. code-block:: python
from channels.db import database_sync_to_async
async def connect(self):
self.username = await get_name()
@database_sync_to_async
def get_name(self):
return User.objects.all()[0].name
aclose_old_connections
----------------------
channels.db.aclose_old_connections is an async wrapper around Django'sclose_old_connections. When using a long-lived AsyncConsumer that
calls the Django ORM it is important to call this function periodically.
Preferrably, this function should be called before making the first query
in a while. For example, it should be called if the Consumer is woken up
by a channels layer event and needs to make a few ORM queries to determine
what to send to the client. This function should be called before making
those queries. Calling this function more than necessary is not necessarily
a bad thing, but it does require a context switch to synchronous code and
so incurs a small penalty.
---
Topics/Routing
Routing
=======
While consumers are valid :doc:ASGI </asgi> applications, you don't want
to just write one and have that be the only thing you can give to protocol
servers like Daphne. Channels provides routing classes that allow you to
combine and stack your consumers (and any other valid ASGI application) to
dispatch based on what the connection is.
.. important::
Channels routers only work on the scope level, not on the level of
individual events, which means you can only have one consumer for any
given connection. Routing is to work out what single consumer to give a
connection, not how to spread events from one connection across
multiple consumers.
Routers are themselves valid ASGI applications, and it's possible to nest them.
We suggest that you have a ProtocolTypeRouter as the root application of
your project - the one that you pass to protocol servers - and nest other,
more protocol-specific routing underneath there.
Channels expects you to be able to define a single root application, and
provide the path to it as the ASGI_APPLICATION setting (think of this as
being analogous to the ROOT_URLCONF setting in Django). There's no fixed
rule as to where you need to put the routing and the root application, but we
recommend following Django's conventions and putting them in a project-level
file called asgi.py, next to urls.py. You can read more about deploying/deploying
Channels projects and settings in :doc:.
Here's an example of what that asgi.py might look like:
.. include:: ../includes/asgi_example.rst
.. note::
We call the as_asgi() classmethod when routing our consumers. This
returns an ASGI wrapper application that will instantiate a new consumer
instance for each connection or scope. This is similar to Django's
as_view(), which plays the same role for per-request instances of
class-based views.
It's possible to have routers from third-party apps, too, or write your own,
but we'll go over the built-in Channels ones here.
ProtocolTypeRouter
------------------
channels.routing.ProtocolTypeRouter
This should be the top level of your ASGI application stack and the main entry
in your routing file.
It lets you dispatch to one of a number of other ASGI applications based on thetype value present in the scope. Protocols will define a fixed type
value that their scope contains, so you can use this to distinguish between
incoming connection types.
It takes a single argument - a dictionary mapping type names to ASGI
applications that serve them:
.. code-block:: python
ProtocolTypeRouter({
"http": some_app,
"websocket": some_other_app,
})
If you want to split HTTP handling between long-poll handlers and Django views,
use a URLRouter using Django's get_asgi_application() specified as the last
entry with a match-everything pattern.
.. _urlrouter:
URLRouter
---------
channels.routing.URLRouter
Routes http or websocket type connections via their HTTP path. Takes a
single argument, a list of Django URL objects (either path() orre_path()):
.. code-block:: python
URLRouter([
re_path(r"^longpoll/$", LongPollConsumer.as_asgi()),
re_path(r"^notifications/(?P<stream>\w+)/$", LongPollConsumer.as_asgi()),
re_path(r"", get_asgi_application()),
])
Any captured groups will be provided in scope as the key url_route, a
dict with a kwargs key containing a dict of the named regex groups and
an args key with a list of positional regex groups. Note that named
and unnamed groups cannot be mixed: Positional groups are discarded as soon
as a single named group is matched.
For example, to pull out the named group stream in the example above, you
would do this:
.. code-block:: python
stream = self.scope["url_route"]["kwargs"]["stream"]
Please note that URLRouter nesting will not work properly withpath() routes if inner routers are wrapped by additional middleware.Issue #1428 <https://github.com/django/channels/issues/1428>
See __.
ChannelNameRouter
-----------------
channels.routing.ChannelNameRouter
Routes channel type scopes based on the value of the channel key in/topics/worker
their scope. Intended for use with the :doc:.
It takes a single argument - a dictionary mapping channel names to ASGI
applications that serve them:
.. code-block:: python
ChannelNameRouter({
"thumbnails-generate": some_app,
"thumbnails-delete": some_other_app,
})
---
Topics/Security
Security
========
This covers basic security for protocols you're serving via Channels and
helpers that we provide.
WebSockets
----------
WebSockets start out life as a HTTP request, including all the cookies
and headers, and so you can use the standard :doc:/topics/authentication
code in order to grab current sessions and check user IDs.
There is also a risk of cross-site request forgery (CSRF) with WebSockets though,
as they can be initiated from any site on the internet to your domain, and will
still have the user's cookies and session from your site. If you serve private
data down the socket, you should restrict the sites which are allowed to open
sockets to you.
This is done via the channels.security.websocket package, and the two
ASGI middlewares it contains, OriginValidator andAllowedHostsOriginValidator.
OriginValidator lets you restrict the valid options for the Origin
header that is sent with every WebSocket to say where it comes from. Just wrap
it around your WebSocket application code like this, and pass it a list of
valid domains as the second argument. You can pass only a single domain (for example,.allowed-domain.com) or a full origin, in the format scheme://domain[:port]
(for example, http://allowed-domain.com:80). Port is optional, but recommended:
.. code-block:: python
from channels.security.websocket import OriginValidator
application = ProtocolTypeRouter({
"websocket": OriginValidator(
AuthMiddlewareStack(
URLRouter([
...
])
),
[".goodsite.com", "http://.goodsite.com:80", "http://other.site.com"],
),
})
Note: If you want to resolve any domain, then use the origin *.
Often, the set of domains you want to restrict to is the same as the DjangoALLOWED_HOSTS setting, which performs a similar security check for theHost header, and so AllowedHostsOriginValidator lets you use this
setting without having to re-declare the list:
.. code-block:: python
from channels.security.websocket import AllowedHostsOriginValidator
application = ProtocolTypeRouter({
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter([
...
])
),
),
})
AllowedHostsOriginValidator will also automatically allow local connections
through if the site is in DEBUG mode, much like Django's host validation.
---
Topics/Sessions
Sessions
========
Channels supports standard Django sessions using HTTP cookies for both HTTP
and WebSocket. There are some caveats, however.
Basic Usage
-----------
The SessionMiddleware in Channels supports standard Django sessions,
and like all middleware, should be wrapped around the ASGI application that
needs the session information in its scope (for example, a URLRouter to
apply it to a whole collection of consumers, or an individual consumer).
SessionMiddleware requires CookieMiddleware to function.
For convenience, these are also provided as a combined callable calledSessionMiddlewareStack that includes both. All are importable fromchannels.session.
To use the middleware, wrap it around the appropriate level of consumer
in your asgi.py:
.. code-block:: python
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from channels.sessions import SessionMiddlewareStack
from myapp import consumers
application = ProtocolTypeRouter({
"websocket": AllowedHostsOriginValidator(
SessionMiddlewareStack(
URLRouter([
path("frontend/", consumers.AsyncChatConsumer.as_asgi()),
])
)
),
})
SessionMiddleware will only work on protocols that provide
HTTP headers in their scope - by default, this is HTTP and WebSocket.
To access the session, use self.scope["session"] in your consumer code:
.. code-block:: python
class ChatConsumer(WebsocketConsumer):
def connect(self, event):
self.scope["session"]["seed"] = random.randint(1, 1000)
SessionMiddleware respects all the same Django settings as the default
Django session framework, like SESSION_COOKIE_NAME andSESSION_COOKIE_DOMAIN.
Session Persistence
-------------------
Within HTTP consumers or ASGI applications, session persistence works as you
would expect from Django HTTP views - sessions are saved whenever you send
a HTTP response that does not have status code 500.
This is done by overriding any http.response.start messages to inject
cookie headers into the response as you send it out. If you have set
the SESSION_SAVE_EVERY_REQUEST setting to True, it will save the
session and send the cookie on every response, otherwise it will only save
whenever the session is modified.
If you are in a WebSocket consumer, however, the session is populated
but will never be saved automatically - you must callscope["session"].save() (or the asynchronous version,scope["session"].asave()) yourself whenever you want to persist a session
to your session store. If you don't save, the session will still work correctly
inside the consumer (as it's stored as an instance variable), but other
connections or HTTP views won't be able to see the changes.
.. note::
If you are in a long-polling HTTP consumer, you might want to save changes
to the session before you send a response. If you want to do this,
call scope["session"].save().
---
Topics/Testing
Testing
=======
Testing Channels consumers is a little trickier than testing normal Django
views due to their underlying asynchronous nature.
To help with testing, Channels provides test helpers called Communicators,
which allow you to wrap up an ASGI application (like a consumer) into its own
event loop and ask it questions.
You can test asynchronous code
<https://docs.djangoproject.com/en/stable/topics/testing/tools/#testing-
asynchronous-code>_ using Django's TestCase. Alternately, you can usepytest with itspytest-asyncio <https://github.com/pytest-dev/pytest-asyncio>_ plugin.
Setting Up Async Tests
----------------------
To use Django's TestCase you simply define an async def test method in
order to provide the appropriate async context::
from django.test import TestCase
from channels.testing import HttpCommunicator
from myproject.myapp.consumers import MyConsumer
class MyTests(TestCase):
async def test_my_consumer(self):
communicator = HttpCommunicator(MyConsumer.as_asgi(), "GET", "/test/")
response = await communicator.get_response()
self.assertEqual(response["body"], b"test response")
self.assertEqual(response["status"], 200)
To use pytest you need to set it up with async test support, and
presumably Django test support as well. You can do this by installing thepytest-django and pytest-asyncio packages:
.. code-block:: sh
python -m pip install -U pytest-django pytest-asyncio
Then, you need to decorate the tests you want to run async withpytest.mark.asyncio. Note that you can't mix this with unittest.TestCase
subclasses; you have to write async tests as top-level test functions in the
native pytest style:
.. code-block:: python
import pytest
from channels.testing import HttpCommunicator
from myproject.myapp.consumers import MyConsumer
@pytest.mark.asyncio
async def test_my_consumer():
communicator = HttpCommunicator(MyConsumer.as_asgi(), "GET", "/test/")
response = await communicator.get_response()
assert response["body"] == b"test response"
assert response["status"] == 200
There's a few variants of the Communicator - a plain one for generic usage,
and one each for HTTP and WebSockets specifically that have shortcut methods,
ApplicationCommunicator
-----------------------
ApplicationCommunicator is the generic test helper for any ASGI application.
It provides several basic methods for interaction as explained below.
You should only need this generic class for non-HTTP/WebSocket tests, though
you might need to fall back to it if you are testing things like HTTP chunked
responses or long-polling, which aren't supported in HttpCommunicator yet.
.. note::
ApplicationCommunicator extends the class provided by the base asgiref
package. Channels adds support for running unit tests with async consumers.
To construct it, pass it an application and a scope:
.. code-block:: python
from channels.testing import ApplicationCommunicator
communicator = ApplicationCommunicator(MyConsumer.as_asgi(), {"type": "http", ...})
send_input
~~~~~~~~~~
Call it to send an event to the application:
.. code-block:: python
await communicator.send_input({
"type": "http.request",
"body": b"chunk one \x01 chunk two",
})
receive_output
~~~~~~~~~~~~~~
Call it to receive an event from the application:
.. code-block:: python
event = await communicator.receive_output(timeout=1)
assert event["type"] == "http.response.start"
.. _application_communicator-receive_nothing:
receive_nothing
~~~~~~~~~~~~~~~
Call it to check that there is no event waiting to be received from the
application:
.. code-block:: python
assert await communicator.receive_nothing(timeout=0.1, interval=0.01) is False
# Receive the rest of the http request from above
event = await communicator.receive_output()
assert event["type"] == "http.response.body"
assert event.get("more_body") is True
event = await communicator.receive_output()
assert event["type"] == "http.response.body"
assert event.get("more_body") is None
# Check that there isn't another event
assert await communicator.receive_nothing() is True
# You could continue to send and receive events
# await communicator.send_input(...)
The method has two optional parameters:
* timeout: number of seconds to wait to ensure the queue is empty. Defaults
to 0.1.
* interval: number of seconds to wait for another check for new events.
Defaults to 0.01.
wait
~~~~
Call it to wait for an application to exit (you'll need to either do this or wait for
it to send you output before you can see what it did using mocks or inspection):
.. code-block:: python
await communicator.wait(timeout=1)
If you're expecting your application to raise an exception, use pytest.raises
around wait:
.. code-block:: python
with pytest.raises(ValueError):
await communicator.wait()
HttpCommunicator
----------------
HttpCommunicator is a subclass of ApplicationCommunicator specifically
tailored for HTTP requests. You need only instantiate it with your desired
options:
.. code-block:: python
from channels.testing import HttpCommunicator
communicator = HttpCommunicator(MyHttpConsumer.as_asgi(), "GET", "/test/")
And then wait for its response:
.. code-block:: python
response = await communicator.get_response()
assert response["body"] == b"test response"
You can pass the following arguments to the constructor:
* method: HTTP method name (unicode string, required)
* path: HTTP path (unicode string, required)
* body: HTTP body (bytestring, optional)
The response from the get_response method will be a dict with the following
keys:
* status: HTTP status code (integer)
* headers: List of headers as (name, value) tuples (both bytestrings)
* body: HTTP response body (bytestring)
WebsocketCommunicator
---------------------
WebsocketCommunicator allows you to more easily test WebSocket consumers.
It provides several convenience methods for interacting with a WebSocket
application, as shown in this example:
.. code-block:: python
from channels.testing import WebsocketCommunicator
communicator = WebsocketCommunicator(SimpleWebsocketApp.as_asgi(), "/testws/")
connected, subprotocol = await communicator.connect()
assert connected
# Test sending text
await communicator.send_to(text_data="hello")
response = await communicator.receive_from()
assert response == "hello"
# Close
await communicator.disconnect()
.. note::
All of these methods are coroutines, which means you must await them.
If you do not, your test will either time out (if you forgot to await a
send) or try comparing things to a coroutine object (if you forgot to
await a receive).
.. important::
If you don't call WebsocketCommunicator.disconnect() before your test
suite ends, you may find yourself getting RuntimeWarnings about
things never being awaited, as you will be killing your app off in the
middle of its lifecycle. You do not, however, have to disconnect() if
your app already raised an error.
You can also pass an application built with URLRouter instead of the
plain consumer class. This lets you test applications that require positional
or keyword arguments in the scope:
.. code-block:: python
from channels.testing import WebsocketCommunicator
application = URLRouter([
path("testws/<message>/", KwargsWebSocketApp.as_asgi()),
])
communicator = WebsocketCommunicator(application, "/testws/test/")
connected, subprotocol = await communicator.connect()
assert connected
# Test on connection welcome message
message = await communicator.receive_from()
assert message == 'test'
# Close
await communicator.disconnect()
.. note::
Since the WebsocketCommunicator class takes a URL in its constructor,
a single Communicator can only test a single URL. If you want to test
multiple different URLs, use multiple Communicators.
connect
~~~~~~~
Triggers the connection phase of the WebSocket and waits for the application
to either accept or deny the connection. Takes no parameters and returns
either:
* (True, <chosen_subprotocol>) if the socket was accepted.
chosen_subprotocol defaults to None.
* (False, <close_code>) if the socket was rejected.
close_code defaults to 1000.
send_to
~~~~~~~
Sends a data frame to the application. Takes exactly one of bytes_data
or text_data as parameters, and returns nothing:
.. code-block:: python
await communicator.send_to(bytes_data=b"hi\0")
This method will type-check your parameters for you to ensure what you are
sending really is text or bytes.
send_json_to
~~~~~~~~~~~~
Sends a JSON payload to the application as a text frame. Call it with
an object and it will JSON-encode it for you, and return nothing:
.. code-block:: python
await communicator.send_json_to({"hello": "world"})
receive_from
~~~~~~~~~~~~
Receives a frame from the application and gives you either bytes ortext back depending on the frame type:
.. code-block:: python
response = await communicator.receive_from()
Takes an optional timeout argument with a number of seconds to wait before
timing out, which defaults to 1. It will typecheck your application's responses
for you as well, to ensure that text frames contain text data, and binary
frames contain binary data.
receive_json_from
~~~~~~~~~~~~~~~~~
Receives a text frame from the application and decodes it for you:
.. code-block:: python
response = await communicator.receive_json_from()
assert response == {"hello": "world"}
Takes an optional timeout argument with a number of seconds to wait before
timing out, which defaults to 1.
receive_nothing
~~~~~~~~~~~~~~~
Checks that there is no frame waiting to be received from the application. For
details see
:ref:ApplicationCommunicator <application_communicator-receive_nothing>.
disconnect
~~~~~~~~~~
Closes the socket from the client side. Takes nothing and returns nothing.
You do not need to call this if the application instance you're testing already
exited (for example, if it errored), but if you do call it, it will just
silently return control to you.
ChannelsLiveServerTestCase
--------------------------
If you just want to run standard Selenium or other tests that require a
webserver to be running for external programs, you can useChannelsLiveServerTestCase, which is a drop-in replacement for the
standard Django LiveServerTestCase:
.. code-block:: python
from channels.testing import ChannelsLiveServerTestCase
class SomeLiveTests(ChannelsLiveServerTestCase):
def test_live_stuff(self):
call_external_testing_thing(self.live_server_url)
.. note::
You can't use an in-memory database for your live tests. Therefore
include a test database file name in your settings to tell Django to
use a file database if you use SQLite:
.. code-block:: python
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
"TEST": {
"NAME": os.path.join(BASE_DIR, "db_test.sqlite3"),
},
},
}
serve_static
~~~~~~~~~~~~
Subclass ChannelsLiveServerTestCase with serve_static = True in order
to serve static files (comparable to Django's StaticLiveServerTestCase, you
don't need to run collectstatic before or as a part of your tests setup).
---
Topics/Troubleshooting
Troubleshooting
===============
ImproperlyConfigured exception
------------------------------
.. code-block:: text
django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured.
You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
This exception occurs when your application tries to import any models before Django finishes
its initialization process <https://docs.djangoproject.com/en/3.2/ref/applications/#initialization-process>_ aka django.setup().
django.setup() should be called only once <https://docs.djangoproject.com/en/3.2/topics/settings/#calling-django-setup-is-required-for-standalone-django-usage>_,
and should be called manually only in case of standalone apps.
In context of Channels usage, django.setup() is called automatically in get_asgi_application(),
which means it needs to be called before any ORM models are imported.
The working code order would look like this:
.. include:: ../includes/asgi_example.rst
---
Topics/Worker
Worker and Background Tasks
===========================
While :doc:channel layers </topics/channel_layers> are primarily designed for
communicating between different instances of ASGI applications, they can also
be used to offload work to a set of worker servers listening on fixed channel
names, as a simple, very-low-latency task queue.
.. note::
The worker/background tasks system in Channels is simple and very fast,
and achieves this by not having some features you may find useful, such as
retries or return values.
We recommend you use it for work that does not need guarantees around
being complete (at-most-once delivery), and for work that needs more
guarantees, look into a separate dedicated task queue.
This feature does not work with the in-memory channel layer.
Setting up background tasks works in two parts - sending the events, and then
setting up the consumers to receive and process the events.
Sending
-------
To send an event, just send it to a fixed channel name. For example, let's say
we want a background process that pre-caches thumbnails:
.. code-block:: python
# Inside a consumer
self.channel_layer.send(
"thumbnails-generate",
{
"type": "generate",
"id": 123456789,
},
)
Note that the event you send must have a type key, even if only one
type of message is being sent over the channel, as it will turn into an event
a consumer has to handle.
Also remember that if you are sending the event from a synchronous environment,
you have to use the asgiref.sync.async_to_sync wrapper as specified inchannel layers </topics/channel_layers>
:doc:.
Receiving and Consumers
-----------------------
Channels will present incoming worker tasks to you as events inside a scope
with a type of channel, and a channel key matching the channel/topics/routing
name. We recommend you use ProtocolTypeRouter and ChannelNameRouter (see
:doc: for more) to arrange your consumers:
.. code-block:: python
application = ProtocolTypeRouter({
...
"channel": ChannelNameRouter({
"thumbnails-generate": consumers.GenerateConsumer.as_asgi(),
"thumbnails-delete": consumers.DeleteConsumer.as_asgi(),
}),
})
You'll be specifying the type values of the individual events yourself
when you send them, so decide what your names are going to be and write
consumers to match. For example, here's a basic consumer that expects to
receive an event with type test.print, and a text value containing
the text to print:
.. code-block:: python
class PrintConsumer(SyncConsumer):
def test_print(self, message):
print("Test: " + message["text"])
Once you've hooked up the consumers, all you need to do is run a process that
will handle them. In lieu of a protocol server - as there are no connections
involved here - Channels instead provides you this with the runworker
command:
.. code-block:: text
python manage.py runworker thumbnails-generate thumbnails-delete
Note that runworker` will only listen to the channels you pass it on the
command line. If you do not include a channel, or forget to run the worker,
your events will not be received and acted upon.
---
Tutorial/Index
Tutorial
========
Channels allows you to use WebSockets and other non-HTTP protocols in your
Django site. For example you might want to use WebSockets to allow a page on
your site to immediately receive updates from your Django server without using
HTTP long-polling or other expensive techniques.
In this tutorial we will build a simple chat server, where you can join an
online room, post messages to the room, and have others in the same room see
those messages immediately.
.. toctree::
:maxdepth: 1
part_1
part_2
part_3
part_4
---