56
loading...
This website collects cookies to deliver better user experience
AsyncConsumer
and SyncConsumer
. The former let us write async-capable code, while the latter is used to write synchronous code.AsyncConsumers
for greater performance and when working with independent tasks that could be done in parallel, otherwise stick with SyncConsumers
. For the purpose of this tutorial, we will be writing SyncConsumers
.channel_layer.group_add
and channel_layer.group_discard
. Don't worry as we are going to see all of these concepts in action.import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
WebsocketConsumer
which is available from channels.generic.websocket
is the base WebSocket consumer. It is synchronous and provides a general encapsulation for the WebSocket handling model that other applications can build on.asgiref
package that includes ASGI base libraries such as Sync-to-async
and async-to-sync
function wrappers is automatically installed as a dependency when you install Django with pip. How is this going to be useful? Glad you asked. ChatConsumer
, is a synchronous WebsocketConsumer
. That's why we need to wrap the channel layer methods in a async-to-sync
function provided from the asgiref.sync
module. This wrapper function takes an async function and returns a sync function. self.channel_layer
and self.channel_name
attribute, which contains a pointer to the channel layer instance and the channel name respectively.ChatConsumer
inherits from the base class, WebsocketConsumer
and use its class methods to do the following:connect()
method => as the name implies it's called when a WebSocket connection is opened. Inside this method, we first obtained the room_name
parameter from the URL route in chat/routing.py that opened the WebSocket connection to the consumer. This parameter is available from the scope of of the consumer instance. room_name
) from the URL route.self.room_group_name = 'chat_%s' % self.room_name
what this does is construct a Channels group name directly from the user-specified room name.async_to_sync(self.channel_layer.group_add)(...)
joins a room group. For reasons I mentioned earlier, this function is wrapped in async-to-sync
function.self.accept()
accepts an incoming socket.disconnect()
method => is called when a WebSocket connection is closed. async_to_sync(self.channel_layer.group_discard)(...)
leaves the group.receive()
method => is called with a decoded WebSocket frame. We can send either text_data
or bytes_data
for each frame.json.loads(text_data)
parses the JSON string (the user's input message) that is sent over the connection from room.html, and converts it into a Python Dictionary.text_data_json['message']
captures that message.async_to_sync(self.channel_layer.group_send)
= sends events over the channel layer. An event has a special type
key, which corresponds to the name of the method that should be invoked on consumers that receive the event. We set this type key as 'type': 'chat_message'
, therefore we are expected to declare a handling function with the name chat_message
that will receive those events and turn them into WebSocket frames.Note: The name of the method will be the type of the event with periods replaced by underscores - so, for example, an event coming in over the channel layer with a type of chat.join
will be handled by the method chat_join
.
chat_message()
method => Handles the chat.message
event when it's sent to us. It receives messages from the room group through event['message']
and send it over to the client.from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
as_asgi()
method is similar to as_view()
method which we call when using class based views. What it does is return an ASGI wrapper application that will instantiate a new consumer instance for each connection or scope.import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
chat.routing
module. This means when a connection is made to the development server (channel's development server), ProtocolTypeRouter
checks whether it's a normal HTTP request or a WebSocket. AuthMiddlewareStack
will take it from there and will populate the connection’s scope with a reference to the currently authenticated user, similar to how Django’s AuthenticationMiddleware
populates the request
object of a view
function with the currently authenticated user. URLRouter
will route the connection to a particular consumer based on the provided url patterns.python manage.py runserver