@@ -101,6 +101,7 @@ call its APIs in the websockets server.
101
101
Now here's how to implement authentication.
102
102
103
103
.. literalinclude :: ../../example/django/authentication.py
104
+ :caption: authentication.py
104
105
105
106
Let's unpack this code.
106
107
@@ -113,23 +114,25 @@ your settings module.
113
114
114
115
The connection handler reads the first message received from the client, which
115
116
is expected to contain a django-sesame token. Then it authenticates the user
116
- with `` get_user() `` , the API for ` authentication outside a view `_. If
117
- authentication fails, it closes the connection and exits .
117
+ with :func: ` ~sesame.utils. get_user` , the API provided by django-sesame for
118
+ ` authentication outside a view `_ .
118
119
119
120
.. _authentication outside a view : https://django-sesame.readthedocs.io/en/stable/howto.html#outside-a-view
120
121
121
- When we call an API that makes a database query such as ``get_user() ``, we
122
- wrap the call in :func: `~asyncio.to_thread `. Indeed, the Django ORM doesn't
123
- support asynchronous I/O. It would block the event loop if it didn't run in a
124
- separate thread.
122
+ If authentication fails, it closes the connection and exits.
123
+
124
+ When we call an API that makes a database query such as
125
+ :func: `~sesame.utils.get_user `, we wrap the call in :func: `~asyncio.to_thread `.
126
+ Indeed, the Django ORM doesn't support asynchronous I/O. It would block the
127
+ event loop if it didn't run in a separate thread.
125
128
126
129
Finally, we start a server with :func: `~websockets.asyncio.server.serve `.
127
130
128
131
We're ready to test!
129
132
130
- Save this code to a file called `` authentication.py ``, make sure the
131
- ``DJANGO_SETTINGS_MODULE `` environment variable is set properly, and start the
132
- websockets server:
133
+ Download :download: ` authentication.py < ../../example/django/authentication.py >`,
134
+ make sure the ``DJANGO_SETTINGS_MODULE `` environment variable is set properly,
135
+ and start the websockets server:
133
136
134
137
.. code-block :: console
135
138
@@ -169,7 +172,7 @@ following code in the JavaScript console of the browser:
169
172
websocket .onmessage = (event ) => console .log (event .data );
170
173
171
174
If you don't want to import your entire Django project into the websockets
172
- server, you can build a separate Django project with ``django.contrib.auth ``,
175
+ server, you can create a simpler Django project with ``django.contrib.auth ``,
173
176
``django-sesame ``, a suitable ``User `` model, and a subset of the settings of
174
177
the main project.
175
178
@@ -184,11 +187,11 @@ action was made. This may be used for showing notifications to other users.
184
187
185
188
Many use cases for WebSocket with Django follow a similar pattern.
186
189
187
- Set up event bus
188
- ................
190
+ Set up event stream
191
+ ...................
189
192
190
- We need a event bus to enable communications between Django and websockets.
191
- Both sides connect permanently to the bus . Then Django writes events and
193
+ We need an event stream to enable communications between Django and websockets.
194
+ Both sides connect permanently to the stream . Then Django writes events and
192
195
websockets reads them. For the sake of simplicity, we'll rely on `Redis
193
196
Pub/Sub `_.
194
197
@@ -219,14 +222,15 @@ change ``get_redis_connection("default")`` in the code below to the same name.
219
222
Publish events
220
223
..............
221
224
222
- Now let's write events to the bus .
225
+ Now let's write events to the stream .
223
226
224
227
Add the following code to a module that is imported when your Django project
225
- starts. Typically, you would put it in a ``signals.py `` module, which you
226
- would import in the ``AppConfig.ready() `` method of one of your apps:
228
+ starts. Typically, you would put it in a :download: `signals.py
229
+ <../../example/django/signals.py>` module, which you would import in the
230
+ ``AppConfig.ready() `` method of one of your apps:
227
231
228
232
.. literalinclude :: ../../example/django/signals.py
229
-
233
+ :caption: signals.py
230
234
This code runs every time the admin saves a ``LogEntry `` object to keep track
231
235
of a change. It extracts interesting data, serializes it to JSON, and writes
232
236
an event to Redis.
@@ -256,13 +260,13 @@ We need to add several features:
256
260
257
261
* Keep track of connected clients so we can broadcast messages.
258
262
* Tell which content types the user has permission to view or to change.
259
- * Connect to the message bus and read events.
263
+ * Connect to the message stream and read events.
260
264
* Broadcast these events to users who have corresponding permissions.
261
265
262
266
Here's a complete implementation.
263
267
264
268
.. literalinclude :: ../../example/django/notifications.py
265
-
269
+ :caption: notifications.py
266
270
Since the ``get_content_types() `` function makes a database query, it is
267
271
wrapped inside :func: `asyncio.to_thread() `. It runs once when each WebSocket
268
272
connection is open; then its result is cached for the lifetime of the
@@ -273,13 +277,10 @@ The connection handler merely registers the connection in a global variable,
273
277
associated to the list of content types for which events should be sent to
274
278
that connection, and waits until the client disconnects.
275
279
276
- The ``process_events() `` function reads events from Redis and broadcasts them
277
- to all connections that should receive them. We don't care much if a sending a
278
- notification fails — this happens when a connection drops between the moment
279
- we iterate on connections and the moment the corresponding message is sent —
280
- so we start a task with for each message and forget about it. Also, this means
281
- we're immediately ready to process the next event, even if it takes time to
282
- send a message to a slow client.
280
+ The ``process_events() `` function reads events from Redis and broadcasts them to
281
+ all connections that should receive them. We don't care much if a sending a
282
+ notification fails. This happens when a connection drops between the moment we
283
+ iterate on connections and the moment the corresponding message is sent.
283
284
284
285
Since Redis can publish a message to multiple subscribers, multiple instances
285
286
of this server can safely run in parallel.
@@ -290,4 +291,4 @@ Does it scale?
290
291
In theory, given enough servers, this design can scale to a hundred million
291
292
clients, since Redis can handle ten thousand servers and each server can
292
293
handle ten thousand clients. In practice, you would need a more scalable
293
- message bus before reaching that scale, due to the volume of messages.
294
+ message stream before reaching that scale, due to the volume of messages.
0 commit comments