Channels를 이용하여 장고 웹 채팅 서비스를 만들기에 앞서 Channels를 구성하는 주요 패키지에 대해 먼저 알아보는 시간을 가져보자.
패키지
목적
channels(필수)
장고의 통합 레이어
daphne(필수)
ASGI 서버로써 채널스 4.0부터 장고/채널스 개발서버로서 사용된다. 또한 실서비스에서는 daphne 명령이나 gunicorn/uvicorn 명령을 사용하여, 장고 서버를 구동한다.
channels_redis(옵션)
Channels 구동에 필수는 아니지만, 채팅 서비스에서는 프로세스간 통신이 필요하기에 필수이다.
위의 표에서 확인할 수 있듯이, channels와 daphne는 필수 라이브러리이다. channels 버전 4.0부터 runserver 명령은 channels가 아니라 daphne를 통해 수행된다. 그리고 채널스에서 레디스 활용을 위해 channels_redis 라이브러리가 필요하다. 해당 라이브러리를 통해 프로세스간 통신이 가능해진다.
scope
현재 요청의 세부내역이 담긴 사전(dict)
장고의 처리 주체는 뷰 함수/클래스
# views.py# 장고 함수 기반 뷰defchatroom_list(request):
# ...# 장고 클래스 기반 뷰에도 HttpRequest가 있다.from django.views.generic import ListView
classChatRoomListView(ListView):
# ...# urls.py
urlpatterns = [
path("chat1/", chatroom_list),
path("chat2/", ChatRoomListView.as_view()),
]
장고 기본에서는 HTTP 요청을 처리하는 주체는 View이다. 위의 코드에서 볼 수 있듯이 함수와 클래스 형태로 구현하였다. 하지만 채널스에서는 HTTP와 웹소켓 요청을 처리하는 주체가 아래의 코드에서 볼 수 있듯이 Consumer 클래스가 된다. 이때, 함수 구현은 지원되지 않으며, 클래스로만 구현할 수 있다.
Consumer 클래스는 채널스에서 요청을 처리하는 주체로서 일관된 처리방법을 제시한다. 웹소켓/HTTP 프로토콜을 처리하는 기능과 채널레이어를 통해 메시지를 보내고 받는 부분까지 모두 지원해 주기에 반복을 줄이고, 최소한의 코드로 비즈니스 로직에 더욱 집중할 수 있도록 도와준다.
# chat/cosumers.py# 웹소캣 클라이언트와 1:1 통신classEchoConsumer(WebsocketConsumer):
defconnect(self):
# 들어오는 웹소캣 연결요청을 모두 허용한다.
self.accept()
defreceive(self, text_data = None, bytes_data = None):
# 웹소캣 클라이언트로부터의 텍스트/바이러니 메세지를 그대로 회신한다.
self.send(text_data)
# chat/consumers.py# 라이브블로그에 연결된 웹소캣 클라이언트에게 포스팅 생성/수정/삭제 시에 즉각 알림을 보내어# 브라우저 새로고침없이도 웹 프론트엔드에서 즉각적으로 포스팅 추가/수정/삭제를 반영할 수 있도록 한다.classLiveblogConsumer(WebsocketConsumer):
# WebsocketConsumer에서는 self.groups에 지정된 그룹에 자동으로 add/discard를 수행한다.
groups = ["liveblog"]
defconnect(self):
# 들어오는 웹소캣 연결요청을 모두 허용한다.
self.accept()
# 그룹을 통해 받은 메세지를 그대로 웹소캣 클라이언트에게 전달한다.defliveblog_post_created(self, event_dict):
post_id = event_dict["post_id"]
self.send(json.dumps({
"type" : "liveblog.post.created",
"post_id" : post_id,
})
defliveblog_post_updated(self, event_dict):
post_id = event_dict["post_id"]
self.send(json.dumps({
"type" : "liveblog.post.updated",
"post_id" : post_id,
})
defliveblog_post_deleted(self, event_dict):
post_id = event_dict["post_id"]
self.send(json.dumps({
"type" : "liveblog.post.deleted",
"post_id" : post_id,
})
Routing
View 함수/클래스도 요청 URL에 따라 그 요청을 처리할 View 함수를 결정할 수 있듯이 채널스에서는 세 가지 기준으로 현재 요청을 처리할 Consumer Instance를 결정할 수 있다.
# chat/routing.py : 장고의 urls.py와 유사한 역할from django.urls import path
from chat import consumers
websocket_urlpatterns = [
# ws : //hostname/ws/echo/ 요청에 대응
path("ws/echo/", consumers.EchoConsumer.as_asgi()),
# ws : //hostname/ws/liveblog/ 요청에 대응
path("ws/liveblog/", consumers.LiveblogConsumer.as_asgi()),
# ws : //hostname/ws/chat/1234/chat/ 요청에 대응# URL을 통해 채팅방을 구별한다면?
path("ws/chat/<int:room_pk>/chat/", consumers.ChatConsumer.as_asgi()),
]
# 워커 역할을 할 Consumer에 채널명(Channel name) 부여
worker_mapping = {
"thumbnail-generate" : ThumbnailGenerateConsumer.as_asgi(),
}
# 프로젝트/asgi.pyfrom channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTING_MODULE", "프로젝트.settings")
# 프로젝트 초기화 과정을 수행
django_asgi_applicatioin = get_asgi_application()
import chat.routing
application = ProtocolTypeRouter( # ← 1단계) 프로토콜 타입에 의한 라우팅
{
# http 요청일 때 처리"http" : django_asgi_application,
# websocket 요청일 때 처리"websocket" : URLRouter( # ← 2.1단계) 요청 URL에 의한 라우팅
chat.routing.websocket_urlpatterns
),
# worker 요청일 때 처리"channel" : ChannelNameRouter(
chat.routing.worker_mapping # ← 2.2단계) 채널명에 의한 라우팅# | other_workers
),
}
)
첫 번째 기준으로 프로토콜 타입으로, HTTP 프로토콜 요청과 WebSocket 프로토콜 요청을 구별한다. 이때 ProtocolTypeRouter를 활용하게 된다. 두 번째 기준으로는 요청 URL 문자열을 분기한다. URLRouter를 활용하며, 구현하는 대다수의 Consumer 클래스는 개별 URL을 가지고, URLRouter에 등록하게 될 것이다. 세 번째 기준으로는 채널명에 의한 라우팅이다. 이는 channels worker에서 사용한다.
Cookies/Session/Auth
맨 처음 잠깐 언급하였지만, 채널수에서도 쿠키/세션/인증 기능을 활용할 수 있다. 장고 웹페이지에서의 쿠키/세션을 그대로 Consumer Instance에서도 활용할 수 있는 것이다. 예를 들어, Login View를 통해 인증된 유저가 웹 소캣으로 접속하면, 인증된 유저의 User Instance를 scope["user"]를 통해 조회할 수 있으며, 이 User Instance를 통해 인증여부(is_authenticated)를 알 수 있고, 웹 소캣 내에서 로그인/로그아웃 등의 기능도 구현할 수 있다.
속담으로 "거북이는 언제나 거기에 있어"라는 세계 거북의 신화가 있다. 가장 큰 거북이가 아래에 있고, 그 위로 거북이가 층층이 쌓여있다. 마찬가지로 채널스에서는 요청을 처리하는 첫 ASGI application 함수가 있고, 그 안에, 안에, 안에 여러 ASGI application들을 랩핑 하는 구조로 동작한다. 모든 ASGI application 함수는 scope, receive, send 인자를 가지고 있다. 결국, ProtocolTypeRouter, AuthMiddlewareStack, URLRouter, ChannelNameRouter 등도 모두 ASGI application 함수인 것이다.