Channels 1 and Channels 2 are substantially different codebases, and the upgrade is a major one. While we have attempted to keep things as familiar and backwards-compatible as possible, major architectural shifts mean you will need at least some code changes to upgrade.
First of all, Channels 2 is Python 3.5 and up only.
If you are using Python 2, or a previous version of Python 3, you cannot use
Channels 2 as it relies on the asyncio
library and native Python async
support. This decision was a tough one, but ultimately Channels is a library
built around async functionality and so to not use these features would be
foolish in the long run.
Apart from that, there are no major changed requirements, and in fact Channels 2 deploys do not need separate worker and server processes and so should be easier to manage.
The fundamental layout and concepts of how Channels work have been significantly changed; you'll need to understand how and why to help in upgrading.
Channels 1 terminated HTTP and WebSocket connections in a separate process to the one that ran Django code, and shuffled requests and events between them over a cross-process channel layer, based on Redis or similar.
This not only meant that all request data had to be re-serialized over the network, but that you needed to deploy and scale two separate sets of servers. Channels 2 changes this by running the Django code in-process via a threadpool, meaning that the network termination and application logic are combined, like WSGI.
Because of this, all processing for a socket happens in the same process,
so ASGI applications are now instantiated once per socket and can use
local variables on self
to store information, rather than the
channel_session
storage provided before (that is now gone entirely).
The channel layer is now only used to communicate between processes for things like broadcast messaging - in particular, you can talk to other application instances in direct events, rather than having to send directly to client sockets.
This means, for example, to broadcast a chat message, you would now send a new-chat-message event to every application instance that needed it, and the application code can handle that event, serialize the message down to the socket format, and send it out (and apply things like multiplexing).
Because of these changes, the way consumers work has also significantly changed. Channels 2 is now a turtles-all-the-way-down design; every aspect of the system is designed as a valid ASGI application, including consumers and the routing system.
The consumer base classes have changed, though if you were using the generic consumers before, the way they work is broadly similar. However, the way that user authentication, sessions, multiplexing, and similar features work has changed.
Channels 2 is also built on a fundamental async foundation, and all servers are actually running an asynchronous event loop and only jumping to synchronous code when you interact with the Django view system or ORM. That means that you, too, can write fully asynchronous code if you wish.
It's not a requirement, but it's there if you need it. We also provide convenience methods that let you jump between synchronous and asynchronous worlds easily, with correct blocking semantics, so you can write most of a consumer in an async style and then have one method that calls the Django ORM run synchronously.
The binding framework has been removed entirely - it was a simplistic implementation, and it being in the core package prevented people from exploring their own solutions. It's likely similar concepts and APIs will appear in a third-party (non-official-Django) package as an option for those who want them.
While this is not an exhaustive guide, here are some rough rules on how to proceed with an upgrade.
Given the major changes to the architecture and layout of Channels 2, it is likely that upgrading will be a significant rewrite of your code, depending on what you are doing.
It is not a drop-in replacement; we would have done this if we could,
but changing to asyncio
and Python 3 made it almost impossible to keep
things backwards-compatible, and we wanted to correct some major design
decisions.
Channels 1 allowed you to route by event type (e.g. websocket.connect
) and
pass individual functions with routing that looked like this:
channel_routing = [ route("websocket.connect", connect_blog, path=r'^/liveblog/(?P<slug>[^/]+)/stream/$'), ]
And function-based consumers that looked like this:
def connect_blog(message, slug): ...
You'll need to convert these to be class-based consumers, as routing is now
done once, at connection time, and so all the event handlers have to be together
in a single ASGI application. In addition, URL arguments are no longer passed
down into the individual functions - instead, they will be provided in scope
as the key url_route
, a dict with an args
key containing a list of
positional regex groups and a kwargs
key with a dict of the named groups.
Routing is also now the main entry point, so you will need to change routing to have a ProtocolTypeRouter with URLRouters nested inside it. See :doc:`/topics/routing` for more.
Any use of the channel_session
or enforce_ordering
decorators can be
removed; ordering is now always followed as protocols are handled in the same
process, and channel_session
is not needed as the same application instance
now handles all traffic from a single client.
Anywhere you stored information in the channel_session
can be replaced by
storing it on self
inside a consumer.
All :doc:`authentication </topics/authentication>` and
:doc:`sessions </topics/session>` are now done with middleware. You can remove
any decorators that handled them, like http_session
, channel_session_user
and so on (in fact, there are no decorators in Channels 2 - it's all middleware).
To get auth now, wrap your URLRouter in an AuthMiddlewareStack
:
from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack application = ProtocolTypeRouter({ "websocket": AuthMiddlewareStack( URLRouter([ ... ]) ), })
You need to replace accesses to message.http_session
with
self.scope["session"]
, and message.user
with self.scope["user"]
.
There is no need to do a handoff like channel_session_user_from_http
any
more - just wrap the auth middleware around and the user will be in the scope
for the lifetime of the connection.
Channel layers are now an optional part of Channels, and the interface they
need to provide has changed to be async. Only channels_redis
, formerly known as
asgi_redis
, has been updated to match so far.
Settings are still similar to before, but there is no longer a ROUTING
key (the base routing is instead defined with ASGI_APPLICATION
):
CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { "hosts": [("redis-server-name", 6379)], }, }, }
All consumers have a self.channel_layer
and self.channel_name
object
that is populated if you've configured a channel layer. Any messages you send
to the channel_name
will now go to the consumer rather than directly to the
client - see the :doc:`/topics/channel_layers` documentation for more.
The method names are largely the same, but they're all now awaitables rather
than synchronous functions, and send_group
is now group_send
.
Group objects no longer exist; instead you should use the group_add
,
group_discard
, and group_send
methods on the self.channel_layer
object inside of a consumer directly. As an example:
from asgiref.sync import async_to_sync class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): await self.channel_layer.group_add("chat", self.channel_name) async def disconnect(self): await self.channel_layer.group_discard("chat", self.channel_name)
If you used the delay server before to put things on hold for a few seconds,
you can now instead use an AsyncConsumer
and asyncio.sleep
:
class PingConsumer(AsyncConsumer): async def websocket_receive(self, message): await asyncio.sleep(1) await self.send({ "type": "websocket.send", "text": "pong", })
The :doc:`testing framework </topics/testing>` has been entirely rewritten to be async-based.
While this does make writing tests a lot easier and cleaner, it means you must entirely rewrite any consumer tests completely - there is no backwards-compatible interface with the old testing client as it was synchronous. You can read more about the new testing framework in the :doc:`testing documentation </topics/testing>`.
Also of note is that the live test case class has been renamed from
ChannelLiveServerTestCase
to ChannelsLiveServerTestCase
- note the extra
s
.
Because the code that's handling a socket is now in the same process as the socket itself, Channels 2 implements cleaner exception handling than before - if your application raises an unhandled error, it will close the connection (HTTP or WebSocket in the case of Daphne) and log the error to console.
Additionally, sending malformed messages down to the client is now caught and raises exceptions where you're sending, rather than silently failing and logging to the server console.