Django WebSockets
Published: 2023-05-12 08:33:05

###Contents

> 1. Why WebSockets?
> 2. How to use WebSockets in Django.
> 3. Doing hands-on with one real-time example.
> 4. Conclusion 

#### Why WebSockets?

If there is any use case in the application where the application needs bidirectional communication between client and server then WebSocket will come into the picture. 
But why WebSockets, there is HTTP right? HTTP is used for handling interactors between client and server, but it has limitations when it comes to bidirectional communication. 

### How to use WebSockets in Django.
Django supports WebSockets through the Channels library. Install the latest version of Channels:
```
pip install channels
```
To use Channels, you need to add it to the list of installed apps in your Django settings:

```
INSTALLED_APPS = [
    # ...
    'channels',
]
```
Specify the ASGI application that Channels should use in your Django settings:

```
ASGI_APPLICATION = '<your_project_name>.asgi.application'
```
Specify the default layer that Channels should use to communicate between your Django app and the WebSocket server.

```
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    },
}
```
### Doing hands-on with one real-time example.

Example:

##### Problem Statement: 
Assume you will be having one website where the users can login and purchase the items, and there is a high possibility that one item might have different buyers at the same point in time. So here when the quantity updates for an item you want to notify all the buyers of the updated quantity.

### Step1
1. First we need to define one consumer which listens for WebSocket connections and handles the incoming messages. 
2. Using consumers we can define a structured code as a series of functions to be called whenever an event happens.
3. Here is the consumer for our case

```
import json
from channels.generic.websocket import AsyncWebsocketConsumer


class NotificationConsumer(AsyncWebsocketConsumer):

    async def connect(self):
       self.channel_layer.group_add(self.scope['user_id'], 
           self.channel_name)
       await self.accept()
       await self.send(text_data=(json.dumps({'grouped': self.scope['user_id']})))


    async def update_count(self, event):
       # This method is a custom method.
        await self.send(text_data=(json.dumps({'text': event['text']})))

    async def disconnect(self):
        await self.channel_layer.group_discard(
            self.scope['user_id'], self.channel_name)
```
##### async def connect(self)
1. This method is called when a WebSocket connection is established.
2. Here we are establishing a WebSocket connection by creating a group concerning the user

##### async def connect(self)
1. This method is called when a WebSocket connection is closed.
2. It checks whether the user and user ID are valid or not and removes the current channel from the relevant groups.

### Step2
Need to define one router to the consumer to establish a WebSocket connection. 

```
from channels.routing import ProtocolTypeRouter, URLRouter
from django.conf.urls import url
from channels.security.websocket import AllowedHostsOriginValidator

class QueryAuthMiddleware:

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        return QueryAuthMiddlewareInstance(scope, self)


class QueryAuthMiddlewareInstance:

    def __init__(self, scope, middleware):
        self.middleware = middleware
        self.scope = dict(scope)
        self.inner = self.middleware.inner

    async def __call__(self, receive, send):
        inner = self.inner(self.scope)
        return await inner(receive, send)


application = ProtocolTypeRouter({'websocket': AllowedHostsOriginValidator(QueryAuthMiddleware(URLRouter([
    url('notification/count', NotificationConsumer)])))})
```
You need to point the variable defined in setting ASGI_APPLICATION  to the file where you will define the socket routing, so that Django will find the URL and will establish the connection.
> EX: ASGI_APPLICATION = "app.routing.application"

Once the routing part is done then we can integrate this from the front end. In this case, we can establish a WebSocket connection for everywhere after the user logins into the application 

> URL: ws://http://127.0.0.1:8000//notification/count?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1Ni

Once the connection is established then the user is ready to send and receive events. 

### Step3
when the quantity updates for an item you want to notify all the item other buyers with the updated quantity. 

There must be one API that we will call to update the quantity. In that API we need to send a WebSocket notification to all other active buyers for that item 

```
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(str(user_id), {"type": "update.count", "text": "UPDATE_COUNT"})
```
the above function will call the update_count method defined in NotificationConsumer consumer. It sends a message to the current WebSocket connection with the new message.

Now front end will receive a message UPDATE_COUNT based on the message we can perform the appropriate action. In this case, we call an API to get the item updated count and will display that accordingly. 

#### Conclusion.
We can define as many consumers needed based on the scenario and handle the respective bidirectional communication with low latency, less load on the server, and many advantages as compared to HTTP. 

#### Resources 
[Channels](https://channels.readthedocs.io/)
[Medium Article](https://medium.com/@ksarthak4ever/django-websockets-and-channels-85b7d5e59dda)