File: introduction.rst

package info (click to toggle)
python-django-channels 4.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 872 kB
  • sloc: python: 2,394; makefile: 154; sh: 8
file content (326 lines) | stat: -rw-r--r-- 12,449 bytes parent folder | download
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`.