Django3 framework-(3)-[Using websocket]: Using channels to implement websocket functions; simplified configuration and actual usage

Overview:

I have written several blog posts before about how Django uses channels to implement websocket functions. With the use and actual maintenance of the project, the relevant processing methods have been reset.

Generally speaking, the front and back ends only maintain a global connection and carry data to determine specific operations. The general business logic (non-group chat function):

1. The front-end actively initiates a connection and sends data to the back-end. After obtaining the data, the back-end parses out what data the front-end needs, queries the data, and returns it to the front-end. (One request and one return)

2. When some data changes, the back-end needs to proactively inform the front-end so that the front-end can re-query the corresponding data. (Real-time updated data)

1. Dependence

python=3.9.0

Bag:

pip install channels==3.0.0
pip install daphne==3.0.2
pip install redis==4.6.0
pip install channels-redis=3.1.0

django-cors-headers==3.5.0

Project structure:

Item name

  • apps

    • user

    • websocket

      • routings.py

      • consumers.py

      • update.py

      • send_date.py

      • __init__.py

  • Item name

    • settings.py

    • asgi.py

    • urls.py

    • wsgi.py

    • __init__.py

  • manage.py

2. settings.py settings

#Register channels
INSTALLED_APPS = [
   ...
    'channels', # Django implements websocket through it
]

WSGI_APPLICATION = 'HeartFailure.wsgi.application'

#channels requires adding ASGI_APPLICATION
ASGI_APPLICATION = 'HeartFailure.asgi.application'

#Using channel_layers requires configuring the channel
CHANNEL_LAYERS = {
    "default": {
        #1. Use memory as a channel (for development use)
        "BACKEND": "channels.layers.InMemoryChannelLayer",
        #2. Use redis (online use)
        # 'BACKEND': 'channels.layers.RedisChannelLayer',
        # 'CONFIG': {
        # 'hosts': [('localhost', 6379)],
        # },
    }
}

#####1. Cross-domain sharing configuration of cors resources
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    'VIEW',
)

CORS_ALLOW_HEADERS = (
    'XMLHttpRequest',
    'X_FILENAME',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'Pragma',
    'token' #Request header allows custom strings
)

3. Create websocket package

Overview: Put all wesocket-related requests into one package and manage them centrally.

Created under the websocket package:

  • routings.py

    • Store routing information related to websocket requests

  • consumers.py

    • Class that stores websocket request processing

  • update.py

    • When the data changes, the server actively notifies the front end to update the data.

  • send_data.py

    • When the front end initiates a request, the data returned

1. consumers.py

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
import time
import json
# Receive the front-end websocket request and directly send the required data to the individual
from apps.websocket.send_data import base_send


class AllDataConsumers(WebsocketConsumer):
    # Unified room name
    room_name = 'chat_all_data'

    def connect(self):
        cls = AllDataConsumers
        self.room_group_name = cls.room_name
        # Add to the room group, self.channel_name is the current
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )
        headers = self.scope['headers']
        print(headers)
        token=None
        for key, value in headers:
            if key == b'token':
                token = value.decode('utf-8')
                print(token)

            # Agree to create connection
        self.accept()

    def disconnect(self, close_code):
        print('A browser has exited websocket!!!!')
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    #Receive message from WebSocket
    def receive(self, text_data=None, bytes_data=None):
        '''
        :param text_data: Receive string type data
        :param bytes_data: Receive bytes type data
        :return:
        If the browser directly requests it, the result will be returned to the browser alone without sending data to the room group.
        '''
        try:
            text_data_json = json.loads(text_data)
            the_type = text_data_json.get('type', 'none')
        except Exception as e:
            self.send(
                json.dumps({'code': 400, 'msg': 'Please pass the data in the format of {"type":"xx","id":x,"params":{}}'}, ensure_ascii=False ))
            self.disconnect(400)
            return

        #1. When the front end actively requests the websocket, it gets the corresponding data and returns the data to the websocket separately.
        send_data = base_send(text_data_json)
        if isinstance(send_data,dict):
            #Need to return data to the requested front end
            self.send(json.dumps(send_data, ensure_ascii=False))
        else:
            #No need to return data to the requested front end
            pass

        # 2. Send data to the room group (no need to do this in non-chat mode)
        # async_to_sync(self.channel_layer.group_send)(
        # self.room_group_name, {'type':'send_to_chrome','data':send_data}
        # )
        '''
        Parameter Description:
        self.room_group, which room group to send data to,
        {'type':'send_to_chrome','data':send_data}
            send_to_chrome is the processing function, which is responsible for sending the data in the room group to the browser
            send_data data to send
        '''

    # Customized processing of data in room groups: real-time push is achieved using this
    def send_to_chrome(self, event):
        try:
            data = event.get('data')
            # Receive room group broadcast data and send the data to websocket
            self.send(json.dumps(data, ensure_ascii=False))
        except Exception as e:
            print('Failed to push message to global websocket')


