Showing posts with label WebSocket. Show all posts
Showing posts with label WebSocket. Show all posts

Monday, July 3, 2017

Django Channels

What is “Django Channels” and why should it be used?

Django Channels is a way to include WebSockets and HTTP2 to your Django servers, and extend its basic functionalities. It allows front end code to receive notifications either when data changes or an event triggers on the backend, without forcing the front end to ask continually and periodically for updates to the backend. Django Channels also makes it possible for the server to run code after a response is sent to the user, allowing for quicker responses and easier data management.
The basic WebSocket functionality works in three steps. First, the client approaches the server to establish the connection, which is known as the “Handshake”. The second phase is a bi-directional connection between the server and the client, in which anyone can send messages to the other. The third phase is the disconnection which any of the two can initiate. This flow is best described in the following diagram:
Django Channels, even while allowing WebSockets functionality, also handles the asynchronous nature of this functionalities, masking it so the programmer can continue using Django synchronously as usual. 
Django Channels (by default) will forward all HTTP requests to the usual Django server so it will not interfere with your already developed application or the usual Django structure and functionality.

How to use Django channels:

Basic configuration

  1. The first step is to install Django Channels for your project. If you are using PyCharm this can be easily done by adding “channels” to your environment on the Project Interpreter. After that you need to add to your settings.py file the following: 
INSTALLED_APPS = (
#Other installed Apps
       'Channels',
)
  1. After integrating Django Channels to your project, the next step is to define the “consumers” who will listen to the defined channel. To do this, in your App you should add a file (which we will call “consumers.py”), where the methods to be called should be added. Here it is intended to listen to the possible WebSocket events that will be defined. For example:
def message_ws(message):
      message.reply_channel.send({"text": message.content['text'],})
After receiving the event for the message WebSocket (“message_ws”), the previous code will send through the reply_channel an object with the message that was received. Basically, this code re-sends the received message.
NOTE: The content that will be sent should be JSON serializable to ensure that it works as expected.
  1. We also need to add a new file for the routing configuration for Django Channels. This file should be added in the same folder as the “settings.py” file and we will call it “routing.py”. Assuming the file you created in the last point is called “consumers.py”, and the App of the project is called “django_channels_app”, the file would be as following:
from channels.routing import route
from django_channels_app.consumers import message_ws
channel_routing = [
      route("websocket.receive", message_ws),
]
  1. Once the consumer and the routing are ready, the settings for the channels should be established. For this, it is required to specify in “settings.py” the channel layers, making appropriate reference to the project you are using. Assuming the project is called “django_channels”, the code to add would be the following:
CHANNEL_LAYERS = {
      "default": {
          "BACKEND": "asgiref.inmemory.ChannelLayer",
            "ROUTING": "django_channels.routing.channel_routing",
      },
}
Once the specified configurations are finished, you will be able to receive messages through the channel on your Django project webpage. In order to test this, you may access the console of your browser and enter the following javascript code:
socket = new WebSocket("ws://127.0.0.1:8000/"); #Or your server IP address
socket.onmessage = function(e) {
    alert(e.data);
}
socket.onopen = function() {
    socket.send("Test message");
}

Groups

The problem with the previously configured connection is that it only allows sending one specific message to one specific user who has registered itself to listen to this messages. What should you do if you want to send one message to multiple users? Well, that is what groups are for!
Using groups is very similar to it was covered so far. The only differences are that the user needs to be added to the group and that messages should be sent to the group and not to the user specific reply_channel. Also, it is a good practice to add a way for the server to disconnect the user, in order to avoid sending messages that will not reach its destination. Django Channels have a default timeout for disconnections, so this is not absolutely mandatory, but it is highly recommended.
To integrate a group to the previous example, just add the following changes:
  1. In “consumers.py” import “Group” from channels, and add methods to register and to disconnect from the group:
from channels import Group

def listener_add(message):
    Group("django_channels_group").add(message.reply_channel)

def listener_discconect(message):
      Group("django_channels_group").discard(message.reply_channel)
  1. The next thing we need to do is change our message sender to send through the new group “django_channels_group”. The following code takes care of this problem:
def message_ws(message):
    Group("django_channels_group").send({
          "text": "My group message",
     })
  1. Finally, add the new methods to the “routing.py” file, to link the new methods to the solution.
from django_channels_app.consumers import message_ws, listener_add, listener_discconect

channel_routing = [
      route("websocket.receive", message_ws),
      route("websocket.disconnect", listener_discconect),
      route("websocket.connect", listener_add),
]
With these configurations out of the way, the new group should be up and running correctly. You can test it the same way as it was done before, and now the response message should be “My group message”. You can even test it by opening two browsers or tabs on the same page, and after opening the socket in both, both will receive the message once it is sent.

Other considerations

Django Channels can be installed on Django 1.8 and 1.9. It also should come integrated with Django 1.10+
Django Channels will only process some of the requests, although it receives all of them. You are able to configure which of this requests Django Channels will actually resolve. What Django Channels does is redirect the requests to their appropriate handler, according to the following diagram:


References / further information

See our GitHub for an implementation of Django Channels and groups: https://github.com/innuy/django_channels_example