Django Websockets: A Complete Beginners Guide!

What is Websockets?

Websockets is a communication protocol like HTTP. It is a full-duplex protocol allowing complete communication over a single TCP connection. It is mostly being used in games development, developing chat applications, and IoT. It is used in those places where you want your front-end to be notified when there is a change in the backend server. In this blog, we will discuss how Django websockets and channel works.

For example, take a cricket live scores application. Multiple users are using it at the same time and all the users are notified when there is an update in the scores. Basically, there are multiple TCP connections opened with the backend server dedicated to each user. So the backend server just updates the registered clients with the score and the front-end listens to that and updates the view.

In the past, this type of functionality was achieved by HTTP protocol using a technique named polling. This technique includes sending HTTP requests to the server after a specific interval like 2 seconds and checking for the changes. The major flaw in this technique was the huge number of requests that the server has to handle consuming all the available resources.

Websockets using Django Channels

Django 2.0 or above comes with asynchronous support. It has full support to create WebSockets connections and handle WebSockets requests in the form of Django Channels. Django Channels comes with all the tools you need for WebSockets in Django.

Channels wrap Django’s native asynchronous view support, allowing Django projects to handle not only HTTP, but protocols that require long-running connections too – WebSockets, MQTT, chatbots, amateur radio, and more.

It does this while preserving Django’s easy-to-use technique to write your code using a synchronous approach by writing views or using full-asynchronous or using both.

Now in order to use Django Channels in your application, you have to install the channels library

pip install channels

Now in your settings.py file, you have to add channels in installed_apps. And run the migrate command.

INSTALLED_APPS = [
 ...
 'channels'
]

Now you have set up Django Channels and you have to run the migrations using migrate command.

Now you have to change your project’s asgi.py file 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')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # Just HTTP for now. (We can add other protocols later.)
})

Now point your ASGI_APPLICATION variable in settings.py to this application.

ASGI_APPLICATION = 'mysite.asgi.application'

When looking at the ProtocolTypeRouter you may be thinking about what a router is. In channels routers basically allow you to stack and combine your consumers to dispatch based on what your connection is. Just like we have in our example. We have currently defined the HTTP router. All the HTTP requests will be dispatched to the default asgi application.
You can define other connection types too like WebSockets.

from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter([
    # you can define all your routers here
        ])
})

You can learn more about channel routing here.

In order for our application to work, we have to write consumers for it. Consumers are the same as Django views. Let’s first create some consumers and then discuss them further. To create your consumer, you have to create a consumers.py file just like we have views.py in Django. In the file, you have to write the following code:

from channels.generic.websocket import AsyncJsonWebsocketConsumer

class PracticeConsumer(AsyncJsonWebsocketConsumer)

      async def connect(self):
           await self.accept()

      async def receive(self, text_data=None, bytes_data=None, **kwargs):
            if text_data == 'PING':
                 await self.send('PONG')

This is a simple consumer which just accepts the WebSocket connections and if PING is received, it will respond with a PONG. The connect function will be called when a new connection wants to connect with the consumer. Here you can place your conditions whether you want to establish the connection or not. If you want to accept, we will call self.accept.

Now when we receive something from the client, the receive method is triggered. That’s why we placed our main login in this method. It will first check if the message is correct, then it will call the self.send method to send a response.

There are multiple such methods that we can override to write our logic like disconnect. Just like connect, disconnect is called when a client is disconnected from the consumer.

 

Looking to Hire a Django developer

Share the details of your request and we will provide you with a full-cycle team under one roof.

Get an Estimate

 

Channel Layer

The consumer that we discussed above is a simple consumer that will only send messages to only one client. While working with WebSockets, there are some places where instead of peer-to-peer communication, you have to broadcast a message to a group. You can do so by using Channel layers. Channel layers allow you to talk between different instances of an application. They’re a useful part of making a distributed real-time application if you don’t want to have to shuttle all of your messages or events through a database.

Channel layers are configured via the CHANNEL_LAYERS Django setting. Channel layers are used for high-level communication. For example, there are a bunch of consumers listening to a group on the channel. You just send the message in the group and the consumers will do the low-level networking of the message to the connected clients.

For example, in a chat application, if we are sending a message to a group, then on the channel layer level we will send the message to the group of the channel layer.

await self.channel_layer.group_send(
    room.group_name,
    {
        "type": "chat.message",
        "room_id": room_id,
        "username": self.scope["user"].username,
        "message": message,
    }
)

After the message is sent to a group, the consumers listening to that group receive that message and the handling function will automatically get triggered. The handling function’s name is the same as the type in the message where the periods (.) are replaced by (_). In the above example, the type of message is chat.message, the handling function will be chat_message.

