장고 실시간 채팅(Channels와 redis server 이용)
		
		
		
		
		
		둘러보기로 가기
		검색하러 가기
		
		
	
1 개요
https://channels.readthedocs.io/en/latest/index.html를 참고하였다.
장고는 기본적으로 동기식으로, 채팅을 위해선 비동기식 처리가 필요하다.
이를 위해 Django Channels가 마련되어 있다.
ASGI(Async Server Gateway Interface)프로토콜은 WSGI를 계승하여 이와 잘 호환되도록 설계되어 있다.
ASGI는 비동기 요청인 웹 소켓을 처리하는 이벤트로 connect, send, receive, disconnect가 있다.
2 기초준비
2.1 채팅어플 만들기
패키지설치부터 진행하기엔 중간점검이 어려워 틀 만들기를 먼저 수행한다.
| 과정 | 설명 | 방법 | 
|---|---|---|
| 어플리케이션생성 | 채팅을 위한 앱을 생성한다.
 앱을 생성하고 __init__.py와 views.py를 제외한 모든 것들을 지운다.  | 
django-admin startapp chat | 
| 앱 등록 | settings.py에 추가. | INSTALLED_APPS = [
    'chat',
 | 
| URL매핑 | 기본이 되는 urls.py 안에서 해당 앱으로 매핑을 시켜준다. | path('chat/', include('chat.urls')), | 
| URL매핑2 | 그리고 앱 안의 urls.py 작성. | from dfango.urls import path
from . import views
urlpatterns = [
    path('/', views.index, name='index'),
]
 | 
| view 작성 | from django.shortcuts import render
def index(request):
    return render(request, 'chat/index.html', {})
 | |
| 탬플릿 준비 | 앱 하위에 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>
 | 
여기까지 하고 runserver 후 제대로 작동하는지 확인하자.
2.2 패키지 설치
| 과정 | 설명 | 방법 | 
|---|---|---|
| 패키지 설치 | Channels 패키지를 설치한다. | pip install -U channels | 
| 앱 등록 | settings.py에 추가.
 channels는 runserver 명령을 제어하여 기존 서버를 대체한다.  | 
INSTALLED_APPS 하위, 가장 처음에 'channels'넣는다.INSTALLED_APPS = [
    'channels',  # 다른 서드파티 앱과 충돌할 수 있어 가장 처음에 둔다.
    ...  # 공식 튜토리얼에선 'chat'을 상위로 올리지만, 아래에 있어도 상관 없다.
    ...
    'chat',
 | 
| 라우팅 설정 작성 | 가장 상위의 디렉터리에 routing.py를 다음과 같이 작성한다.
 (취향에 따라 달리 작성해도 된다.)  | 
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({
    # (http->django views is added by default)
})
 | 
| 설정 추가 | settings.py 안에 우측의 내용을 추가한다.
 라우팅파일의 위치만 잘 잡아주면 된다.  | 
ASGI_APPLICATION = 'routing.application'  # routing.py 파일의 application을 불러온다.
 | 
3 채팅룸 구현
| 과정 | 설명 | 방법 | 
|---|---|---|
| URL매핑 | 앱 내의 urls.py에 추가. | urlpatterns = [
    ...
    path('<str:room_name>/', views.room, name='room'),
    ...
]
 | 
| 뷰 작성 | room 뷰 작성
 mark_safe와 json을 가져온다.  | 
from django.shortcuts import render
from django.utils.safestring import mark_safe
import json
def room(request, room_name):
    return render(request, 'chat/room.html', {
        'room_name_json': mark_safe(json.dumps(room_name))
    })
 | 
| 탬플릿 작성 | /char/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"/>
</body>
<script>
    var roomName = {{ room_name_json }};
    var chatSocket = new WebSocket(
        'ws://' + window.location.host +
        '/ws/chat/' + roomName + '/');
    chatSocket.onmessage = function(e) {
        var data = JSON.parse(e.data);
        var message = data['message'];
        document.querySelector('#chat-log').value += (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) {
        var messageInputDom = document.querySelector('#chat-message-input');
        var message = messageInputDom.value;
        chatSocket.send(JSON.stringify({
            'message': message
        }));
        messageInputDom.value = '';
    };
</script>
</html>
 | 
3.1 참가자 구현
| 과정 | 설명 | 방법 | 
|---|---|---|
| 참가자 파일 작성 | /chat/consumers.py 작성.
 모든 요청을 받아들이는 비동기적인 웹소켓. 클라이언트로부터 메시지를 받아서 그대로 전달.  | 
from channels.generic.websocket import WebsocketConsumer
import json
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/routing.py 작성 | from django.conf.urls import url
from . import consumers
websocket_urlpatterns = [
    url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
]
 | 
| 라우팅 등록 | /routing.py 수정
 chat 안의 라우팅을 등록해준다. 그리고 임포트하는 모듈을 수정해준다.  | 
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
application = ProtocolTypeRouter({
    # (http->django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),
})
 | 
여기까지 하고 채팅을 쳐 보면... 채팅이 나와야 정상.
3.2 체널레이어 구현
| 과정 | 설명 | 방법 | 
|---|---|---|
| 패키지 설치 | Channels가 Redis인터페이스를 인식하도록. | pip install channels_redis | 
| settings.py 설정 | ASGI 아래 추가하자. | ASGI_APPLICATION = 'routing.application'  # routing.py 파일의 application을 불러온다.
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}
 |