본문 바로가기
개발 공부/Django

django channels - Tutorial Part 2: Implement a Chat Server

by 느림보어른 2021. 7. 4.

Channels의 ws 처리 과정

Channels은 웹 소켓 연결을 수락할 때 root routing 구성을 참조하여 consumer를 조회한 다음, 연결에서 이벤트를 처리하기 위해 소비자의 다양한 함수들을 호출합니다.

이 과정은 Django가 HTTP 요청을 수락하면 root URLconf를 참조해 view 함수를 조회한 후 요청을 처리하기 위해 view 함수를 호출하는 것과 유사하다.

즉, 정리하면 다음과 같습니다.

Django URLconf consumer
Channels routing  view 함수

웹 소켓과 일반 HTTP 연결에 관한 Tip

/ws/와 같은 공통 경로 접두사를 사용하여 웹 소켓 연결을 일반 HTTP 연결과 구별하는 것이 좋습니다. 특정 구성의 프로덕션 환경에 채널을 쉽게 배포할 수 있기 때문입니다.

대규모 사이트의 경우

  1. 일반적인 HTTP 요청에 대한 Gunicorn+Django와 같은 운영 등급 WSGI 서버 구성
  2. 웹 소켓 요청에 대한 운영 등급 ASGI 서버로의 경로에 따라 요청을 라우팅 하도록 nginx와 같은 운영 등급 HTTP 서버를 구성

소규모 사이트의 경우

별도의 WSGI 서버를 보유하지 않고 Daphne(Channels의 official ASGI HTTP/WebSocket server)가 HTTP 및 WebSocket 등 모든 요청을 처리하는 간단한 구축 전략을 사용할 수 있습니다. 이 배포 구성에서는 /ws/와 같은 공통 경로 접두사가 필요하지 않습니다.

Consumer

비동기처리에 관해 주의해야 할 점

채널은 더 높은 성능을 위해 비동기 consumers를 지원합니다. 그러나 비동기 consumers는 Django Model에 접근하는 것과 같은 차단 작업을 직접 수행하지 않도록 주의해야 합니다.

Routing

chat의 routing

# 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()),
]

각 사용자 연결에 대한 consumer 인스턴스를 인스턴스화하는 ASGI 애플리케이션을 얻기 위해 as_asgi() 클래스 메서드를 호출합니다. 이는 요청당 Django 보기 인스턴스에 대해 동일한 역할을 수행하는 django의 as_view()와 유사합니다.

root의 routing

# config/asgi.py
import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import chat.routing

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")

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

이전 과정에서 설계한 asgi.py에서

"websocket": AuthMiddlewareStack(
        URLRouter(
            chat.routing.websocket_urlpatterns
        )
    ),

가 추가되었다.

ProtocolTypeRouter

연결 유형을 검사합니다.

AuthMiddlewareStack

Django의 AuthenticationMiddleware가 수신되는 모든 HttpRequest 객체에 현재 로그인된 사용자를 나타내는 사용자 속성을 추가하는 방법과 마찬가지로 AuthMiddlewareStack은 현재 인증된 사용자에 대한 참조로 scope를 결정합니다. 그런 다음 URLRouter에 연결됩니다.

URLRouter 

The URLRouter 작성한 URL 패턴을 기준으로 특정 consumer에게 라우팅하기 위해 HTTP 경로를 검사합니다.

channel layer

channel layer은 통신 시스템의 일종이다. 여러 consumer 인스턴스가 서로, 그리고 Django의 다른 부분과 대화할 수 있게 해 줍니다.

 

channel layer 다음과 같은 추상화를 제공합니다.

  • channel은 메시지를 보낼 수 있는 편지함입니다. 각 채널에는 이름이 있습니다. 채널 이름을 가진 사람은 누구나 채널로 메시지를 보낼 수 있습니다.
  • group은 관련 채널의 그룹입니다. group에는 이름이 있습니다. 그룹 이름을 가진 사람은 누구나 이름으로 group에 채널을 추가/제거하고 그룹의 모든 채널에 메시지를 보낼 수 있습니다. 특정 그룹에 어떤 채널이 있는지 열거할 수 없습니다.

모든 consumer 인스턴스는 자동으로 생성된 고유한 channel 이름을 가지므로 channel layer을 통해 통신할 수 있습니다.

채팅 응용 프로그램에서는...

채팅 응용 프로그램에서는 동일한 룸에 있는 여러 ChatConsumer 인스턴스가 서로 통신하도록 하고 싶습니다. 이를 위해 각 채팅 consuomer가 룸 이름을 기반으로 하는 그룹에 channel을 추가하도록 하겠습니다. 이렇게 하면 Chat Consumer가 동일한 룸에 있는 다른 모든 Chat Consumer에게 메시지를 전송할 수 있습니다.

튜토리얼의 consumer code 리뷰

  • self.scope['url_route']['kwargs']['room_name']
    • consumer에게 WebSocket 연결을 연 chat/routing.py의 URL 경로에서 'room_name' 매개 변수를 가져옵니다.
    • 모든 소비자는 특히 URL 경로의 위치 또는 키워드 인수와 현재 인증된 사용자(있는 경우)를 포함하여 연결에 대한 정보를 포함하는 scope를 가집니다.
  • self.room_group_name = 'chat_%s' % self.room_name
    • 사용자가 지정한 룸 이름에서 직접 채널 group 이름을 구성합니다.
    • group 이름에는 문자, 숫자, 하이픈 및 마침표만 포함될 수 있습니다.
  • async_to_sync(self.channel_layer.group_add)(...)
    • group에 참여합니다.
    • ChatConsumer는 동기 웹 소켓 consumer이지만 비동기 cahnnel layer 메서드를 호출하기 때문에 async_to_sync()로 감싸줘야 한다. (모든 channel layer 메서드는 비동기식입니다.)
    • group 이름은 ASCII 영숫자, 하이픈 및 마침표로만 제한됩니다.
  • self.accept()
    • WebSocket 연결을 수락합니다.
    • connect() 메서드 내에서 accept()을 호출하지 않으면 연결이 거부되고 닫힙니다. 예를 들어 요청한 사용자에게 요청된 작업을 수행할 수 있는 권한이 없기 때문에 연결을 거부할 수 있습니다.
    • 연결을 수락하도록 선택한 경우 accept()를 connect()의 마지막 작업으로 호출하는 것이 좋습니다.
  • async_to_sync(self.channel_layer.group_discard)(...)
    • group에 나갑니다.
  • async_to_sync(self.channel_layer.group_send)
    • group에 이벤트를 보냅니다.
    • 이벤트에는 이벤트를 수신하는 consumer에게 호출해야 하는 메서드의 이름에 해당하는 특수 'type' 키가 있습니다.