Django Channels2
Django 3.0.1 has released for sometime. We still use Django 1.11 in one of our project, with Channels 1.1.8, I think it's the time to upgrade to newest Django and Channels.
According this upgrade guide, Channels 2 is a fully rewritten, lots of things has changed. This post is just a record about the upgrade I've done.
Routing
In Channels 2 you need to add an ASGI_APPLICATION
in the settings file, point to the asgi application.
ASGI_APPLICATION = "proj.routing.application"
And add routing.py
file, add an application
variable in the file like the bellow.
application = ProtocolTypeRouter({
"websocket": URLRouter([url(r"^ws$", MyConsumer)])
})
The websocket
key means it will handle websocket requests, the URLRouter
just work as Django's. MyConsumer
is the handler for the request.
Consumer
Add MyConsumer
class.
class MyConsumer(WebsocketConsumer):
groups = ["broadcast"]
def connect(self):
self.accept()
print("connected")
def receive(self, text_data=None, bytes_data=None):
print("recv: ", text_data)
def disconnect(self, close_code=None):
print("disconnect")
When a client connected, it will call connect
at first, you can do authentication(If you have your own auth method other than use Django's, otherwise you can use AuthMiddlewarestack instead), some preparing, if you didn't want to accept the connect, you can use self.disconnect()
to end the connection, it will also call disconnect
, where you can do some cleaning. The function receive
will be called when the client send messages to you.
Simple, huh? There are many other types of consumers, you can check here.
Passing through data in session
When a client connected, you may need to check the database and put some useful data in the session, so that you can use these data later. You have to use django session to do this in Channels 1, but in Channels 2 you didn't need to use django session, just store the data to self
.
self.my_data = {
'user': ....,
'role': ...
}
Of cause, you still can use django sessions to store data, check the doc here, just add the SessionMiddlewareStack
in the routing file.
Use self.scope["session"]["seed"] = '123'
to set the session, and don't forget to use self.scope["session"].save()
to save the session if you want to persist.
Broadcast messages
You can use self.send(text_data="")
to send a message to a client. But if you need to send to multiple client at a same time, you need to use Channel Layers.
There are many backends you can use, I recommend you use channels_redis
.
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis-server-name", 6379)],
},
},
}
Then you need to create groups when user connect in MyConsume
.
class MyConsumer(WebsocketConsumer):
groups = ["broadcast"]
def connect(self):
self.accept()
print("connected")
async_to_sync(self.channel_layer.group_add)("some_room", self.channel_name)
def receive(self, text_data=None, bytes_data=None):
print("recv: ", text_data)
def disconnect(self, close_code=None):
print("disconnect")
async_to_sync(self.channel_layer.group_discard)(
"some_room",
self.channel_name
)
def group_notify(self, msg):
self.send(text_data=msg["text"])
The string some_room
is the group id you want the user to join, think about it"s just like a chat room, everyone who join this room is a group. Or you can use the user_id
as the group id like us, so you can send same messages to the same user at once even they have many sessions through many clients.
Use self.channel_layer.group_discard
to quite a group and use self.channel_layer.group_send
to send a message to a group. If you need to send to the group outside the comsumer, just use the codes below to get the channel_layer
.
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"some_room", {
"type": "group.notify",
"text": str(notify)
},
)
Please beware that the message type here is group.notify
, it can be anything you want, just add a handler for it in the comsumer, like the function group_notify
I added above(replace the "." to "_").
Deploy and run
After add the ASGI_APPLICATION
, you can run the server use command ./manage.py runserver
, the server will handle both http and websocket requests.
You can also use daphne
to run the server, just need to add an asgi.py
like the bellow.
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings")
django.setup()
application = get_default_application()
Then run with command daphne -p 8001 proj.asgi:application
, it also can handle both http and websocket request.
If you prefer to handle http requests use WSGI method like before, just run proj.asgi:application
use wsgi server like gunicorn
.