2. send_data.py

def base_send(data:dict):
    '''
    Function: When initiating a websocket request, return data to the current websocket
    :param data: {'type':'Data type to be operated','id':'If you have an id, you specify each data','params':{'page':'Page number','page_size':'Page size', }}
    :return:
    '''
    the_type = data.get('type')
    id = data.get('id')
    send_data = {
        'type':the_type,
        'data':'returned data'
    }
    #User management-search function, user information is updated in real time
    if the_type == 'search_user_data':
        #When the front end initiates a websocket request, there is no need to return data in this type
        return send_data

3. update.py

#channels package related
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

class AllDataConsumersUpdate:
    '''
    Function: In the http view, push the specified message to the room group = chat_all_data
    '''

    def _make_channel_layer(self,send_data):
        '''
        :param send_data: Query the good data in the http view and send data to all websocket objects in the room group
        '''
        channel_layer = get_channel_layer()
        #Get the room group name
        group_name = 'chat_all_data'
        #Send data to the room group. Note the group_send method.
        async_to_sync(channel_layer.group_send)(
            group_name, #room group name, send data to this room group
            {
                'type':'send_to_chrome', #The consumer class that handles this room group must have a send_to_chrome method
                'data':send_data #Data to be sent to the websocket object
            }
        )
        '''
        send_to_chrome: The consumer corresponding to the room group must have this function. In this function, data is sent to all websocket objects in the room group.
        send_data: queried data
        '''
    #User management-search user page-update data in real time, and the front end will obtain the data by itself
    def search_user_data(self):
        send_data = {
            'type':'search_user_data',
            'page_update':1
        }
        #Send data to the room
        self._make_channel_layer(send_data=send_data)
        return True

4. routings.py

from django.urls import path
from .import consumers

# This variable is the route to store websocket
socket_urlpatterns = [
    path('socket/all/',consumers.AllDataConsumers.as_asgi()),

]

4. Modify the asgi.py file at the same level as settings.py

asgi.py

import os

from django.core.asgi import get_asgi_application

#new module
from channels.routing import ProtocolTypeRouter, URLRouter
#Import the routing module of websocket
from apps.websocket import routings

#Project name, directory name where settings.py is located
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project name.settings')

application = ProtocolTypeRouter({
    # http routing goes here
    "http": get_asgi_application(),
    # The routing variable socket_urlpatterns under the routings module under the chat application is the list of stored routes.
    "websocket": URLRouter(routings.socket_urlpatterns)
})

5. How to send websocket notification in view function

from apps.websocket.update import websocket_update_obj #Interface for websocket to push data

#Push can be achieved by directly calling the required method in the view function.
websocket_update_obj.search_user_data()

Putting all the push methods into one class can be easily managed, and unified modifications can also be achieved during later modifications.

6. Start the project

python manage.py runserver 8005

Seeing ASGI/Channels Version xxx indicates that the startup is successful. At this time, the Django project only supports websocket.

7. Test

Visit: EasySwoole-WebSocket online testing tool

  • 1. Service address: http://127.0.0.1:8005/socket/all/

  • 2. Click Connect

  • 3. Send data: {“type”:”search_user_data”}

  • 4. Click to send