Published 2023. 6. 15. 00:32

알고보니? 지난주를 마감하는 기록을 쓰지 않았기에 늦었지만 지금 써보려고 한다. 정리할 것은 내가 이해한 django websocket의 흐름이다. 난 라우팅 설정을 asgi.py에 직접 썼다. 프로토콜 타입 라우터를 통해 어떤 유형의 프로토콜을 진행할지 결정할 수 있다.

 

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": TokenAuthMiddleware(
        URLRouter([
            path('ws/alert/', commu.NotificationConsumer.as_asgi()),
        ]),
    ),
})

http 연결의 경우 get_asgi_application을...(기존 앱들이다) 웹소켓의 경우 미들웨어를 거친 아래의 라우팅을 사용하기로 말이다. JWT를 사용하는 프로젝트였기에 사용자 지정 미들웨어를 생성해서 사용하였다. 이 미들웨어 tokenAuthMiddleware를 거친 Consumer Class들을 url과 매핑하여 연결한다. 이제 프로토콜을 ws: 형태로, 해당하는 url에 들어가면 해당 consumer에서 정의된대로 연결된다

 

from channels.generic.websocket import AsyncWebsocketConsumer
import json

class NotificationConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope["user"]
        self.group_name = f"user_{self.user.id}"
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.group_name, self.channel_name)

    async def receive(self, text_data):
        pass

    async def notify(self, event):
        await self.send(text_data=json.dumps(event))

먼저 connect를 통해 연결될 때의 동작이 진행된다.  TokenAuthMiddleware에서 전달받은 스코프의 키 "user"로 부터 user 데이터를 건네받는다. group_name은 user_id로 부터 생성되어 각 유저마다 개별적인 채널 레이어 그룹을 부여받는다. channel_layer.group_add에서 group_name과 channel_name을 통해 개별 채널을 할당받았는데, channel_name은 클래스명으로 부터 자연스럽게 생성되는 것으로 나는 채널레이어가 전체 웹소켓 연결이라면 채널은 하나의 아파트(class)를, 그룹은 동 하나(특정 유저층)라고 이해했다.

 

이렇게 할당을 받아 accept되면 웹소켓 연결이 된 것이다. 내 예제에서 클라이언트와 연결이 해제되면 disconnect, 클라이언트가 서버로 메세지를 보낸다면 receive 메서드가(알림인데 유저가 서버로 알림을 보낼리가 없으니 여기선 비어있다), 마지막으로 서버에서 보낸 알람을 받기 위해 내가 생성한 notify 메서드가 비동기적으로 실행된다.

 

@receiver(post_save, sender=Comment)
def send_notification(sender, instance, created, **kwargs):
    if created:
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            f"user_{instance.post.user.id}",
            {
                "type": "notify",  # Calls NotificationConsumer.notify
                "event": "New Comment",
                "nickname": instance.user.nickname,
                "comment": instance.content,
                "post": instance.post.title,
            },
        )

메세지를 보내는 역할은 시그널에서 정의했는데, Comment가 save() 될때마다 실행된다. 채널 레이어의 약속된 그룹명을 박아넣고 아래의 형태의 객체를 보내는 것이다. 서버에서 각 클라이언트로 메세지를 보낼 때에는 notify 처럼 event를 포함한 메서드를 생성하여 처리하자.

'일지' 카테고리의 다른 글

2023.06.15  (0) 2023.06.16
2023.06.14  (0) 2023.06.15
2023.06.13  (1) 2023.06.13
2023.06.12  (0) 2023.06.13
2023.06.09  (0) 2023.06.09
복사했습니다!