async def chat_message(self, event):
    """
    Called when someone has messaged our chat.
    """
    # Send a message down to the client
    await self.send_json(
        {
            "room": event["room_id"],
            "username": event["username"],
            "message": event["message"],
        },
    )

The send_json method will then send the payload out to the clients. If you are using consumers from channels, they will by default have self.channel_layer and self.channel_name variables defined and configured. By default, they will have a default channel layer. Any message sent to that channel name – or to a group the channel name was added to – will be received by the consumer much like an event from its connected client, and dispatched to a named method on the consumer.

Finalizing our sample app

Now let’s finalize our PING app by configuring the URL for the PracticeConsumer. You can do this same as you configure URLs for Django views. But for consumers, you have to define them in your router. If you can remember, we created a ProtocolTypeRouter that had URLRouter initialized in it. We will user Django’s built-in path function to configure the URL.

from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import path
from practice.consumers import PracticeConsumer


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter([
     path('practice', PracticeConsumer.as_asgi())
])
})

Now we have our WebSocket application setup and completed. The only thing left is to test this out. To simple test the app you can just run the server and open your browser’s console and write the following lines there:

ws = new WebSocket("ws://localhost:8000/practice");
ws.onmessage = (data) => console.log(data.data);
ws.send("PING");

Now when the lines will run, it will send PING to the consumer and the consumer will return back PONG that will be logged to the console.

Adding WebSockets to a Django app without external dependencies

To start, we will need Python >= 3.6 because Django 3.0 is only compatible with Python >= 3.6. 

You will be wondering why I used Django 3.0, it’s because Django 3.0 comes with built-in WebSockets support.

A python begins supporting asynchronous programming from v3.6 so it makes use of the async and await keywords. Once you have Python >= 3.6 setup, create a directory and make a virtual environment there and create a new Django app.

mkdir socket_programming && cd socket_programming
python -m venv ws
source ws/bin/activate
pip install Django
django-admin startproject sockets .

Now when you look at the sockets directory, you will see a file called asgi.py. This file provides the default Django ASGI setup. Like WSGI, ASGI has you supply an application callable which the application server uses to communicate with your code. It’s commonly provided as an object named application in a Python module accessible to the server. It’s not used by the development server. ASGI comes in use when you deploy your Django application in a development or production environment.

An ASGI application is a single asynchronous function that takes 3 parameters:

  • Scope: this is where the context of the current request is defined
  • Receive: the async function that listens to incoming requests
  • Send: async function that is used to send the message to the clients.

 

In order to create a simple application in Django without any external dependencies, we have to wrap our existing asgi application with a custom one. When a WebSocket client connects to a server, the server will receive a websockets.connect type event. To allow the connection, the server has to send a websockets.accept type event. This will complete the WebSocket handshake and the client will establish a persistent connection with the server.

To create a WebSocket application, let’s create a file named websocket.py in our Django app and write an async function named websockets_application that accepts three parameters, scope, receive and send and write the logic for the connection in it. Remember, to receive WebSockets events we have to wrap the logic in an infinite loop.

# websocket.py
async def websocket_application(scope, receive, send):
    while True:
        event = await receive()

        if event['type'] == 'websocket.connect':
            await send({
                'type': 'websocket.accept'
            })

        if event['type'] == 'websocket.disconnect':
            break

        if event['type'] == 'websocket.receive':
            isf event['text'] == 'ping':
                await send({
                    'type': 'websocket.send',
                    'text': 'pong!'
                })

When a client disconnects with the server, the server receives a websocket.disconnect event. That’s why we are breaking the loop while disconnecting. After a successful connection is established, when the client sends a message to the server, the server gets a websocket.receive the event.

Now for the ASGI application, let’s edit the asgi.py file and create an async function application in there. This will act as our own custom ASGI application.

# asgi.py
import os

from sockets.websocket import websocket_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'websocket_app.settings')
async def application(scope, receive, send):
        await websocket_application(scope, receive, send)

Now our WebSocket application is completed and in order to test it out, we have to use any external asgi server like uvicorn or daphne. Right now, Django’s development server does not support the asgi requests.

We will be using daphne to run our app. To do so, we have to first install it:

pip install daphne

After daphne is installed, just run the following command to start daphne:

daphne <app_name>.asgi:application

Our development server for asgi requests will be started and listened on 8000 port. To connect to the server, we have to use a javascript client.

ws = new WebSocket('ws://localhost:8000/')
ws.onmessage = (data) => console.log(data.data)
ws.send("ping")

When the send function is executed, pong will be logged to the console which means our websockets app is running successfully.

Share this article

Leave a comment

Related Posts

How to write Django Unit test?

In the development of any software, whether it’s desktop software, a mobile app, or a web app, testing plays an important role. Doing proper testing […]

28 Apr 2022