Django websocket 적용하기

김혁준·2024년 2월 23일
0

django

목록 보기
18/18

프로젝트에 채팅,알람 기능을 만들기 위해 프론트와 백엔드에 websocket를 적용했다.
1. 세팅
https://channels.readthedocs.io/en/latest/ 문서 참고
2. middleware overriding

  • 알람과 채팅기능을 이용할때 로그인 한 유저만 이용가능하게 만들어야 하는 상황에서 로그인했는지 안했는지 판단하기 위해 wetsocket연결시 user_id의 유무를 확인하는 작업을 진행했다.
middleware.py
from channels.db import database_sync_to_async
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from channels.db import database_sync_to_async
from users.models import User


@database_sync_to_async
def get_user(user_id):
    try:
        return User.objects.get(id=user_id)
    except User.DoesNotExist:
        return AnonymousUser()


class QueryAuthMiddleware:

    def __init__(self, app):
        # Store the ASGI application we were passed
        self.app = app

    async def __call__(self, scope, receive, send):

        scope["user"] = await get_user(
            int(scope["query_string"].decode().split("=")[-1])
        )

        return await self.app(scope, receive, send)

연결 시도시 QueryAuthMiddleware가 실행되는데(세팅은 문서참고) 프론트에서 유저 아이디를 쿼리 파라미터로 받으면 해당 유저를 반환한다.
3. api작성

  • 처음 연결할때 프론트로 채팅목록과 알림목록을 보내기 위한 코드를 작성했다.
class MessageViewSet(viewsets.ViewSet):
    def list(self, request):
        try:
            message = Message.objects.all()
        
            serializer = MessageSerializer(message, many=True)
            return Response(serializer.data)
        except Exception as e:
            print(e)
            return Response([])


class NotificationViewSet(viewsets.ViewSet):
    def list(self, request):
       
        try:
            notification = Notification.objects.filter(reciever=request.user).order_by(
                "-created_at"
            )
            serializer = NotificationSerializer(notification, many=True)
            return Response(serializer.data)
        except Exception as e:
            print(e)
            return Response([])
  1. websocket 연결 consumer.py 코드 작성
  • 채팅
class WebChatConsumer(websocket.JsonWebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = None

    def connect(self):
        self.user = self.scope["user"]
        self.accept()
        if not self.user.is_authenticated:
            self.close(code=4001)
        self.user = User.objects.get(id=self.user.id)

        async_to_sync(self.channel_layer.group_add)("all_users", self.channel_name)

    def receive_json(self, content):

        sender = self.user
        message = content["message"]
        new_message = Message.objects.create(sender=sender, content=message)
        async_to_sync(self.channel_layer.group_send)(
            "all_users",
            {
                "type": "chat.message",
                "new_message": {
                    "id": new_message.id,
                    "sender": new_message.sender.username,
                    "content": new_message.content,
                    "timestamp": new_message.timestamp.isoformat(),
                    "sender_id": new_message.sender.id,
                    "sender_image": new_message.sender.image,
                },
            },
        )

    def chat_message(self, event):

        self.send_json(event)

    def disconnect(self, close_code):
        async_to_sync(self.channel_layer.group_discard)("all_users", self.channel_name)
        super().disconnect(close_code)

처음 연결될때 connect함수 실행, 인증되지 않은 사용자면 4001코드 반환하면서 연결 종료. 그렇지 않으면 async_to_sync 사용해서 그룹에 유저 추가.
프론트에서 유저가 메세지를 보내면 receive_json 함수 실행. 메세지를 Message모델에 추가하고 프론트 화면을 유저가 보기 좋게 꾸미기 위한 정보를 넣어서 chat_message함수에 event로 전달.("type": "chat.message"로 보내면 chat_message함수가 실행된다)
chat_message함수에서 받은 메세지를 프론트로 보냄. 연결이 종료될 경우 disconnect함수가 실행되어 연결이 종료된다.

  • 알림
@receiver(post_save, sender=Notification)
def send_update(sender, instance, created, **kwargs):

    serializer = NotificationSerializer(instance)

    if created:

        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            str(instance.reciever.id),
            {"type": "notify", "data": serializer.data},
        )
class NotificationConsumer(websocket.JsonWebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = None

    def connect(self):
        self.user = self.scope["user"]
        self.accept()
        if not self.user.is_authenticated:
            self.close(code=4001)
        self.user = User.objects.get(id=self.user.id)

        async_to_sync(self.channel_layer.group_add)(
            str(self.user.id), self.channel_name
        )

    def receive_json(self, content):
        pass

    def notify(self, event):

        self.send_json(event)

    def disconnect(self, close_code):
        async_to_sync(self.channel_layer.group_discard)(
            str(self.user.id), self.channel_name
        )
        super().disconnect(close_code)

알림 기능은 Notification모델의 정보가 db에 저장될때 실행되도록 했다. reciever 데코레이션을 통해 Notification이 생성될때 send_update 함수가 실행된다. 이 함수에서는 async_to_sync를 사용하여 알림을 보낸다.
알림을 받으면 notify 함수가 실행된다.

다음에는 프론트에서 어떻게 websocket을 연결하는지 작성할 예정.

profile
스프링 개발자 지망생입니다

0개의 댓글