Django WebSocket AsyncConsumer [0]

Chung Hwan·2021년 8월 9일
0

Django

목록 보기
1/2

얼마전 고카톤(Korea Hacks 2021)에 참가했었다. 30시간 동안 열심히 개발했지만 코드가 상당히 개판이다.

모든 참사의 원흉은 WebSocket이었다.

django에서 WebSocket 통신을 구현하기 위해 삽질을 정말 많이 했다. 다음에 같은 삽질을 하지 않기 위해 좀 정리해보려 한다.

setting.py

소켓 통신을 위해 channels 모듈을 이용한다. django와 연동하려면 채널 레이어를 setting.py에 명시해 두어야한다.

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

routing.py

그러면 django가 알아서 routing.py 찾는다. 여기서 HTTP와 WebSocket을 분리해 routing하는 것이다.

from django.urls import path

from channels.routing import URLRouter, ProtocolTypeRouter
from channels.auth import AuthMiddlewareStack

from main.consumers import WriteConsumer
from main.consumers import AuthConsumer

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter([
            path("write/<str:board_url>/<str:session_id>/", WriteConsumer.as_asgi()),
            path("auth/<str:board_url>/<str:session_id>/", AuthConsumer.as_asgi()),
        ])
    )
})

이런 식으로 routing 설정을 하는데, 뭔가 익숙하지 않나?

urlpatterns = [
    path('new_board', views.make_board),
    path('<uuid:board_url>', views.get_board),
]

urls.py와 굉장히 비슷하다. routing.py의 consumer는 http를 처리할 때의 view와 비슷한 역할이라고 생각하면 될 것 같다.
그러면 그 다음 consumers.py를 작성해야 하고 그게 제일 어렵고 오래 걸릴 일이라는 것도 눈치챌 수 있다.

consumers.py

우리는 AsyncConsumer를 이용했다(참사의 시작). 클래스 이름에서 알 수 있듯, javascript 마냥 async로 동작한다. python에서 async 코딩은 아무리 해도 진짜 적응 안 된다. 기본적으로 동작하는 방법은 이렇다.

class EchoConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, event):
        await self.send({
            "type": "websocket.send",
            "text": event["text"],
        })

websocket_connect

클라이언트로부터 WebSocket 연결 요청이 들어오면, websocket_connect 함수가 동작한다. send()는 말그대로 클라이언트에게 응답을 보내고 위에서는 "type": "websocket.accept"를 보냄을 통해 WebSocket이 정상적으로 연결되었음을 알린다.

websocket_receive

그 이후 클라이언트로부터 WebSocket 패킷이 도착하면, websocket_receive 함수가 동작한다. 클라이언트는 json 형식으로 데이터를 보낼 수 있고 서버는 그 데이터를 websocket_receive 함수의 argument인 event로 받을 수 있다.

  • 예시
    클라이언트: {"text": "hello"}
    서버: text = event["text"] #text == "hello"

위에서 send({"type": "websocket.send", "text": event["text"]}) 라는 라인이 있다. 처음에 이 부분을 이해하기가 어려웠고 덕분에 삽질을 정말 많이 했다. 이건 좀 자세히 볼 필요가 있다.

send type

send()의 argument의 type은 Callback이라고 생각하면 된다. async에 callback이라니 이게 javascript인지 python인지... 아무튼 그렇다. 나머지는 그 콜백 함수에 들어가는 argument라고 보면 된다. 즉, send({"type": "websocket.send", "text": event["text"]})는 콜백으로 websocket.send(text=event["text"])를 넘긴다고 볼 수 있다.

그러니까 따지고 보면 아까 send()가 클라이언트에게 응답을 보낸다고 했는데 사실 그렇지 않을 수도 있는거다. 정확히는 send()에서 콜백으로 websocket.send(text=event["text"])로 넘어가고 여기서 클라이언트에게 응답이 간다. 그리고 그 응답 내용은 text가 된다.

고카톤 대회 당일 이러한 동작 방식을 제대로 이해하지 못하고 "text" 를 다르게 바꿨다가 왜 응답이 안 가는지 한참 해맸다.
예를 들어서 send({"type": "websocket.send", "pos": 1}) 이렇게 한다면 콜백 websocket.send(pos=1)로 넘어가고 websocket.send함수에서 text로 넘어온 argument가 없으니 아무 응답도 가지 않는다.

이 부분만 이해해도 django에서 websocket 구현하는게 훨씬 수월해진다.

1개의 댓글

comment-user-thumbnail
2022년 8월 17일

감사합니다

답글 달기