Plug Websockets into Djangos ASGI-Consumers
This is a recap of an POC how to plug websockets into a django backend and allow communication between the consumer instances using channels. There will not a lot of documentation found here, please go to the Django docs if you’re interested into details
Installation
Assumptions:
- (Django >= 3) is installed and a project is created.
- channels is installed.
- For testing websocket connections node-ws is installed (apt install node-ws)
Note: For the sake of simplicity no app is created, everything just lives in the project-app.
# asgi.py
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poc_channels_websockets_redis.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
# Just HTTP for now. (We can add other protocols later.)
})
# settings.py
...
INSTALLED_APPS = [
...
'channels',
]
ASGI_APPLICATION = 'myapp.asgi.application'
CHANNEL_LAYERS = {
"default": {
# for local testing we can just use the in unsecure in memory channel
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
Ping Pong Consumer
# .consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
from uuid import uuid4
class PingConsumer(AsyncWebsocketConsumer):
groups = ["pingpong"]
async def connect(self):
await self.accept()
async def receive(self, text_data=None, bytes_data=None):
await self.send(text_data="Pong")
Register the consumer
# asgi.py
...
from .consumers import PingConsumer
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter([
url("ping/", PingConsumer.as_asgi()),
])
})
Testing the connection
wscat -c "ws://localhost:8000/ping/"
# Connected (press CTRL+C to quit)
> [Enter]
# < Pong
Django does respond to the websocket call. The websockets are integrated into Djangos asgi-server using the consumer above. Yay!
Ping Pong Consumer broadcasting Ping Events
For the communication between the consumers, we can send data through the channel layers.
# .consumers
from channels.generic.websocket import AsyncWebsocketConsumer
from uuid import uuid4
class PingConsumer(AsyncWebsocketConsumer):
groups = ["pingpong"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# each consumer has it's own ident
# We use that to identify the scope on the broadcast
self.ident = str(uuid4())
async def connect(self):
await self.accept()
async def receive(self, text_data=None, bytes_data=None):
# sending down the channel
await self.channel_layer.group_send(self.groups[0], {
# maps to method ping_requested
'type': 'ping.requested',
# Injecting the callers id
'consumer_id': self.ident
})
await self.send(text_data="Pong")
async def ping_requested(self, event):
# The method awaits channel calls with type `ping.requested`
# send string data to the websocket client
await self.send(text_data=f"Pong requested during {event=}")
Testing
wscat -c "ws://localhost:8000/ping/" git:master*
Connected (press CTRL+C to quit)
>
< Pong
< Pong requested during event={'type': 'ping.requested', 'consumer_id': '62422280-9752-485a-90d7-0c9c5339a4c2'}
> %
# a second client calls ping in the background
< Pong requested during event={'type': 'ping.requested', 'consumer_id': 'd4605a01-40c1-42f5-ac1a-a7f3c0c57984'}
The websocket sessions successfully communicating between each another. The system Works!