Channels
Channels is a project that extends Django's capabilities beyond traditional HTTP, enabling it to handle asynchronous protocols such as WebSockets, chat protocols, and IoT protocols. It is built upon the Asynchronous Server Gateway Interface (ASGI) specification, allowing Django applications to support long-running, event-driven connections alongside conventional HTTP views. The library is currently at version 4.3.2 and is actively maintained as an official Django Project, aligning its release cadence and Python/Django version support with the core framework.
Warnings
- breaking Channels 1.x to 2.x was a complete rewrite, introducing Python's asyncio framework and running async-native. It is not backwards-compatible, requiring significant code changes and dropping Python 2.7/3.4 support. The `channel_session` concept was removed, and applications now run inside their protocol servers.
- breaking Channels 3.x introduced ASGI v3 compliance, aligning with Django's native ASGI support (Django 3.0+). Consumers now require an `.as_asgi()` class method when used in routing. Middleware signatures changed, and `channels.http.AsgiHandler` was deprecated in favor of Django's `get_asgi_application()`.
- breaking Channels 4.x made Daphne an optional dependency. It also removed deprecated static files handling and the `AsgiHandler`. The minimum supported Django version is 3.2, and Python 3.7. (As of 4.3.2, Python >=3.9 and Django 4.2+ are supported).
- gotcha Mixing synchronous Django ORM operations with asynchronous Channels consumers can lead to blocking issues if not handled correctly. Django's database access is synchronous.
- gotcha For distributed applications, real-time communication between multiple consumer instances, or persisting messages across processes (e.g., chat rooms), a channel layer backend (like `channels_redis`) is crucial. The default `InMemoryChannelLayer` is only suitable for single-process development and will not work across multiple servers or even multiple worker processes.
- gotcha It's critical to initialize Django's ASGI application early in your `asgi.py` file (`django_asgi_app = get_asgi_application()`) to ensure the Django AppRegistry is fully populated before any ORM models or app-specific code is imported within your Channels routing or consumers.
Install
-
pip install channels -
pip install 'channels[daphne]' channels_redis
Imports
- ProtocolTypeRouter
from channels.routing import ProtocolTypeRouter
- URLRouter
from channels.routing import URLRouter
- AuthMiddlewareStack
from channels.auth import AuthMiddlewareStack
- WebsocketConsumer
from channels.generic.websocket import WebsocketConsumer
- AsyncWebsocketConsumer
from channels.generic.websocket import AsyncWebsocketConsumer
- sync_to_async
from asgiref.sync import sync_to_async
- async_to_sync
from asgiref.sync import async_to_sync
- get_asgi_application
from django.core.asgi import get_asgi_application
Quickstart
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', 'myproject.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()
# myapp/consumers.py (example consumer)
# from channels.generic.websocket import AsyncWebsocketConsumer
# import json
# class ChatConsumer(AsyncWebsocketConsumer):
# async def connect(self):
# self.room_name = self.scope['url_route']['kwargs']['room_name']
# self.room_group_name = 'chat_%s' % self.room_name
# await self.channel_layer.group_add(
# self.room_group_name,
# self.channel_name
# )
# await self.accept()
#
# async def disconnect(self, close_code):
# await self.channel_layer.group_discard(
# self.room_group_name,
# self.channel_name
# )
#
# async def receive(self, text_data):
# text_data_json = json.loads(text_data)
# message = text_data_json['message']
# await self.channel_layer.group_send(
# self.room_group_name,
# {
# 'type': 'chat_message',
# 'message': message
# }
# )
#
# async def chat_message(self, event):
# message = event['message']
# await self.send(text_data=json.dumps({'message': message}))
# myproject/routing.py
# from django.urls import re_path
# from myapp import consumers
#
# websocket_urlpatterns = [
# re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
# ]
# Root ASGI application in myproject/asgi.py
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
# myproject.routing.websocket_urlpatterns # Uncomment and define your routing
[
path("ws/echo/", (lambda scope: type('EchoConsumer', (object,), {'as_asgi': lambda: lambda scope, receive, send: (async def _(): await scope['accept'](), await scope['send']({'type': 'websocket.accept'}), async for msg in scope['receive'](): await scope['send']({'type': 'websocket.send', 'text': msg['text']})})())())(0).as_asgi())
]
)
)
),
})