Django Channel real time push and chat

Let's take a look at the final effect

 

Start chatting, input message and click send message to start chatting

 

 

 

Click "get back-end data" to start real-time push

Let's take a brief look at Django Channel

Channels is a project that uses Django and extends its functions beyond HTTP to handle WebSocket, chat protocol, IoT protocol, etc. It's based on what's called ASGI Build the Python specification for.

It is based on the core of Django, and a completely asynchronous layer is layered below it, running Django itself in synchronous mode, but asynchronous processing of connections and sockets, and providing the option of writing in two ways, so as to achieve this.

Please refer to official documents for details: https://channels.readthedocs.io/en/latest/introduction.html

Let's talk about what ASGI is

ASGI was proposed by Django team to solve the problem of simultaneous processing of HTTP, HTTP2 and WebSocket protocols in a network framework (such as Django). To this end, the Django team developed the Django Channels plug-in, which brings the ASGI capabilities to Django.
In ASGI, a network request is divided into three processing layers. The first layer, the interface server, is responsible for parsing the request protocol and distributing different protocols to different channels. The Channel belongs to the second layer and can usually be a queue system. The Channel is bound to the Consumer of the third layer.  

Please refer to official documents for details: https://channels.readthedocs.io/en/latest/asgi.html

 

Next, let's talk about the specific implementation steps

For reprint, please indicate the original link: https://www.cnblogs.com/Sunzz/p/12788608.html

1, Install channel

pip3 install channels 
pip3 install channels_redis

2, New Django project

1. New project

django-admin startproject mysite

2. New application

python3 manage.py startapp chat

3. Edit mysite/settings.py file

#Sign up for apps
INSTALLED_APPS = [
    ....
    'chat.apps.ChatConfig',
    "channels",
]

# Add the following configuration at the end of the file
#take ASGI_APPLICATION Set to point to this routing object as your root application: ASGI_APPLICATION = 'mysite.routing.application' #To configure Redis CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('10.0.6.29', 6379)], }, }, }

3, Detailed code and configuration

1. Add template of index view

Create a templates directory in the chat directory. In the templates directory you just created, create another directory named chat, and create a file named index.html in it to save the template of the indexed view

Put the following code in chat/templates/chat/index.html

<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Rooms</title>
</head>
<body>
    What chat room would you like to enter?<br>
    <input id="room-name-input" type="text" size="100"><br>
    <input id="room-name-submit" type="button" value="Enter">

    <script>
        document.querySelector('#room-name-input').focus();
        document.querySelector('#room-name-input').onkeyup = function(e) {
            if (e.keyCode === 13) {  // enter, return
                document.querySelector('#room-name-submit').click();
            }
        };

        document.querySelector('#room-name-submit').onclick = function(e) {
            var roomName = document.querySelector('#room-name-input').value;
            window.location.pathname = '/chat/' + roomName + '/';
        };
    </script>
</body>
</html>

2. Create chat and message push template

chat/templates/chat/room.html

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js" type="text/javascript"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="150" rows="30" class="text"></textarea><br>
<input id="chat-message-input" type="text" size="150"><br>
<input id="chat-message-submit" type="button" value="send message" class="input-sm">
<button id="get_data" class="btn btn-success">Get back-end data</button>
{{ room_name|json_script:"room-name" }}

<script>

    $("#get_data").click(function () {
        $.ajax({
            url: "{% url 'push' %}",
            type: "GET",
            data: {
                "room": "{{ room_name }}",
                "csrfmiddlewaretoken": "{{ csrf_token }}"
            },
        })
    });

    const roomName = JSON.parse(document.getElementById('room-name').textContent);
    const chatSocket = new WebSocket(
        'ws://' + window.location.host
        + '/ws/chat/'
        + roomName + '/'
    );
    let chatSocketa = new WebSocket(
        "ws://" + window.location.host + "/ws/push/" + roomName
    );
    chatSocket.onmessage = function (e) {
        const data = JSON.parse(e.data);
        // data To receive data from the backend
        //console.log(data);
        document.querySelector('#chat-log').value += (data.message + '\n');
    };
    chatSocketa.onmessage = function (e) {
        let data = JSON.parse(e.data);
        //let message = data["message"];
        document.querySelector("#chat-log").value += (data.message + "\n");
    };


    chatSocket.onclose = function (e) {
        console.error('Chat socket closed unexpectedly');
    };
    chatSocketa.onclose = function (e) {
        console.error("Chat socket closed unexpectedly");
    };
    document.querySelector('#chat-message-input').focus();
    document.querySelector('#chat-message-input').onkeyup = function (e) {
        if (e.keyCode === 13) {  // enter, return
            document.querySelector('#chat-message-submit').click();
        }
    };

    document.querySelector('#chat-message-submit').onclick = function (e) {
        const messageInputDom = document.querySelector('#chat-message-input');
        const message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message
        }));
        messageInputDom.value = '';
    };
