1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
|
Introduction
============
Welcome to Channels!
Channels wraps Django's native asynchronous view support, allowing Django
projects to handle not only HTTP, but protocols that require long-running
connections too - WebSockets, MQTT, chatbots, amateur radio, and more.
It does this while preserving Django's synchronous and easy-to-use nature,
allowing you to choose how you write your code - synchronous in a style like
Django views, fully asynchronous, or a mixture of both. On top of this, it
provides integrations with Django's auth system, session system, and more,
making it easier than ever to extend your HTTP-only project to other protocols.
Channels also bundles this event-driven architecture with *channel layers*,
a system that allows you to easily communicate between processes, and separate
your project into different processes.
If you haven't yet installed Channels, you may want to read :doc:`installation`
first to get it installed. This introduction isn't a direct tutorial, but
you should be able to use it to follow along and make changes to an existing
Django project if you like.
Turtles All The Way Down
------------------------
Channels operates on the principle of "turtles all the way down" - we have
a single idea of what a channels "application" is, and even the simplest of
*consumers* (the equivalent of Django views) are an entirely valid
:doc:`/asgi` application you can run by themselves.
.. note::
ASGI is the name for the asynchronous server specification that Channels
is built on. Like WSGI, it is designed to let you choose between different
servers and frameworks rather than being locked into Channels and our server
Daphne. You can learn more at https://asgi.readthedocs.io
Channels gives you the tools to write these basic *consumers* - individual
pieces that might handle chat messaging, or notifications - and tie them
together with URL routing, protocol detection and other handy things to
make a full application.
We treat HTTP and the existing Django application as part of a bigger whole.
Traditional Django views are still there with Channels and still usable - with
Django's native ASGI support but you can also write custom HTTP
long-polling handling, or WebSocket
receivers, and have that code sit alongside your existing code. URL routing,
middleware - they are all just ASGI applications.
Our belief is that you want the ability to use safe, synchronous techniques
like Django views for most code, but have the option to drop down to a more
direct, asynchronous interface for complex tasks.
Scopes and Events
------------------
Channels and ASGI split up incoming connections into two components: a *scope*,
and a series of *events*.
The *scope* is a set of details about a single incoming connection - such as
the path a web request was made from, or the originating IP address of a
WebSocket, or the user messaging a chatbot. The scope persists throughout the
connection.
For HTTP, the scope just lasts a single request. For WebSockets, it lasts for
the lifetime of the socket (but changes if the socket closes and reconnects).
For other protocols, it varies based on how the protocol's ASGI spec is written;
for example, it's likely that a chatbot protocol would keep one scope open
for the entirety of a user's conversation with the bot, even if the underlying
chat protocol is stateless.
During the lifetime of this *scope*, a series of *events* occur. These
represent user interactions - making a HTTP request, for example, or
sending a WebSocket frame. Your Channels or ASGI applications will be
**instantiated once per scope**, and then be fed the stream of *events*
happening within that scope to decide what action to take.
An example with HTTP:
* The user makes an HTTP request.
* We open up a new ``http`` type scope with details of the request's path,
method, headers, etc.
* We send a ``http.request`` event with the HTTP body content
* The Channels or ASGI application processes this and generates a
``http.response`` event to send back to the browser and close the connection.
* The HTTP request/response is completed and the scope is destroyed.
An example with a chatbot:
* The user sends a first message to the chatbot.
* This opens a scope containing the user's username, chosen name, and user ID.
* The application is given a ``chat.received_message`` event with the event
text. It does not have to respond, but could send one, two or more other chat
messages back as ``chat.send_message`` events if it wanted to.
* The user sends more messages to the chatbot and more
``chat.received_message`` events are generated.
* After a timeout or when the application process is restarted the scope is
closed.
Within the lifetime of a scope - be that a chat, an HTTP request, a socket
connection or something else - you will have one application instance handling
all the events from it, and you can persist things onto the application
instance as well. You can choose to write a raw ASGI application if you wish,
but Channels gives you an easy-to-use abstraction over them called *consumers*.
What is a Consumer?
-------------------
A consumer is the basic unit of Channels code. We call it a *consumer* as it
*consumes events*, but you can think of it as its own tiny little application.
When a request or new socket comes in, Channels will follow its routing table -
we'll look at that in a bit - find the right consumer for that incoming
connection, and start up a copy of it.
This means that, unlike Django views, consumers are long-running. They can
also be short-running - after all, HTTP requests can also be served by consumers -
but they're built around the idea of living for a little while (they live for
the duration of a *scope*, as we described above).
A basic consumer looks like this:
.. code-block:: python
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.username = "Anonymous"
self.accept()
self.send(text_data="[Welcome %s!]" % self.username)
def receive(self, *, text_data):
if text_data.startswith("/name"):
self.username = text_data[5:].strip()
self.send(text_data="[set your username to %s]" % self.username)
else:
self.send(text_data=self.username + ": " + text_data)
def disconnect(self, message):
pass
Each different protocol has different kinds of events that happen, and
each type is represented by a different method. You write code that handles
each event, and Channels will take care of scheduling them and running them
all in parallel.
Underneath, Channels is running on a fully asynchronous event loop, and
if you write code like above, it will get called in a synchronous thread.
This means you can safely do blocking operations, like calling the Django ORM:
.. code-block:: python
class LogConsumer(WebsocketConsumer):
def connect(self, message):
Log.objects.create(
type="connected",
client=self.scope["client"],
)
However, if you want more control and you're willing to work only in
asynchronous functions, you can write fully asynchronous consumers:
.. code-block:: python
class PingConsumer(AsyncConsumer):
async def websocket_connect(self, message):
await self.send({
"type": "websocket.accept",
})
async def websocket_receive(self, message):
await asyncio.sleep(1)
await self.send({
"type": "websocket.send",
"text": "pong",
})
You can read more about consumers in :doc:`/topics/consumers`.
Routing and Multiple Protocols
------------------------------
You can combine multiple consumers (which are, remember, their own ASGI apps)
into one bigger app that represents your project using routing:
.. code-block:: python
application = URLRouter([
path("chat/admin/", AdminChatConsumer.as_asgi()),
path("chat/", PublicChatConsumer.as_asgi(),
])
Channels is not just built around the world of HTTP and WebSockets - it also
allows you to build any protocol into a Django environment, by building a
server that maps those protocols into a similar set of events. For example,
you can build a chatbot in a similar style:
.. code-block:: python
class ChattyBotConsumer(SyncConsumer):
def telegram_message(self, message):
"""
Simple echo handler for telegram messages in any chat.
"""
self.send({
"type": "telegram.message",
"text": "You said: %s" % message["text"],
})
And then use another router to have the one project able to serve both
WebSockets and chat requests:
.. code-block:: python
application = ProtocolTypeRouter({
"websocket": URLRouter([
path("chat/admin/", AdminChatConsumer.as_asgi()),
path("chat/", PublicChatConsumer.as_asgi()),
]),
"telegram": ChattyBotConsumer.as_asgi(),
})
The goal of Channels is to let you build out your Django projects to work
across any protocol or transport you might encounter in the modern web, while
letting you work with the familiar components and coding style you're used to.
For more information about protocol routing, see :doc:`/topics/routing`.
Cross-Process Communication
---------------------------
Much like a standard WSGI server, your application code that is handling
protocol events runs inside the server process itself - for example, WebSocket
handling code runs inside your WebSocket server process.
Each socket or connection to your overall application is handled by an
*application instance* inside one of these servers. They get called and can
send data back to the client directly.
However, as you build more complex application systems you start needing to
communicate between different *application instances* - for example, if you
are building a chatroom, when one *application instance* receives an incoming
message, it needs to distribute it out to any other instances that represent
people in the chatroom.
You can do this by polling a database, but Channels introduces the idea of
a *channel layer*, a low-level abstraction around a set of transports that
allow you to send information between different processes. Each application
instance has a unique *channel name*, and can join *groups*, allowing both
point-to-point and broadcast messaging.
.. note::
Channel layers are an optional part of Channels, and can be disabled if you
want (by setting the ``CHANNEL_LAYERS`` setting to an empty value).
.. code-block:: python
#In a consumer
self.channel_layer.send(
'event',
{
'type': 'message',
'channel': channel,
'text': text,
}
)
You can also send messages to a dedicated process that's listening on its own,
fixed channel name:
.. code-block:: python
# In a consumer
self.channel_layer.send(
"myproject.thumbnail_notifications",
{
"type": "thumbnail.generate",
"id": 90902949,
},
)
You can read more about channel layers in :doc:`/topics/channel_layers`.
Django Integration
------------------
Channels ships with easy drop-in support for common Django features, like
sessions and authentication. You can combine authentication with your
WebSocket views by just adding the right middleware around them:
.. code-block:: python
from django.core.asgi import get_asgi_application
from django.urls import re_path
# 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 channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter([
re_path(r"^front(end)/$", consumers.AsyncChatConsumer.as_asgi()),
])
)
),
})
For more, see :doc:`/topics/sessions` and :doc:`/topics/authentication`.
|