django_sockets
Django Sockets
Simplified Django websocket processes designed to work with cloud caches (valkey|redis on single|distributed|serverless)
Setup
General
Make sure you have Python 3.10.x (or higher) installed on your system. You can download it here.
Installation
pip install django_sockets
Other Requirements
Redis / Valkey Cache Server: If you plan to
broadcastmessages across clients and not just respond to individual clients, make sure a cache (valkey or redis) is setup and accessible from your server.Expand this to setup a local valkey cache using Docker.
- Install Docker: https://docs.docker.com/get-docker/
Create and start a valkey cache server using docker:
docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7To run the container after it has been stopped:
docker start django_sockets_cacheTo kill the container later:
docker kill django_sockets_cache
Usage
- Low level docs: https://connor-makowski.github.io/django_sockets/django_sockets.html
- Working django and non django examples can be found here.
Examples
Example: Simple Counter
- Make sure a redis / valkey cache server is running.
Install Requirements:
shellpip install django_sockets- Note: This would normally be done via your
requirements.txtfile and installed in a virtual environment.
- Note: This would normally be done via your
Create a new Django project (if you don't already have one) and navigate to the project directory:
shellpython3 -m django startproject myapp cd myappModify your settings file:
- Add
ASGI_APPLICATIONabove yourINSTALLED_APPS - Add
'daphne'to the top of yourINSTALLED_APPSin yoursettings.pyfile- Daphne is the django created ASGI server that is used by
django_sockets.
- Daphne is the django created ASGI server that is used by
myapp/settings.pyASGI_APPLICATION = 'myapp.asgi.application' INSTALLED_APPS = [ 'daphne', # Your other installed apps ]- Add
Create a new file called
ws.pyand place it inmyapp.- This file will hold the websocket server logic.
- Define a
SocketServerclass that extendsBaseSocketServer.- Define a
configuremethod to set the cache hosts. - Define a
connectmethod to handle logic when a client connects. - Define a
receivemethod to handle logic when a client sends data.
- Define a
- Define a
get_ws_asgi_applicationfunction that returns a URL Router with the websocket routes.- This is where you can apply any needed middleware.
myapp/ws.pyfrom django.urls import path from django_sockets.middleware import SessionAuthMiddleware from django_sockets.sockets import BaseSocketServer from django_sockets.utils import URLRouter class SocketServer(BaseSocketServer): def configure(self): ''' This method is optional and only needs to be defined if you are broadcasting or subscribing to channels. It is not required if you just plan to respond to individual websocket clients. This method is used during the initialization of the socket server to define the cache hosts that will be used for broadcasting and subscribing to channels. ''' self.hosts = [{"address": "redis://0.0.0.0:6379"}] def connect(self): ''' This method is optional and is called when a websocket client connects to the server. It can be used for a variety of purposes such as subscribing to a channel. ''' # When a client connects, create a channel_id attribute # that is set to the user's id. This allows for user scoped # channels if you are using auth middleware. # Note: Since we are not using authentication, all # clients will be subscribed to the same channel ('None'). self.channel_id = str(self.scope['user'].id) self.subscribe(self.channel_id) def receive(self, data): ''' This method is called when a websocket client sends data to the server. It can be used to: - Execute Custom Logic - Update the state of the server - Send data back to the client - Subscribe to a channel - Broadcast data to be sent to subscribed clients ''' if data.get('command')=='reset': data['counter']=0 elif data.get('command')=='increment': data['counter']+=1 else: raise ValueError("Invalid command") # Broadcast the update to all websocket clients # subscribed to this socket's channel_id self.broadcast(self.channel_id, data) # Alternatively if you just want to respond to the # current socket client, just use self.send(data): # self.send(data) def get_ws_asgi_application(): ''' Define the websocket routes for the Django application. You can have multiple websocket routes defined here. This is the place to apply any needed middleware. ''' # Note: `SessionAuthMiddleware` is not required, but is useful # for user scoped channels. return SessionAuthMiddleware(URLRouter([ path("ws/", SocketServer.as_asgi), ]))Modify your
asgi.pyfile:- Use the
django_socketsProtocolTypeRouter - Based on the protocol type, return the appropriate ASGI application.
myapp/asgi.pyimport os from django.core.asgi import get_asgi_application from django_sockets.utils import ProtocolTypeRouter from .ws import get_ws_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings') asgi_app = get_asgi_application() ws_asgi_app = get_ws_asgi_application() application = ProtocolTypeRouter( { "http": asgi_app, "websocket": ws_asgi_app, } )- Use the
In the project root, create
templates/client.html:- This will be the client side of the websocket connection.
- It will contain a simple counter that can be incremented and reset.
- The client will send commands to the server to reset or increment the counter.
- The server will handle the commands and broadcast or send the updated counter relevant clients.
templates/client.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebSocket Client</title> </head> <body> <h1>WebSocket Client</h1> <h2>User: {{ user.username }}</h2> <div> <button id="resetBtn">Reset Counter</button> <button id="incrementBtn">Increment Counter</button> </div> <div> <h3>Messages:</h3> <pre id="messages"></pre> </div> <script> // Connect to the WebSocket server const wsUrl = "ws://localhost:8000/ws/"; const websocket = new WebSocket(wsUrl); var counter = 0; // DOM elements const messages = document.getElementById("messages"); const resetBtn = document.getElementById("resetBtn"); const incrementBtn = document.getElementById("incrementBtn"); // Helper function to display messages const displayMessage = (msg) => { messages.textContent += msg + " "; }; // Handle WebSocket events websocket.onopen = () => { displayMessage("WebSocket connection established."); }; websocket.onmessage = (event) => { displayMessage("Received: " + event.data); counter = JSON.parse(event.data).counter; }; websocket.onerror = (error) => { displayMessage("WebSocket error: " + error); }; websocket.onclose = () => { displayMessage("WebSocket connection closed."); }; // Send 'reset' command resetBtn.addEventListener("click", () => { const command = { command: "reset" }; websocket.send(JSON.stringify(command)); displayMessage("Sent: " + JSON.stringify(command)); }); // Send 'increment' command incrementBtn.addEventListener("click", () => { const command = { "command": "increment", "counter": counter }; websocket.send(JSON.stringify(command)); displayMessage("Sent: " + JSON.stringify(command)); }); </script> </body> </html>In
settings.py:- Update
DIRSin yourTEMPLATESto include your new template directory
myapp/settings.pyTEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # Modify this line 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]- Update
In
urls.py:- Add a simple
clent_viewto render theclient.htmltemplate - Set at it the root URL
myapp/urls.pyfrom django.contrib import admin from django.shortcuts import render from django.urls import path def client_view(request): ''' Render the client.html template ''' # Pass the user to the client.html template return render(request, 'client.html', {'user': request.user}) urlpatterns = [ path('admin/', admin.site.urls), path('', client_view), ]- Note: Normally something like
client_viewwould be imported from aviews.pyfile, but for simplicity it is defined here.
- Add a simple
Setup and run the server:
- Make any needed migrations (determine if the database needs to be created or updated)
- Migrate any changes to bring the database up to date
- Run the server
shellpython manage.py makemigrations python manage.py migrate python manage.py runserverOpen your browser:
- Navigate to
http://localhost:8000/to see the client page. - Duplicate the tab.
- You should see the counter incrementing and resetting in both tabs.
- Note: The counter state is maintained client side.
- If one tab joins after the other has modified the counter, it will not be in sync.
- Whichever counter fires first will determine the next counter value for both tabs.
- Note: Since you have not logged in yet, your Auth Middleware will just return an Anonymous User.
- This means that all users are subscribed to the same channel from the user id ('None').
- Once users are logged in, they will be subscribed to their own user id channel.
- Navigate to
To avoid creating a custom login page, we will just use a superuser and take advantage of the admin login page.
To create a superuser, you can run the following command:
python manage.py createsuperuser- Follow the prompts to create a superuser.
- Login at
http://localhost:8000/admin/login/?next=/with your superuser credentials.- You can logout by navigating to
http://localhost:8000/admin/and clicking the logout button.
- You can logout by navigating to
- You should now see a functional counter page with websockets scoped to the logged in user.
Example: Simple Counter Extension
Use DjangoRestFramework for Token Authentication instead of Session based Authentication
- Complete all steps in the previous example.
Install DjangoRestFramework:
shellpip install djangorestframeworkModify your
settings.pyfile:- Add
'rest_framework.authtoken'to the end of yourINSTALLED_APPS
myapp/settings.pyINSTALLED_APPS = [ 'daphne', # Your other installed apps, 'rest_framework.authtoken', # Add this installed app ]- Add
Make and run migrations:
shellpython manage.py makemigrations python manage.py migrateIn your view (specified in
myapp.urls.py):- Ensure you have a DRF Token and pass it to your websocket template.
- Force users to login before accessing the websocket client.
- In general, you would want to create a custom login page and use the
@login_requireddecorator on your view. - For simplicity, we are just using the admin login page.
myapp/urls.py
- In general, you would want to create a custom login page and use the
from django.contrib import admin from django.shortcuts import render from django.urls import path from rest_framework.authtoken.models import Token # Add this import from django.contrib.auth.decorators import login_required # Add this import @login_required(login_url="/admin/login/") # Add this decorator def client_view(request): ''' Render the client.html template ''' # Get or create a token for the user token, created = Token.objects.get_or_create(user=request.user) # Add this line # Pass the user and token to the client.html template return render(request, 'client.html', {'user': request.user, 'token': token}) # Modify this line urlpatterns = [ path('admin/', admin.site.urls), path('', client_view), ]Update your middleware to use the
DRFTokenAuthMiddlewareinstead of theSessionAuthMiddleware:myapp/ws.pyfrom django.urls import path from django_sockets.middleware import DRFTokenAuthMiddleware # Modify this line from django_sockets.sockets import BaseSocketServer from django_sockets.utils import URLRouter # Your existing code here def get_ws_asgi_application(): ''' Define the websocket routes for the Django application. You can have multiple websocket routes defined here. This is the place to apply any needed middleware. ''' return DRFTokenAuthMiddleware(URLRouter([ # Modify this line path("ws/", SocketServer.as_asgi), ]))Update your client to pass the token to the websocket server on connection:
Option 1: Use a
sec-websocket-protocolheader to pass the token:templates/client.htmlconst websocket = new WebSocket(wsUrl,["Token.{{ token }}"]);Option 2: Use a query parameter to pass the token:
templates/client.htmlconst wsUrl = "ws://localhost:8000/ws/?token={{ token }}"; const websocket = new WebSocket(wsUrl);
Run the server and navigate to
http://localhost:8000/to see the client page.- You will be redirected to the admin login page.
- Login with your superuser credentials.
- You should now see a functional counter page with websockets scoped to the logged in user.
1""" 2# Django Sockets 3[](https://badge.fury.io/py/django_sockets) 4[](https://opensource.org/licenses/MIT) 5 6Simplified Django websocket processes designed to work with cloud caches (valkey|redis on single|distributed|serverless) 7 8# Setup 9 10### General 11 12Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/). 13 14### Installation 15 16``` 17pip install django_sockets 18``` 19 20### Other Requirements 21 22- <b>Redis / Valkey Cache Server</b>: If you plan to `broadcast` messages across clients and not just respond to individual clients, make sure a cache (valkey or redis) is setup and accessible from your server. 23 <details> 24 <summary>Expand this to setup a local valkey cache using Docker.</summary> 25 26 - Install Docker: https://docs.docker.com/get-docker/ 27 - Create and start a valkey cache server using docker: 28 ```bash 29 docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7 30 ``` 31 - To run the container after it has been stopped: 32 ```bash 33 docker start django_sockets_cache 34 ``` 35 - To kill the container later: 36 ```bash 37 docker kill django_sockets_cache 38 ``` 39 </details> 40 41# Usage 42 43- Low level docs: https://connor-makowski.github.io/django_sockets/django_sockets.html 44- [Working django and non django examples can be found here](https://github.com/connor-makowski/django_sockets/tree/main/examples). 45 46## Examples 47 48### Example: Simple Counter 49 501. Make sure a redis / valkey cache server is running. 512. Install Requirements: 52 53 `shell` 54 ```bash 55 pip install django_sockets 56 ``` 57 - Note: This would normally be done via your `requirements.txt` file and installed in a virtual environment. 583. Create a new Django project (if you don't already have one) and navigate to the project directory: 59 60 `shell` 61 ```sh 62 python3 -m django startproject myapp 63 cd myapp 64 ``` 654. Modify your settings file: 66 - Add `ASGI_APPLICATION` above your `INSTALLED_APPS` 67 - Add `'daphne'` to the top of your `INSTALLED_APPS` in your `settings.py` file 68 - Daphne is the django created ASGI server that is used by `django_sockets`. 69 70 `myapp/settings.py` 71 ```py 72 ASGI_APPLICATION = 'myapp.asgi.application' 73 INSTALLED_APPS = [ 74 'daphne', 75 # Your other installed apps 76 ] 77 ``` 785. Create a new file called `ws.py` and place it in `myapp`. 79 - This file will hold the websocket server logic. 80 - Define a `SocketServer` class that extends `BaseSocketServer`. 81 - Define a `configure` method to set the cache hosts. 82 - Define a `connect` method to handle logic when a client connects. 83 - Define a `receive` method to handle logic when a client sends data. 84 - Define a `get_ws_asgi_application` function that returns a URL Router with the websocket routes. 85 - This is where you can apply any needed middleware. 86 87 `myapp/ws.py` 88 ```py 89 from django.urls import path 90 from django_sockets.middleware import SessionAuthMiddleware 91 from django_sockets.sockets import BaseSocketServer 92 from django_sockets.utils import URLRouter 93 94 95 class SocketServer(BaseSocketServer): 96 def configure(self): 97 ''' 98 This method is optional and only needs to be defined 99 if you are broadcasting or subscribing to channels. 100 101 It is not required if you just plan to respond to 102 individual websocket clients. 103 104 This method is used during the initialization of the 105 socket server to define the cache hosts that will be 106 used for broadcasting and subscribing to channels. 107 ''' 108 self.hosts = [{"address": "redis://0.0.0.0:6379"}] 109 110 def connect(self): 111 ''' 112 This method is optional and is called when a websocket 113 client connects to the server. 114 115 It can be used for a variety of purposes such as 116 subscribing to a channel. 117 ''' 118 # When a client connects, create a channel_id attribute 119 # that is set to the user's id. This allows for user scoped 120 # channels if you are using auth middleware. 121 # Note: Since we are not using authentication, all 122 # clients will be subscribed to the same channel ('None'). 123 self.channel_id = str(self.scope['user'].id) 124 self.subscribe(self.channel_id) 125 126 def receive(self, data): 127 ''' 128 This method is called when a websocket client sends 129 data to the server. It can be used to: 130 - Execute Custom Logic 131 - Update the state of the server 132 - Send data back to the client 133 - Subscribe to a channel 134 - Broadcast data to be sent to subscribed clients 135 ''' 136 if data.get('command')=='reset': 137 data['counter']=0 138 elif data.get('command')=='increment': 139 data['counter']+=1 140 else: 141 raise ValueError("Invalid command") 142 # Broadcast the update to all websocket clients 143 # subscribed to this socket's channel_id 144 self.broadcast(self.channel_id, data) 145 # Alternatively if you just want to respond to the 146 # current socket client, just use self.send(data): 147 # self.send(data) 148 149 150 def get_ws_asgi_application(): 151 ''' 152 Define the websocket routes for the Django application. 153 154 You can have multiple websocket routes defined here. 155 156 This is the place to apply any needed middleware. 157 ''' 158 # Note: `SessionAuthMiddleware` is not required, but is useful 159 # for user scoped channels. 160 return SessionAuthMiddleware(URLRouter([ 161 path("ws/", SocketServer.as_asgi), 162 ])) 163 ``` 1646. Modify your `asgi.py` file: 165 - Use the `django_sockets` `ProtocolTypeRouter` 166 - Based on the protocol type, return the appropriate ASGI application. 167 168 `myapp/asgi.py` 169 ```py 170 import os 171 172 from django.core.asgi import get_asgi_application 173 from django_sockets.utils import ProtocolTypeRouter 174 from .ws import get_ws_asgi_application 175 176 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings') 177 178 asgi_app = get_asgi_application() 179 ws_asgi_app = get_ws_asgi_application() 180 181 application = ProtocolTypeRouter( 182 { 183 "http": asgi_app, 184 "websocket": ws_asgi_app, 185 } 186 ) 187 ``` 1887. In the project root, create `templates/client.html`: 189 - This will be the client side of the websocket connection. 190 - It will contain a simple counter that can be incremented and reset. 191 - The client will send commands to the server to reset or increment the counter. 192 - The server will handle the commands and broadcast or send the updated counter relevant clients. 193 194 `templates/client.html` 195 ```html 196 <!DOCTYPE html> 197 <html lang="en"> 198 <head> 199 <meta charset="UTF-8"> 200 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 201 <title>WebSocket Client</title> 202 </head> 203 <body> 204 <h1>WebSocket Client</h1> 205 <h2>User: {{ user.username }}</h2> 206 <div> 207 <button id="resetBtn">Reset Counter</button> 208 <button id="incrementBtn">Increment Counter</button> 209 </div> 210 <div> 211 <h3>Messages:</h3> 212 <pre id="messages"></pre> 213 </div> 214 215 <script> 216 // Connect to the WebSocket server 217 const wsUrl = "ws://localhost:8000/ws/"; 218 const websocket = new WebSocket(wsUrl); 219 var counter = 0; 220 221 // DOM elements 222 const messages = document.getElementById("messages"); 223 const resetBtn = document.getElementById("resetBtn"); 224 const incrementBtn = document.getElementById("incrementBtn"); 225 226 // Helper function to display messages 227 const displayMessage = (msg) => { 228 messages.textContent += msg + "\n"; 229 }; 230 231 // Handle WebSocket events 232 websocket.onopen = () => { 233 displayMessage("WebSocket connection established."); 234 }; 235 236 websocket.onmessage = (event) => { 237 displayMessage("Received: " + event.data); 238 counter = JSON.parse(event.data).counter; 239 }; 240 241 websocket.onerror = (error) => { 242 displayMessage("WebSocket error: " + error); 243 }; 244 245 websocket.onclose = () => { 246 displayMessage("WebSocket connection closed."); 247 }; 248 249 // Send 'reset' command 250 resetBtn.addEventListener("click", () => { 251 const command = { command: "reset" }; 252 websocket.send(JSON.stringify(command)); 253 displayMessage("Sent: " + JSON.stringify(command)); 254 }); 255 256 // Send 'increment' command 257 incrementBtn.addEventListener("click", () => { 258 const command = { "command": "increment", "counter": counter }; 259 websocket.send(JSON.stringify(command)); 260 displayMessage("Sent: " + JSON.stringify(command)); 261 }); 262 </script> 263 </body> 264 </html> 265 ``` 266 2678. In `settings.py`: 268 - Update `DIRS` in your `TEMPLATES` to include your new template directory 269 270 `myapp/settings.py` 271 ```py 272 TEMPLATES = [ 273 { 274 'BACKEND': 'django.template.backends.django.DjangoTemplates', 275 'DIRS': [BASE_DIR / 'templates'], # Modify this line 276 'APP_DIRS': True, 277 'OPTIONS': { 278 'context_processors': [ 279 'django.template.context_processors.debug', 280 'django.template.context_processors.request', 281 'django.contrib.auth.context_processors.auth', 282 'django.contrib.messages.context_processors.messages', 283 ], 284 }, 285 }, 286 ] 287 ``` 288 2899. In `urls.py`: 290 - Add a simple `clent_view` to render the `client.html` template 291 - Set at it the root URL 292 293 `myapp/urls.py` 294 ```py 295 from django.contrib import admin 296 from django.shortcuts import render 297 from django.urls import path 298 299 def client_view(request): 300 ''' 301 Render the client.html template 302 ''' 303 # Pass the user to the client.html template 304 return render(request, 'client.html', {'user': request.user}) 305 306 urlpatterns = [ 307 path('admin/', admin.site.urls), 308 path('', client_view), 309 ] 310 ``` 311 - Note: Normally something like `client_view` would be imported from a `views.py` file, but for simplicity it is defined here. 312 31310. Setup and run the server: 314 - Make any needed migrations (determine if the database needs to be created or updated) 315 - Migrate any changes to bring the database up to date 316 - Run the server 317 318 `shell` 319 ```sh 320 python manage.py makemigrations 321 python manage.py migrate 322 python manage.py runserver 323 ``` 32411. Open your browser: 325 - Navigate to `http://localhost:8000/` to see the client page. 326 - Duplicate the tab. 327 - You should see the counter incrementing and resetting in both tabs. 328 - Note: The counter state is maintained client side. 329 - If one tab joins after the other has modified the counter, it will not be in sync. 330 - Whichever counter fires first will determine the next counter value for both tabs. 331 - Note: Since you have not logged in yet, your Auth Middleware will just return an Anonymous User. 332 - This means that all users are subscribed to the same channel from the user id ('None'). 333 - Once users are logged in, they will be subscribed to their own user id channel. 33412. To avoid creating a custom login page, we will just use a superuser and take advantage of the admin login page. 335 - To create a superuser, you can run the following command: 336 ```bash 337 python manage.py createsuperuser 338 ``` 339 - Follow the prompts to create a superuser. 340 - Login at `http://localhost:8000/admin/login/?next=/` with your superuser credentials. 341 - You can logout by navigating to `http://localhost:8000/admin/` and clicking the logout button. 342 - You should now see a functional counter page with websockets scoped to the logged in user. 343 344<br/><hr/><br/> 345 346### Example: Simple Counter Extension 347#### Use DjangoRestFramework for Token Authentication instead of Session based Authentication 348 3491. Complete all steps in the previous example. 3502. Install DjangoRestFramework: 351 352 `shell` 353 ```bash 354 pip install djangorestframework 355 ``` 3563. Modify your `settings.py` file: 357 - Add `'rest_framework.authtoken'` to the end of your `INSTALLED_APPS` 358 359 `myapp/settings.py` 360 ```py 361 INSTALLED_APPS = [ 362 'daphne', 363 # Your other installed apps, 364 'rest_framework.authtoken', # Add this installed app 365 ] 366 ``` 3674. Make and run migrations: 368 369 `shell` 370 ```bash 371 python manage.py makemigrations 372 python manage.py migrate 373 ``` 3745. In your view (specified in `myapp.urls.py`): 375 - Ensure you have a DRF Token and pass it to your websocket template. 376 - Force users to login before accessing the websocket client. 377 - In general, you would want to create a custom login page and use the `@login_required` decorator on your view. 378 - For simplicity, we are just using the admin login page. 379 `myapp/urls.py` 380 ```py 381 from django.contrib import admin 382 from django.shortcuts import render 383 from django.urls import path 384 385 from rest_framework.authtoken.models import Token # Add this import 386 from django.contrib.auth.decorators import login_required # Add this import 387 388 @login_required(login_url="/admin/login/") # Add this decorator 389 def client_view(request): 390 ''' 391 Render the client.html template 392 ''' 393 # Get or create a token for the user 394 token, created = Token.objects.get_or_create(user=request.user) # Add this line 395 # Pass the user and token to the client.html template 396 return render(request, 'client.html', {'user': request.user, 'token': token}) # Modify this line 397 398 urlpatterns = [ 399 path('admin/', admin.site.urls), 400 path('', client_view), 401 ] 402 ``` 4036. Update your middleware to use the `DRFTokenAuthMiddleware` instead of the `SessionAuthMiddleware`: 404 405 `myapp/ws.py` 406 ```py 407 from django.urls import path 408 from django_sockets.middleware import DRFTokenAuthMiddleware # Modify this line 409 from django_sockets.sockets import BaseSocketServer 410 from django_sockets.utils import URLRouter 411 412 # Your existing code here 413 414 def get_ws_asgi_application(): 415 ''' 416 Define the websocket routes for the Django application. 417 418 You can have multiple websocket routes defined here. 419 420 This is the place to apply any needed middleware. 421 ''' 422 return DRFTokenAuthMiddleware(URLRouter([ # Modify this line 423 path("ws/", SocketServer.as_asgi), 424 ])) 425 ``` 426 4277. Update your client to pass the token to the websocket server on connection: 428 - Option 1: Use a `sec-websocket-protocol` header to pass the token: 429 430 `templates/client.html` 431 ```html 432 const websocket = new WebSocket(wsUrl,["Token.{{ token }}"]); 433 ``` 434 - Option 2: Use a query parameter to pass the token: 435 436 `templates/client.html` 437 ```html 438 const wsUrl = "ws://localhost:8000/ws/?token={{ token }}"; 439 const websocket = new WebSocket(wsUrl); 440 ``` 4418. Run the server and navigate to `http://localhost:8000/` to see the client page. 442 - You will be redirected to the admin login page. 443 - Login with your superuser credentials. 444 - You should now see a functional counter page with websockets scoped to the logged in user. 445 446<br/><hr/><br/>"""