Skip to content

writing / 2020 /

Weeknotes 2020 WK 43

Updating Channels

One of my hopes for this quarter was to get some time to work on Channels, the ASGI framework that, primarily, adds WebSocket to Django. (There’s much more to Channels than just WebSockets, and it’ll slot into any ASGI stack, so there’s more than just Django too, but WebSockets for Django is the perception I think.)

This update had been dragging on. There’s a nice little community around Channels, and we’ve had a number of contributions, and releases, for Channels itself, Daphne (the twisted-based ASGI protocol server), and channels_redis, the channel layer backed.

What’s been needed, though, was a reworking inside Channels. The goal was to integrate with Django’s own async ASGI support, and to do that we needed to update an ASGI protocol version, which meant going over pretty much all the code, and all the docs.

Work on this began with some contributions before the Summer. I was making good progress before the need to write my DjangoCon Europe talk came up, and it then stalled again, pending the final push.

90% of that came this week 💃

Reader’s note: These next two bits probably assume that you’ve at least looked at Channels before. You can probably skip to Due next week if not.

Updating to ASGI v3

Updating should be relatively easy.

The main difference internally is that we updated to ASGI v3, which is the current version, and switched to the single callable interface. That means your ASGI apps look like this:

application(scope, receive, send)

Where application is a coroutine (an async def function).

This should be largely transparent to you. If you overrode __init__() or __call__() on your consumers then you’ll need to adjust, but otherwise consumer code should be largely unchanged.

New as_asgi() routing classmethod

The main noticeable difference is when routing your consumers. You instantiate a consumer you get an ASGI application:

class AsyncConsumer:
    async def __call__(self, scope, receive, send):
        ...

But you can’t just route a consumer instance, since all requests will share the self and trouble will ensue. (You’d receive from one request only to send to another.)

This is the same problem Django’s class-based views have with self: you need self.request to be this request, not some other one. So just as Django’s class-based views have as_view(), Channel consumers will have an as_asgi() that you use when routing, to make sure you get a new consumer instance per-request.

With that, and using Django’s get_asgi_application() to wire-up your HTTP handler, a minimal Channels v3 asgi.py entry point will look something like this:

import os

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from django.core.asgi import get_asgi_application

from chat.consumers import AdminChatConsumer, PublicChatConsumer

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

application = ProtocolTypeRouter({
    # Django's ASGI application to handle traditional HTTP requests
    "http": get_asgi_application()
    # WebSocket chat handler
    "websocket":
        URLRouter([
            re_path(r"^chat/$", PublicChatConsumer.as_asgi()),
        ])
    ),
})

It’s really not far from the routing.py you’ll have been using with Channels v2, but we’ve moved to asgi.py to match the expectation from Django v3+.

Due next week

I don’t really like giving due dates. I prefer only talking about what’s already done.

Nonetheless, Channels v3 should be out next week. I need to finish going through the docs, update the release notes, do the quick last obligatory Anything else?, and that’s about it.