๐[Django] Django Channels๋?
Django Channels
Channels
๋ ์น ์์ผ, ์ฑํ
ํ๋กํ ์ฝ, IoT ํ๋กํ ์ฝ ๋ฑ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด Django๋ฅผ HTTP ์ด์์ผ๋ก ํ์ฅํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
โ์ ๊ณผ์ ๊ฐ ์ฑํ ์๋น์ค์ผ๊น?โ์ ๋ํ ํ ๊ฐ์ง ๋ต๋ณ์ด ๋ ์๋ ์๋ ๊ฐ๋ ์ธ ๋ฏ ํ๋ค. (IoT ํ๋กํ ์ฝ ์ฒ๋ฆฌ)
Channels๋ ASGI
๋ผ๋ ํ์ด์ฌ ์ฌ์์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ค.
Django๋ ์ฌ์ ํ ๊ธฐ์กด HTTP๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ๋ฉด, Channels๋ ๋๊ธฐ ๋ฐ ๋น๋๊ธฐ ์คํ์ผ๋ก ๋ค๋ฅธ ์ฐ๊ฒฐ์ ์ฒ๋ฆฌํ ์ ์๋ ์ ํ๊ถ์ ์ ๊ณตํ๋ค.
ASGI?
ASGI
๋ Application Server Gateway Interface์ ์ฝ์๋ก, ๋น๋๊ธฐ
๊ฐ ๊ฐ๋ฅํ ํ์ด์ฌ ์น ์๋ฒ, ํ๋ ์์ํฌ ๋ฐ ์์ฉ ํ๋ก๊ทธ๋จ ๊ฐ์ ํ์ค ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๊ธฐ ์ํ WSGI์ ํ์ ์ ํ์ด๋ค.
WSGI๊ฐ ์ฑ์ ๋ํ ํ์ค์ ์ ๊ณตํ๋ค๋ฉด, ASGI๋ ๋น๋๊ธฐ ๋ฐ ๋๊ธฐ ์ฑ ๋ชจ๋์ ๋ํ ํ์ค์ ์ ๊ณตํ๋ค.
ASGI๋ ๊ณง WSGI์ ๋ํ ํธํ์ฑ์ ๊ฐ์ง๋ฉด์, ๋น๋๊ธฐ์ ์ธ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๋ ์ธํฐํ์ด์ค์ด๋ค.
ASGI ์๋ฒ๋ก๋ ๋ณดํต uvicorn
์ ๋ง์ด ์ฌ์ฉํ๋ค๊ณ ํ๋ค.
์ธํ ์ ํ์ํ ๊ฒ๋ค์ ๋ญ์ง?
Django
- HTTP ํต์ ์ ์ํ ์น ํ๋ ์์ํฌ
Channels
- ์น ์์ผ ํต์ ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
channels-redis
- ์์ผ์ ์ด๊ณ ๋ซ์ ๋ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ด ๋ ์ ๊ทผํ ์ ์๋๋ก ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
Docker (๊ถ์ฅ)
Django Channels ๊ตฌ์ฑ ์์
Scope
scope
๋ ํ์ฌ ์์ฒญ์ ์ธ๋ถ ๋ด์ญ์ด ๋ด๊ธด dict์ด๋ค.
๋ชจ๋ ASGI
์ ํ๋ฆฌ์ผ์ด์
ํจ์๋ scope
, receive
, send
์ธ์๋ฅผ ๊ฐ๋๋ค.
Consumer
Django์์ HTTP ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ฃผ์ฒด๊ฐ View์ธ ๊ฒ์ฒ๋ผ, Channels์์๋ Consumer
๊ฐ HTTP, ์น ์์ผ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ฃผ์ฒด๊ฐ ๋๋ค.
Class
๋ง ์ง์ํ๋ค.- Consumer์์
self.scope
์ ์ฌ์ฉํด ํ์ฌ ์์ฒญ์ ๋ชจ๋ ๋ด์ญ์ ์กฐํํ ์ ์๋ค. - channel_layer๋ฅผ ํตํด ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ถ๋ถ๊น์ง ๋ชจ๋ ์ง์ํ๊ธฐ ๋๋ฌธ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋์ฑ ์ง์คํ ์ ์๋ค.
view์์๋ url ๋งคํ์ ํตํด ์์ฒญ์ ์ฒ๋ฆฌํ๋ฏ์ด, Channels์์๋ 3๊ฐ์ง ๊ธฐ์ค์ผ๋ก ํ์ฌ ์์ฒญ์ ์ฒ๋ฆฌํ Consumer Instance๋ฅผ ๊ฒฐ์ ํ ์ ์๋ค.
๊ธฐ์ค 1: ํ๋กํ ์ฝ ํ์
HTTP
์์ฒญ์ ์น ์์ผ
ํ๋กํ ์ฝ ์์ฒญ ํ์
์ ๊ตฌ๋ถํ๋ค.
ProtocolTypeRouter
๋ฅผ ํ์ฉํ๋ค.
๊ธฐ์ค 2: ์์ฒญ URL ๋ฌธ์์ด๋ก ๋ถ๊ธฐ
URLRouter
๋ฅผ ํ์ฉํด ๋๋ค์์ Consumer ํด๋์ค๊ฐ ๊ฐ๋ ๊ฐ๋ณ URL์ URLRouter์ ๋ฑ๋กํ๋ค.
๊ธฐ์ค 3: ์ฑ๋๋ช ์ ์ํ ๋ผ์ฐํ
channels worker
์์ ์ฌ์ฉํ๋ค.
Channels์์๋ ์ฟ ํค/์ธ์ ์ธ์ฆ์ด ๊ฐ๋ฅํ๋ค?
Django ์น ํ์ด์ง์์์ ์ฟ ํค/์ธ์ ์ ๊ทธ๋๋ก Consumer Instance์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
LoginView๋ฅผ ํตํด ์ธ์ฆ๋ ์ ์ ๊ฐ ์น ์์ผ์ผ๋ก ์ ์ํ๋ฉด, ์ธ์ฆ๋ ์ ์ ์ User Instance๋ฅผ scope[โuserโ]
๋ก ์กฐํ๊ฐ ๊ฐ๋ฅํ๋ค.
์ด๋ฅผ ํตํด ์ธ์ฆ ์ฌ๋ถ๋ฅผ ์ ์ ์๊ณ , ์น์์ผ ๋ด์์์ ๋ก๊ทธ์ธ/๋ก๊ทธ์์์ด ๊ฐ๋ฅํ๋ค.
๊ธฐ๋ณธ ์ธํ ์์
mysite/asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django_asgi_app = get_asgi_application()
import chat.routing
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
)
),
})
- ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์, ์น ์์ผ ์ฉ URL์ ๋งคํ
AllowedHostsOriginValidator
: ์น ์์ผ ์ด๊ฒฐ์ Origin ํค๋๋ฅผ ์ฌ์ฉํด ํ์ฉ๋ ํธ์คํธ์์ ์ค๋ ์์ฒญ๋ง ์ฒ๋ฆฌAuthMiddlewareStack
: ์น ์์ผ ์์ฒญ์ ๋ํด Django์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ
mysite/settings.py
...
ASGI_APPLICATION = "mysite.asgi.application"
# Channels Layer
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [('Redis ํธ์คํธ', 6379)],
},
},
}
chat/routing.py
from django.urls import re_path
from . import consumers
from . import custom_consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
# ์ฐ๊ฒฐ๋ฌ์ ๋ ์คํ๋๋ ํจ์
def connect(self):
# self.scope['url_route'] = /ws/localhost:8000/ws/chat/1
self.room_name = self.scope['url_route']['kwargs']['room_name']
# ์์๋ก ๊ทธ๋ฃน๋ช
์ ๋ง๋ ๊ณผ์ -> ์์ ๊ฐ๋ฅ
self.room_group_name = 'chat_%s' % self.room_name
# Join room group -> group ์ฐ๊ฒฐ๋ ์์ผ์ ๊ฐ์ ๊ทธ๋ฃน๋ช
์ ์ฐ๊ฒฐ
# -> ๊ฐ์ ๊ทธ๋ฃน๋ช
์ด๋ฉด ๊ฐ์ ๋ฉ์ธ์ง ๋ฐ์
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
# ์ฐ๊ฒฐ ํด์ ๋ ๋ ์คํ๋๋ ํจ์
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ผ ๋ ์คํ
def receive(self, text_data):
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': text_data
}
)
# ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ ๋ ์คํ
def chat_message(self, event):
# ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ์คํ๋๋ฏ๋ก ๋ด๋ถ์ message๋ฅผ ๊บผ๋ด์ผํจ
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': event['message']
}))
๋๊ธ๋จ๊ธฐ๊ธฐ