WebSockets-Based APIs with Django(Real-Time communication made easy)

elijah samson
Django Unleashed
Published in
7 min readOct 20, 2023

--

WebSockets?

WebSocket is a bidirectional communication protocol that can send the data from the client to the server or from the server to the client by reusing the established connection channel.

How websockets work?

WebSockets are a protocol that allows for bidirectional communication between a client and a server over a single, long-lived connection. This is in contrast to the traditional HTTP request-response model, which involves the client making a request to the server and the server responding with a single, static response.

They are commonly used to build interactive and dynamic web applications. Here’s how WebSockets work:

  1. Handshake:

The WebSocket connection begins with a handshake. This is a standard HTTP request made by the client to the server, requesting an upgrade to the WebSocket protocol. The client sends an HTTP request that includes an “Upgrade” header to request the upgrade.

2. Server Acceptance:

The server, upon receiving the client’s handshake request, checks if it supports the WebSocket protocol. If it does, the server responds with an HTTP 101 (Switching Protocols) status code and accepts the upgrade request. The server includes the “Upgrade” and “Connection” headers in the response to indicate the protocol switch.

3. Persistent Connection:

Once the handshake is successful, both the client and the server switch to the WebSocket protocol. Unlike traditional HTTP requests, WebSocket connections are persistent, meaning they remain open as long as both parties want to communicate.

4. Full-Duplex Communication:

With a WebSocket connection established, both the client and server can send data to each other at any time. This enables full-duplex communication, where data can be sent and received simultaneously without the need for repeated HTTP requests.

5. Data Framing:

Data sent over a WebSocket connection is divided into small frames for efficient transmission. Each frame can carry text or binary data. The frames contain information about the data type, length, and other control bits.

6. Data Encoding:

WebSockets support text and binary data transmission. Text data is encoded using UTF-8, and binary data is sent as raw binary. This flexibility allows WebSockets to handle various types of information, including JSON, XML, and images.

7. Event-Driven:

WebSockets are event-driven, which means they use a callback mechanism to handle messages and events. Both the client and the server can register event listeners to respond to incoming data or events.

8. Security:

WebSocket connections can use the same security mechanisms as standard HTTP connections, such as TLS/SSL encryption. This ensures that data transmitted over WebSocket connections remains secure.

9. Close Connection:

Either party can initiate the closing of a WebSocket connection. The initiating party sends a close frame, and the other party responds with its own close frame. This graceful closure allows resources to be released properly.

Django and WebSockets

Now that we have a basic understanding of WebSockets, let’s take a look at how to use them in Django.

Django is a popular web framework for building web applications in Python. While Django doesn’t have built-in support for WebSockets, there are several third-party libraries available that make it easy to work with WebSockets in Django.

One such library is Channels, which provides a layer of abstraction over WebSockets and allows for real-time communication between clients and servers. Channels is built on top of the ASGI (Asynchronous Server Gateway Interface) specification, which is designed to handle asynchronous communication between clients and servers.

Creating a Chat Application with Django and WebSockets

To get started, we’ll need to create a new Django project and install Channels:

$ django-admin startproject websocket_demo
$ cd websocket_demo
$ python -m venv venv
$ source venv/bin/activate
$ pip install channels

Creating the WebSocket Consumer

Next, we’ll create a new file called consumers.py in our project's root directory. This file will contain the code for our WebSocket consumer, which will handle incoming WebSocket connections and messages.

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
await self.send(text_data=json.dumps({
'message': message
}))

Let’s break down what this code is doing:

  • We import the AsyncWebsocketConsumer class from the Channels library.
  • We create a new class called ChatConsumer, which inherits from AsyncWebsocketConsumer.
  • We define three methods: connect, disconnect, and receive. These methods handle WebSocket connections, disconnections, and incoming messages, respectively.
  • In the connect method, we accept the WebSocket connection.
  • In the disconnect method, we do nothing (for now).
  • In the receive method, we parse the incoming message as JSON, extract the message text, and then send the message back to the client.

Routing WebSocket Requests

Next, we need to define a URL route for our WebSocket consumer. We’ll create a new file called routing.py in our project's root directory:

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
re_path(r'ws/chat/$', consumers.ChatConsumer.as_asgi()),
]

This file defines a URL route for WebSocket requests to /ws/chat/. When a WebSocket request is made to this URL, it will be handled by our ChatConsumer.

Updating Django Settings

Finally, we need to update our Django settings to include Channels:

INSTALLED_APPS = [
# ...
'channels',
]

ASGI_APPLICATION = 'websocket_demo.routing.application'

We add 'channels' to our INSTALLED_APPS, and we specify the ASGI_APPLICATION as websocket_demo.routing.application.

Testing Our Application

Now that our application is set up, let’s test it out! First, we’ll start our Django development server:

