python -m pip install -U channels["daphne"]
- Add
dephne
toINSTALLED_APPS
insettings.py
INSTALLED_APPS = (
"daphne",
...
)
- Then, adjust your project’s
asgi.py
file, e.g.myproject/asgi.py
, to wrap the Django ASGI application:
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
# 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()
application = ProtocolTypeRouter({
"http": django_asgi_app,
# Just HTTP for now. (We can add other protocols later.)
})
- And finally, add
ASGI_APPLICATION
insettings.py
ASGI_APPLICATION = "myproject.asgi.application"
- Add the room view to
chat/views.py
:
def room(request, room_name):
return render(request, "chat/room.html", {"room_name": room_name})
- Create the route for the room view in
chat/urls.py
:
urlpatterns = [
...
path("<str:room_name>/", views.room, name="room"),
]
- Create a new file
chat/consumers.py
:
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
self.send(text_data=json.dumps({"message": message}))
- Create a new file
chat/routing.py
:
# chat/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]
- The next step is to point the main ASGI configuration at the chat.routing module. In
mysite/asgi.py
, importAuthMiddlewareStack
,URLRouter
, andchat.routing
; and insert a'websocket'
key in theProtocolTypeRouter
list in the following format:
# project/asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# 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()
import chat.routing
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns))
),
}
)
docker run -p 6379:6379 -d redis:5
python3 -m pip install channels_redis
- Before we can use a channel layer, we must configure it. Edit the
project/settings.py
file and add aCHANNEL_LAYERS
setting to the bottom. It should look like
# project/settings.py
# Channels
ASGI_APPLICATION = "mysite.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
- But iam using inmemory channel layer
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}
- Now that we have a channel layer, let’s use it in
ChatConsumer
. Put the following code inchat/consumers.py
, replacing the old code:
# chat/consumers.py
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}))
-
When a user posts a message, a JavaScript function will transmit the message over WebSocket to a ChatConsumer. The ChatConsumer will receive that message and forward it to the group corresponding to the room name.
-
self.scope["url_route"]["kwargs"]["room_name"]
- Obtains the
'room_name'
parameter from the URL route inchat/routing.py
that opened the WebSocket connection to the consumer. - بيحصل علي الباراميتر اللي اسمه
room_name
من الراوت اللي فاتح الكونكشن
- Obtains the
-
self.room_group_name = "chat_%s" % self.room_name
- Constructs a Channels group name directly from the user-specified room name, without any quoting or escaping.
- بيبني اسم للجروب بيستخدم فيه اسم الروم اللي اتحط في الURL
-
async_to_sync(self.channel_layer.group_add)(self.room_group_name, self.channel_name)
- Joins a group.
- بيضيف الكونسيومر ده للجروب اللي اسمه
self.room_group_name
-
async_to_sync(self.channel_layer.group_discard)(self.room_group_name, self.channel_name)
- Leaves a group.
- بيشيل الكونسيومر ده من الجروب اللي اسمه
self.room_group_name
-
async_to_sync(self.channel_layer.group_send)(self.room_group_name, {"type": "chat_message", "message": message})
- Sends an event to a group.
- An event has a special
'type'
key corresponding to the name of the method that should be invoked on consumers that receive the event. - بيبعت ايفنت للجروب اللي اسمه
self.room_group_name
{"type": "chat_message", "message": message}
: بيبعت ايفنت من نوعchat_message
و بيبعت معاه الرساله اللي اتبعتتchat_message
هو الاسم اللي هيتعرف عليه الكونسيومر اللي هيستقبل الايفنت دهmessage
هو الاسم اللي هيستخدمه الكونسيومر اللي هيستقبل الايفنت ده للوصول للرساله اللي اتبعتتself.send(text_data=json.dumps({"message": message}))
: بيبعت الرساله اللي اتبعتت للكونسيومر اللي هيستقبل الايفنت ده
- Let’s rewrite
ChatConsumer
to be asynchronous. Put the following code inchat/consumers.py
:
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async 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
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"] # comes from the frontend
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat_message", "msgGroup": message}
)
# Receive message from room group
async def chat_message(self, event):
message = event["msgGroup"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))
- This new code is for ChatConsumer is very similar to the original code, with the following differences:
WebsocketConsumer
has been replaced byAsyncWebsocketConsumer
.- All methods have been made
async def
. await
has been added before all calls intoself.channel_layer
.async_to_sync
has been removed from the import list.async_to_sync
has been removed from all calls intoself.channel_layer
.ChatConsumer
now inherits fromAsyncWebsocketConsumer
rather thanWebsocketConsumer
.- All methods are
async def
rather than justdef
. await
is used to call asynchronous functions that perform I/O.async_to_sync
is no longer needed when calling methods on the channel layer.
- If you are writing asynchronous code, however, you will need to call database methods in a safe, synchronous context, using
database_sync_to_async
. | If you wish to control the maximum number of threads used, set theASGI_THREADS
environment variable to the maximum number you wish to allow. By default, the number of threads is set to “the number of CPUs * 5” for Python 3.7 and below, and min(32, os.cpu_count() + 4) for Python 3.8+.
channels.db.database_sync_to_async
is a version ofasgiref.sync.sync_to_async
that also cleans up database connections on exit.- To use it, write your ORM queries in a separate function or method, and then call it with
database_sync_to_async
like so:
from channels.db import database_sync_to_async
async def connect(self):
self.username = await database_sync_to_async(self.get_name)()
def get_name(self):
return User.objects.all()[0].name
- You can also use it as a decorator:
from channels.db import database_sync_to_async
async def connect(self):
self.username = await self.get_name()
@database_sync_to_async
def get_name(self):
return User.objects.all()[0].name
- Let’s modify
ChatConsumer
to get the username from the database, Put the following code inchat/consumers.py
:
# connect function
async def connect(self):
self.user = await self.get_name()
...
await self.accept()
# get_name function to get username for first user
@database_sync_to_async
def get_name(self):
return User.objects.all()[0].username
#---------------------------------------------------------------------------/
# modify receive function to send username to room group
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chat_message",
"msgGroup": message,
"username": self.user, # New
},
)
#---------------------------------------------------------------------------/
# modify chat_message function to send username to websocket
async def chat_message(self, event):
message = event["msgGroup"]
username = event["username"] # New
# add username to text_data
await self.send(
text_data=json.dumps({"message": message, "username": username})
)
-
You can change the query to get the username from the database however you like. For example, you could get the username from the session, or from a cookie, or from a token in the URL.
-
Let's modify
room.html
to display the username, Put the following code inchat/templates/chat/room.html
:
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.username + ': ' +data.message + '\n');
};