</script>
</body>
</html>

3. Create a view of the room

Put the following code in chat/views.py

# chat/views.py
from django.shortcuts import render
from django.http import JsonResponse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


def index(request):
    return render(request, "chat/index.html")


def room(request, room_name):
    return render(request, "chat/room.html", {"room_name": room_name})


def pushRedis(request):
    room = request.GET.get("room")
    print(room)

    def push(msg):
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            room,
            {"type": "push.message", "message": msg, "room_name": room}
        )

    push("Push test", )
    return JsonResponse({"1": 1})

4. Create project secondary route

Create a file named urls.py in chat directory

# mysite/chat/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
   path('<str:room_name>/', views.room, name='room'),
]

5. Modify the root route

# mysite/urls.py

from
django.contrib import admin from django.urls import path, include from chat.views import pushRedis urlpatterns = [ path('admin/', admin.site.urls), path("chat/", include("chat.urls")), path("push", pushRedis, name="push"), ]

6. Create a consumer

File chat/consumers.py

When Django accepts a HTTP request, it queries the root URLconf to find the view function and then calls the view function to process the request. Similarly, when Channels accept a WebSocket connection, it queries the root routing configuration to find the consumer, and then invokes various functions on the consumer to handle events from the connection.

import time
import json
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
import redis

pool = redis.ConnectionPool(
    host="10.0.6.29",
    port=6379,
    max_connections=10,
    decode_response=True,
)
conn = redis.Redis(connection_pool=pool, decode_responses=True)


class ChatConsumer(AsyncWebsocketConsumer):
    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):
        print("close_code: ", close_code)
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data=None, bytes_data=None):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        print("receive_message: ", message)
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                "type": "chat_message",
                "message": message
            }
        )

    async def chat_message(self, event):
        receive_message = event["message"]
        response_message = "You message is :" + receive_message
        await self.send(text_data=json.dumps({
            "message": response_message
        }))


class PushMessage(WebsocketConsumer):

    def connect(self):
        self.room_group_name = self.scope["url_route"]["kwargs"]["room_name"]
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        self.accept()

    def disconnect(self, code):
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    def push_message(self, event):
        """
        //Active push
        :param event:
        :return:
        """
        print(event, type(event))
        while True:
            time.sleep(2)
            msg = time.strftime("%Y-%m-%d %H:%M:%S") + "---  room_name: %s" % event["room_name"]
            self.send(text_data=json.dumps(
                {"message": msg}
            ))

7. Add routing configuration of websocket for the project

Create a file named routing.py in chat directory

# mysite/chat/routing.py

from django.urls import re_path, path
from . import consumers

websocket_urlpatterns = [
    re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer),
    path("ws/push/<room_name>", consumers.PushMessage),
]

8. Configure websocket root route

New ws root routing file routing.py with setting directory

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import chat.routing

application = ProtocolTypeRouter({
    "websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})

9. The final document relationship is as follows

10. Start service

python3 manage.py runserver 10.0.6.2:80

Note that this is not the same as django

 

There is another more robust way to start

Add asgi.py file at the same level as setting

import os
import django
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()
application = get_default_application()

The starting mode is:

daphne -b 10.0.6.2 -p 80 mysite.asgi:application
daphne is installed automatically when the channel is installed

Reference:

  https://channels.readthedocs.io/en/latest/tutorial/index.html

  https://blog.ernest.me/post/asgi-demonstration-realtime-blogging

Tags: Python Django JSON Redis network

Posted on Wed, 29 Apr 2020 17:44:34 -0700 by cdog5000