$ python manage.py runserver

Next, we’ll open up a browser and navigate to http://localhost:8000/ws/chat/. This will initiate a WebSocket connection to our ChatConsumer.

We can now open up the browser console and execute the following JavaScript code to send a message to the server:

const chatSocket = new WebSocket('ws://localhost:8000/ws/chat/');

chatSocket.onopen = function() {
console.log('WebSocket connection established.');
const message = {
'message': 'Hello, world!'
};
chatSocket.send(JSON.stringify(message));
};
chatSocket.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('Received message:', message);
};

When we execute this code, we should see a message printed to the console that says “Received message: { message: ‘Hello, world!’ }”. This confirms that our WebSocket connection is working and that we’re able to send and receive messages in real-time!

Advanced Features of Channels

Channels is a powerful library that provides many advanced features for building real-time applications with WebSockets. In this section, we’ll explore some of the most useful features of Channels and how to use them in Django.

Group Communication

One of the most useful features of Channels is the ability to group WebSocket connections together. This allows you to send messages to multiple clients at once, rather than sending messages to each client individually.

To use group communication in Channels, you’ll first need to import the ChannelLayer object:

from channels.layers import get_channel_layer
channel_layer = get_channel_layer()

Once you have the ChannelLayer object, you can use the group_add, group_discard, and group_send methods to manage groups and send messages to group members. Here's an example of how to use these methods:

async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name

await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)

await self.accept()

async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)

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',
'message': message
}
)

async def chat_message(self, event):
message = event['message']

await self.send(text_data=json.dumps({
'message': message
}))

In this example, we’re using group communication to create a chat room where multiple clients can send messages to each other. When a client connects to the WebSocket, we add their channel to a group that corresponds to the chat room they’ve joined. When a client sends a message, we use the group_send method to send the message to all clients in the chat room. Finally, we define a chat_message method that is called when a message is received by a group member, which sends the message back to the client.

Handling Binary Data

By default, Channels only handles text data in WebSocket messages. However, you can also send binary data, such as images or audio, by using the bytes type instead of str.

To send binary data, you’ll need to use the bytes method instead of json.dumps to serialize the data. Here's an example of how to send a binary image:

import base64

async def receive(self, text_data):
bytes_data = base64.b64decode(text_data)
# handle binary data here

In this example, we’re decoding a base64-encoded string into binary data, which we can then handle as needed.

Background Tasks

Sometimes, you’ll need to perform long-running or asynchronous tasks in response to a WebSocket message. For example, you might need to process a large dataset, send an email, or make an API request.

Channels provides a way to run background tasks using async_to_sync and SyncConsumer. Here's an example of how to run a background task in response to a WebSocket message:

from asgiref.sync import async_to_sync
from channels.generic.sync import SyncConsumer

class TaskConsumer(SyncConsumer):
def task(self, message):
# long-running or asynchronous task
return 'Task complete!'
async def receive(self, text_data):
message = json.loads(text_data)
task_type = message['task_type']
task_args = message['task_args']
response = await async_to_sync(TaskConsumer().task)(task_args)
await self.send(text_data=json.dumps({
'response': response
}))

In this example, we’re defining a TaskConsumer that runs a long-running or asynchronous task when the task method is called. We're then using async_to_sync to run the task method in a background thread.

In the receive method, we're parsing a message that includes the type and arguments of the task to run. We're then using async_to_sync to call the TaskConsumer with the specified arguments and return the result. Finally, we're sending the result back to the client using the WebSocket connection.

Conclusion

In this blog, we’ve explored the world of WebSockets and how to use them in Django to create real-time communication between clients and servers.

With WebSockets and Django, the possibilities for real-time communication and interaction in applications are endless. Whether you’re building a chat app, a collaborative editing platform, or a real-time dashboard, WebSockets provide a powerful and efficient way to handle real-time data transfer between clients and servers.

I hope this article has been informative and helpful in getting you started with WebSockets in Django. For further reading, we recommend checking out the official Channels documentation and the Django documentation on asynchronous views .

References

  1. https://blog.heroku.com/in_deep_with_django_channels_the_future_of_real_time_apps_in_django
  2. https://www.geeksforgeeks.org/what-is-web-socket-and-how-it-is-different-from-the-http/#:~:text=WebSocket%20is%20a%20bidirectional%20communication,reusing%20the%20established%20connection%20channel.
  3. https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

That’s it, I guess.

Cheers, Happy Coding!!!

🙏🙏🙏

Since you’ve made it this far, sharing this article on your favorite social media network would be highly appreciated. For feedback, please ping me on Twitter.

--

--

elijah samson
Django Unleashed

Software engineer (Backend, Web Dev). ✍️ I write about backend, data and other amazing stuff