Sunday, 27 November 2016

Django Channels - Realtime Web Apps

Django since its birth has been supporting the request response format of communication over the network. With the advent of WebSockets and need for realtime web applications, Channels is being introduced for 
  • avoiding long pollings/auto refreshes, 
  • implementing asynchronous tasks (e.g. image thumbnailing, sending email) and
  • pushing changes to Web UIs.
Click Here to access documentation.

This post isn't going to be too verbose, rather I'll quickly take you through the steps required to have your first django application with channel running.

Aim: Client opens a websocket, sends a string message. It gets length of string as response.

Solution Steps:

1] Activate a virtual env (click here to know HowTo)

2] On  my Linux system, I was getting error for Python.h, which I resolved referring to this StackOverflow thread.

sudo apt-get  update; sudo apt-get install  python-dev -y

3] Install django, djangorestframework (optional) and channels

pip install django
pip install djangoroestframework
pip install channels

This will also download the dependencies viz. asgi, daphne, twisted.

4] Created django project tutorial and chatbox app inside it.

django-admin.py startproject tutorial
cd tutorial
django-admin.py startapp chatbox


5] Add routing.py and consumers.py in chatbox app

consumers.py - It will contain methods which will consume message placed on a channel.

def ws_echo(message):
    message.reply_channel.send({
        'text': str(len(message.content['text'])),
    })

A consumer functions takes message as input. The two important attributes of message to note here are -
  • message.content : holds a dictionary as message content
  • message.reply_channel : to send response to sender webocket
A message always has a reply_channel attached. The consumer function takes message, and responds with a dictionary with key text holding len of text sent by sender websocket.

IMP: The type of value for key text should be str or something which supports encode else it throws following exception -

ERROR - server - HTTP/WS send decode error: 'int' object has no attribute 'encode'

routing.py - Mapping between consumer method and channel is defined in routing.py

from channels.routing import route

channel_routing = [
    route('websocket.receive', 'chatbox.consumers.ws_echo'),
]

chatbox.consumers.ws_echo - chatbox is django app, consumers is module inside it and ws_echo is method defined inside module. This consumer will listed on channel websocket.receive

6] Specify INSTALLED_APPS in tutorial/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    .........
    .........
    'channels',
    'chatbox',
]

7] Adds Channels backend in tutorial/settings.py

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgiref.inmemory.ChannelLayer',
        'ROUTING': 'chatbox.routing.channel_routing',
    },

BACKEND - Specifies it is going to be in memory channel, (later we will use redis)
ROUTING - This is pointing to channel_routing array in routing.py of chatbox django app.

Note: You may think of creating routing.py in individual apps, merging all in routing.py of tutorial and then passing it in ROUTING. The way we do for urls.py.

8] Start Django Webserver

$ python manage.py runserver 0.0.0.0:8000
Performing system checks...

System check identified no issues (0 silenced).
November 27, 2016 - 06:20:33
Django version 1.10.3, using settings 'tutorial.settings'
Starting Channels development server at http://0.0.0.0:8000/
Channel layer default (asgiref.inmemory.ChannelLayer)
Quit the server with CONTROL-C.
2016-11-27 06:20:33,190 - INFO - worker - Listening on channels http.request, websocket.connect, websocket.receive
2016-11-27 06:20:33,191 - INFO - worker - Listening on channels http.request, websocket.connect, websocket.receive
2016-11-27 06:20:33,191 - INFO - worker - Listening on channels http.request, websocket.connect, websocket.receive
2016-11-27 06:20:33,192 - INFO - worker - Listening on channels http.request, websocket.connect, websocket.receive
2016-11-27 06:20:33,194 - INFO - server - Using busy-loop synchronous mode on channel layer

9] From Console of chrome debugger, send a message as follows.

ws = new WebSocket('ws://localhost:8000/')

ws.onmessage = function(message) {
  alert(message.data);
}

ws.send("Hello World")

Once you create WebSocket object, the server log will show following entry.

[2016/11/27 06:28:30] WebSocket CONNECT / [127.0.0.1:52394]

We have defined onmessage handler to websocket. It will be invoked each time a message is received from server.

After sending message via websocket, you will get response. This will invoke onmessage handler and you will see alert - 11.

Question: How the message got delivered to websocket.receive? I have no where specified channel in my client socket?

Answer: There are channels which are already available for us. For example –
  • http.request channel can be listened on if we want to handle incoming http messages
  • websocket.receive can be used to process incoming websocket messages.

Reference:
Chatting in Realtime with WebSockets and Django Channels.