Django

Django Channels - Consumer 살펴보기

r잡초처럼 2023. 5. 5. 22:02

채팅 기능을 구현하려고 찾아보다 Django channels 라는 라이브러리를 보았다. 구현도 tutorial을 보고 따라가면 무리없이 구현할 수 있을 정도로 잘 정리되어 있었다. 그래서 구현에 대한 글을 쓰기보단 consumer, routing 등을 자세하게 살펴보고자 한다.

 

Django Channel 

Introduction

Channels는 Django의 기본 비동기 뷰 지원을 래핑하여, HTTP뿐만 아니라 장기 실행 연결이 필요한 프로토콜 - WebSockets, MQTT, 챗봇, 아마추어 라디오 등을 처리할 수 있도록 합니다. 이를 위해 Django의 동기 및 사용하기 쉬운 특성을 유지하면서, 동기식 Django 뷰와 유사한 스타일로 동기식으로, 완전히 비동기식으로 또는 둘 다의 혼합 방식으로 코드를 작성할 수 있습니다. 이에 더해, Django의 인증 시스템, 세션 시스템 등과 통합하여 HTTP 전용 프로젝트를 다른 프로토콜로 확장하는 것이 이전보다 쉬워졌습니다. Channels는 채널 레이어와 함께 이 이벤트 기반 아키텍처를 번들로 제공하여 프로세스 간 통신을 쉽게 할 수 있으며, 프로젝트를 여러 프로세스로 분리할 수 있습니다. Channels를 아직 설치하지 않으셨다면, 설치를 위해 Installation을 먼저 읽으시는 것이 좋습니다. 이 소개는 직접적인 튜토리얼은 아니지만, 기존 Django 프로젝트를 따라하고 변경하는 데 사용할 수 있습니다.

 

여기서 Consumer 를 좀 더 살펴보자

Consumer

 

 

정의

Consumer는 Channels 코드의 기본 단위입니다. 이벤트를 소비하므로 우리는 이를 Consumer라고 부르지만, 자체적인 작은 응용 프로그램으로 생각할 수 있습니다. 요청 또는 새 소켓이 들어오면 Channels는 라우팅 테이블을 따르고 - 곧 살펴볼 것입니다 - 해당 연결에 대한 올바른 Consumer를 찾아 복사본을 시작합니다. 이는 Django 뷰와 달리 소비자가 장기간 실행됨을 의미합니다. 짧게 실행될 수도 있습니다. HTTP 요청도 소비자로 서비스될 수 있기 때문입니다. 그러나 소비자는 잠시 살아있는 개념으로 구축되었습니다(위에서 설명한 대로 범위의 지속 시간 동안 살아 있습니다).

Consumer를 이벤트를 소비하는 작은 응용 프로그램으로 소개하고 있다. 여기서 event란 클라이언트와 서버 간의 통신에서 발생하는 메시지를 뜻한다. 예를 들어 클라인터가 WebSocket을 통해 서버에 메시지를 보낼 때, 이 메시지는 Consumer에서 'receive' 이벤트로 처리 된다.

 

Consumer 의 코드 예시는 다음과 같다:

class ChatConsumer(WebsocketConsumer):

    def connect(self):
        self.username = "Anonymous"
        self.accept()
        self.send(text_data="[Welcome %s!]" % self.username)

    def receive(self, *, text_data):
        if text_data.startswith("/name"):
            self.username = text_data[5:].strip()
            self.send(text_data="[set your username to %s]" % self.username)
        else:
            self.send(text_data=self.username + ": " + text_data)

    def disconnect(self, message):
        pass

각 이벤트를 처리하는 코드는 Channels 가 병렬로 스케줄링하고 실행한다. Channels는 완전한 비동기 이벤트 루프에서 실행되며, 위와 같은 코드를 작성하면 동기 스레드에서 호출 된다. 이는 Django ORM을 호출하는 것과 같은 블로킹 작업을 안전하게 수행할 수 있다는 것을 의미한다.

class LogConsumer(WebsocketConsumer):

    def connect(self, message):
        Log.objects.create(
            type="connected",
            client=self.scope["client"],
        )

혹은 완전히 비동기적인 Consumer를 작성할 수도 있다.

class PingConsumer(AsyncConsumer):
    async def websocket_connect(self, message):
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, message):
        await asyncio.sleep(1)
        await self.send({
            "type": "websocket.send",
            "text": "pong",
        })

참고 문서

1.  https://channels.readthedocs.io/en/stable/introduction.html#turtles-all-the-way-down