ddit/Python

Django - 웹소켓을 이용한 실시간 채팅구현하기

ssong2ku 2022. 9. 19. 13:02
728x90

1. 환경세팅

Redis-x64-5.0.14.1.msi
6.79MB

pip install redis
pip install channels
pip install channels_redis

2. index.html 만들기

<!DOCTYPE html> 
<html> 
<head> 
  <meta charset="utf-8"/> 
  <title>간단한 채팅에 입실하기</title> 
</head> 
<body> 
  <label>입장할 채팅방 이름:</ label> 
  <input id="room-name-input" type="text" size="100">  
  <input id="room-name-submit"type="button" value="입실">

  <script> 
    // 채팅방에 들어가는 로직
    const submit = document.querySelector('#room-name-submit')

    input.focus()   // 포커스를 입력란에 설정 
    input.onkeyup = (ev) => { 
      if (ev.keyCode === 13) { 
        // ENTER 키를 누르면 입실 버튼을 누른다 
        submit.click() 
      } 
    }

    submit.onclick = (ev) => { 
      // 입장 버튼이 클릭되면 입력 된 대화방 이름의 페이지 (경로)로 이동 
      window.location.pathname = '/chat/' + input.value + '/' 
    } 
  </script> 
</body> 
</html>

 

3-1. chat-view

from django.shortcuts import render

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

 

3-2. chat -url 

from django.urls import path
from chat.views import index


from . import views

urlpatterns = [ 
    path('' , index , name='index'), 
    
]

 

3-3. testchat-url 

"""TESTCHAT URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include,path


urlpatterns = [
    path('admin/', admin.site.urls),
    path('chat/', include('chat.urls')) ,
]

 

4. 채널 만들기

routing.py생성

-> 어떤 네트워크 안에서 통신 데이터를 보낼 때 최적의 경로를 선택하는 과정

# chat/routing.py
from django.urls import re_path

from . import consumers

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

5.setting.py 수정

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'TESTCHAT.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'TESTCHAT.wsgi.application'

 

6. room추가

<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Chat Room</title>
</head>
<body>
    <textarea id="chat-log" cols="100" rows="20"></textarea><br>
    <input id="chat-message-input" type="text" size="100"><br>
    <input id="chat-message-submit" type="button" value="Send">
    {{ room_name|json_script:"room-name" }}
    <script>
        const roomName = JSON.parse(document.getElementById('room-name').textContent);

        const chatSocket = new WebSocket(
            'ws://'
            + window.location.host
            + '/ws/chat/'
            + roomName
            + '/'
        );

        chatSocket.onmessage = function(e) {
            const data = JSON.parse(e.data);
            document.querySelector('#chat-log').value += (data.message + '\n');
        };

        chatSocket.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>

-> view 수정

from django.shortcuts import render

def index(request): 
    return render(request, 'chat/index.html')
# Create your views here.
def room(request, room_name):
    return render(request, "chat/room.html", {"room_name": room_name})

->url수정

from django.urls import path
from chat.views import index


from . import views

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

 

7.user생성

# # chat/consumers.py
# import json
#
# from channels.generic.websocket import WebsocketConsumer
#
#
# class ChatConsumer(WebsocketConsumer):
#     def connect(self):
#         self.accept()
#
#     def disconnect(self, close_code):
#         pass
#
#     def receive(self, text_data):
#         text_data_json = json.loads(text_data)
#         message = text_data_json["message"]
#
#         self.send(text_data=json.dumps({"message": message}))
# chat/consumers.py
import json

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        print("ChatConsumer","connect");
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = "chat_%s" % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name, self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        print("ChatConsumer","disconnect");
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name, self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        print("ChatConsumer","receive");
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name, {"type": "chat_message", "message": message}
        )

    # Receive message from room group
    def chat_message(self, event):
        print("ChatConsumer","chat_message");
        message = event["message"]

        # Send message to WebSocket
        self.send(text_data=json.dumps({"message": message}))

 

8. testchat routing 추가

#TESTCHAT-asgi
"""
ASGI config for TESTCHAT project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""

import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TESTCHAT.settings')
django_asgi_app = get_asgi_application()

import chat.routing

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns))
        ),
    }
)
#TESTCHAT-wsgi
"""
WSGI config for TESTCHAT project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TESTCHAT.settings')

application = get_wsgi_application()
728x90