본문 바로가기

내일배움 캠프/TIL

django channels를 이용한 실시간 알림

Django에서 실시간 알람을 구현하기위해서는 웹소켓을 통해서 사용자와 서버와의 실시간 연결을 통해 메세지를 보내야합니다. 이거를 하기위해서 asgi서버인 daphne 서버 세팅을 하고 알림의 조건에 맞추어서 ws로 메세지를 보내면됩니다

이거를 하기위해서 메세지 브로커인 redis를 settings에 설정을 해주고 INSTALLED_APPS에 daphne를 추가시켜줍니다

INSTALLED_APPS = [
    "daphne",
    ...
]

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis", 6379)],
        },
    },
}

ASGI_APPLICATION = "프로젝트.asgi.application"

그후 asgi.py에서

import os

from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application

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

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

설정을 해주면되는데 저희 프로젝트는 jwt token을 이용해서 로그인을 구현했기때문에 websocket를 따로 커스텀을 해가지고 구현을 했습니다

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

그후 alarm앱에서 라우팅을 해준다음에 comsumer에서

from django.urls import path

from . import consumers

websocket_urlpatterns = [
    path("ws/alarm/", consumers.AlarmConsumer.as_asgi()),
]
import json
from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer

from .models import Alarm
from .serializers import AlarmSerializer


def get_alarm(user):
    notifications = Alarm.objects.filter(user=user)
    serializer = AlarmSerializer(notifications, many=True)
    return serializer.data


class AlarmConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        user = self.scope.get("user")
        self.group_name = f"user{user.id}"
        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()
        alarms = await database_sync_to_async(get_alarm)(user)

        if alarms:
            await self.channel_layer.group_send(
                self.group_name, {"type": "send_alarm", "message": alarms}
            )

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

    async def send_alarm(self, event):
        message = event["message"]

        await self.send(text_data=json.dumps({"message": message}))

위 처럼 ws가 연결되었을때 메세지를 보낼때 끊길때를 작성해 주면 프론트에서 웹소켓으로 ws/alarm으로 연결했을때 connect가 실행이되고 끊길때 disconnect가 실행이되게됩니다