2์ฐจ ํ๋ก์ ํธ์์ ์ํธํํ ๊ฑฐ๋์ ํด๋ก ์ ํ๊ฒ ๋์ด, ๊ณ์์ ์ผ๋ก ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ getํ ์ ์๋ api๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ฐพ๊ณ ์๋ค. ๊ธฐ์ ์กฐ์ฌ ๊ณผ์ ์์ channel, websocket์ ๋ํ ๋ด์ฉ์ ์ ๋ฆฌํ๋ ค๊ณ ํ๋ค.
ํด๋น ๊ฒ์๋ฌผ์ ์ฅ๊ณ ๊ณต์ ๋ฌธ์, ์ฅ๊ณ ์ฑ๋ ํํ ๋ฆฌ์ผ์ ๋ฒ์ญํ๋ค.
์ฑ๋์ django๊ฐ ๋น๋๊ธฐ์ asynchronous์ผ๋ก ์ฝ๋๋ฅผ ์งค ์ ์๋๋ก ๋ฐ๊ฟ์ฃผ๊ณ , django์ ๋๊ธฐ์ ์ธ ํต์ฌ์synchronous core ํตํด ํ๋ก์ ํธ๊ฐ HTTP๋ฟ ์๋๋ผ, ์ฅ๊ธฐ๊ฐ ์ฐ๊ฒฐ์ ํ์๋ก ํ๋ ํ๋กํ ์ฝ Websockets, MQTT, chatbots, amateur radio ๋ฑ์ ํธ๋ค๋งํ ์ ์๋๋ก ํด์ค๋ค.
django์ ๋๊ธฐ์ ์ด๊ณ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์์ฑ์ ์ ์งํ๋ฉด์ ์ฝ๋ ์คํ์ผ์ ๊ณ ๋ฅผ ์ ์๋๋ก ํด์ฃผ์ด ์ ์ฉํ๋ค. django view style์ ๋๊ธฐ์ ํน์ง์ด๋, ์์ ํ ๋น๋๊ธฐ์ ์ด๊ฑฐ๋, ๋ ๋ค ์๊ฑฐ๋๋ฅผ ํ ์ ์๋ค.
๋ฌด์๋ณด๋ค๋ django์ auth, session system๊ณผ ํตํฉ๋์ด HTTP-only ํ๋ก์ ํธ๋ฅผ ๋ค๋ฅธ ํ๋กํ ์ฝ๋ก ํ์ฅ์์ผ์ฃผ๋ ๊ฒ์ด ์ฝ๋ค.
channel ์ค์น ๋ฐฉ๋ฒ
๋์ปค ์ค์น ๋งํฌ
๋์ปค๋?
๋์ปค(Docker)๋ ๋ฆฌ๋ ์ค์ ์์ฉ ํ๋ก๊ทธ๋จ๋ค์ ์ํํธ์จ์ด ์ปจํ ์ด๋ ์์ ๋ฐฐ์น์ํค๋ ์ผ์ ์๋ํํ๋ ์คํ ์์ค ํ๋ก์ ํธ์ด๋ค. ๋์ปค ์น ํ์ด์ง์ ๊ธฐ๋ฅ์ ์ธ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค:"๋์ปค ์ปจํ ์ด๋๋ ์ํํธ์จ์ด๋ฅผ ์ํํธ์จ์ด์ ์คํ์ ํ์ํ ๋ชจ๋ ๊ฒ์ ํฌํจํ๋ ์์ ํ ํ์ผ ์์คํ ์์ ๊ฐ์ธ๋ ๊ฒ์ด๋ค. ์ฌ๊ธฐ์๋ ์ฝ๋, ๋ฐํ์, ์์คํ ๋๊ตฌ, ์์คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฑ ์๋ฒ์ ์ค์น๋๋ ๋ฌด์์ด๋ ์์ฐ๋ฅธ๋ค. ์ด๋ ์คํ ์ค์ธ ํ๊ฒฝ์ ๊ด๊ณ ์์ด ์ธ์ ๋ ๋์ผํ๊ฒ ์คํ๋ ๊ฒ์ ๋ณด์ฆํ๋ค."
"Turtles all the way down"์ ๋ฌดํ ํ๊ท์ ๋ฌธ์ ์ ๋ํ ํํ์ด๋ค. ๋ฌดํ ํ๊ท๋ P1์ ์ง์ค์ ๋ํ ์ ์ ๋ก P2๊ฐ ํ์ํ๊ณ , P2์ ๋ํ ์ ์ ๋ก P3์ด ํ์ํ ๋ฌธ์ ๊ฐ ๋ฌดํ๋๋ก ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
์ฅ๊ณ ๊ณต์ ๋ฌธ์์์ ์ฑ๋์ 'turtles all the way down' ๋ฐฉ์์ผ๋ก ์ด์๋๋ค๊ณ ๋์์๋ค. ๋ฌดํ ํ๊ท ๋ฌธ์ ์ ๋์ฒด ๋ญ ์๊ด์ธ์ง stackoverflow์ ์ฐพ์๋ณด๋, pureํ ๊ฐ์ฒด์งํฅ ์ธ์ด ํน์ self-hosting ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ๊ฐ๋ฆฌํค๋ ๋ง์ด๋ผ๊ณ ํ๋๋ฐ, ์ถ๊ฐ ์ค๋ช ์ด ์์ด์ ์ ํํ๊ฒ ์ดํดํ๊ธด ์ด๋ ต๋ค ๐ค
์ฑ๋์ basic consumer, url ๋ผ์ฐํ , protocol detection, ๋ฑ full application์ ๋ง๋ค๊ธฐ ์ํ ๋๊ตฌ๋ฅผ ํ ๋ฐ ๋ชจ์์ค๋ค.
์ฌ์ค ์ ํต์ ์ธ django view๋ channels๊ฐ ์๊ณ ์ฌ์ฉํ ์ ์๊ธด ํ๋ค. channels.httl.AsgiHandler๋ผ๋ ASGI application ์ ๋ชจ์ฌ์๋ค๊ณ ํ๋ค. ํ์ง๋ง ์ฑ๋์ ํตํด ์ปค์คํ ํ๋ HTTP long-polling handling ์ฆ ์น์์ผ receiver๋ฅผ ์จ์ ๋ด ์กด์ฌํ๋ ์ฝ๋์ ํจ๊ป ๋ ์ ์๋ค.
Asynchronous Server Gateway Interface. WSGI(Web Server Gateway Interface)๋ ์น์๋ฒ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธํฐํ์ด์ค๋ฅผ ์ํ ํ์ด์ฌ ํ๋ ์์ํฌ์ธ๋ฐ, ์๊ฑฐ์ ๋น๋๊ธฐ์ ๋ฒ์ ์ด๋ค. ์ฑ๋์ด ๋ง๋ค์ด์ง๋ ๋น๋๊ธฐ์ ์๋ฒ spec์ ์ด๋ฆ์ด๊ณ , WSGI์ ๋์ผํ๊ฒ channels์ daphne์ ๊ฐํ์๊ธฐ๋ณด๋ค ๋ค๋ฅธ ์๋ฒ๋ค๊ณผ ํ๋ ์์ํฌ ์ค์์ ๋ด๊ฐ ์ ํํ ์ ์๋๋ก ๋์์ธ๋์ด์๋ค.
Daphne
django channel์ ๋ณด๊ฐํ๊ธฐ ์ํด ๊ฐ๋ฐ๋ HTTP, HTTP2 ๊ทธ๋ฆฌ๊ณ ์น์์ผ ํ๋กํ ์ฝ ์๋ฒ(for ASGI, ASGI-HTT)์ด๋ค. ์๋ํ๋ ํ๋กํ ์ฝ ์ค์ฌ๋ฅผ ์ง์ํ๋ค. HTTP endpoint์ ์น์์ผ endpoint๋ฅผ ๊ฒฐ์ ํ ๋ URL์ prefixingํ์ง ์์๋ ๋๋ค.
๋ณดํต django๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ์ํตํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด HTTP๋ฅผ ์ฌ์ฉํ๋ค.
HTTP์์๋ ํธ๋ฆญ์ ์ฌ์ฉํด ์ค์๊ฐ์ฒ๋ผ ๋ณด์ด๋๋ก ํต์ ํ ์ ์๊ธด ํ๋ค. ํด๋ผ์ด์ธํธ์์ ์๋ฒ๋ก ๊ณ์ ์์ฒญ์ ๋ณด๋ด ์ด๋ฒคํธ๋ฅผ ๋ฐ๊ฑฐ๋(polling), ํด๋ผ์ด์ธํธ์์ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๊ณ ํ์ํ ์ด๋ฒคํธ๊ฐ ์์ ๋์๋ง response๋ฅผ ๋ณด๋ธ๋ค๋ ์ง(streaming)ํ๋ ๋ฐฉ์์ด๋ค.
HTTP์ ๋ค๋ฅด๊ฒ, ์น์์ผ ํ๋กํ ์ฝ์ ์๋ฐฉํฅ ์ปค๋ฎค๋์ผ์ด์
(bi-directional communication)์ ํ์ฉ
ํ๋ค. ์๋ฒ์ ํด๋ผ์ด์ธํธ ์ฌ์ด์ ์์ผ์ฐ๊ฒฐ(ํด๋ผ์ด์ธํธ โ ์๋ฒ์ฐ๊ฒฐ์ด ๊ณ์ ์ ์ง๋์ด ์๋์ํ)์ ์ง์ํ๋ ๊ฒ์ด๋ค.push ๋ฐฉ์
์ด๋ผ๊ณ ํ๋๋ฐ, ์๋ฒ๋ ์ ์ ์ ์์ฒญ ์์ด ํด๋ผ์ด์ธํธ์ ๋ฐ์ดํฐ๋ฅผ ์ค ์ ์๋ค๋ ๋ป์ด๋ค. HTTP๋ก๋ request์ ๋ณด๋ธ ํด๋ผ์ด์ธํธ๋ง response๋ฅผ ๋ฐ์ ์ ์๋ ๋ฐ๋ฉด, ์น์์ผ์ผ๋ก๋ ๋ค์ค ํด๋ผ์ด์ธํธ์ ๋์์ ์ํตํ ์ ์๋ค. html5 ๋ฒ์ ์ด ๋์ฌ ๋ ์น์์ผ๋ ํจ๊ป ๋ฑ์ฅํ๋ค๊ณ ํ๋ค.
ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ๊ธฐ๋ณธ ์ฐ๊ฒฐ์ ํธ๋ค๋ง ํ ์ฒซ ๋ฒ์งธ consumer ์์๋ฅผ ๋ง๋ค์ด๋ณด์.
consumer๋ ์น์์ผ ์ฐ๊ฒฐ์ ๋ฐ๋๋ค. ํํ ๋ฆฌ์ผ์ consumer๊ฐ django view์ counterpart๋ผ๊ณ ๋์์๋๋ฐ, channel : consumer = django : view
๋ก ๋ณด๋ฉด ๋๋ค.
http request๋ฅผ ๋ฐ์ ๋, django๋ view function์ ๋ณด๊ธฐlookup ์ํด root URLconf๋ฅผ ์ฐพ๊ณ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด view function์ ํธ์ถํ๋ค. ๋น์ทํ๊ฒ, ์ฑ๋์ web socket์ ์ฐ๊ฒฐ์ ๋ฐ๊ณ , consumer๋ฅผ ๋ณด๊ธฐ ์ํด root routing ์ค์ ์ ์ฐพ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๊ฒฐ์ผ๋ก๋ถํฐ ์จ ์ด๋ฒคํธ๋ค์ ํธ๋ค๋งํ๊ธฐ ์ํด consumer์ ์ฌ๋ฌ ํจ์๋ฅผ ํธ์ถํ๋ค.
์น ์์ผ ์ฐ๊ฒฐ์ ๋ฐ์์ ์น์์ผ์ผ๋ก๋ถํฐ ์ด๋ค ๋ฉ์์ง๋ ๋ฐ๊ณ , ๊ฐ์ ์น์์ผ์ผ๋ก echo back ํ๋ basic consumer code๋ ๋ค์๊ณผ ๊ฐ๋ค.
import json
from channels.generic.websocket import WebsocketConsumer
#์น์์ผ class instance๋ฅผ ๋ง๋ค์ด์
class ChatConsumer(WebsocketConsumer):
#์น์์ผ์ ์ฐ๊ฒฐ, ํน์ ์ฐ๊ฒฐ ํด์ ํด์
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
#josn์ผ๋ก ์ฑํ
๋ฉ์์ง๋ฅผ ๋ฐ์์
text_data_json = json.loads(text_data)
message = text_data_json['message']
#json ๊ฐ์ฒด๋ฅผ ์ธ์ฝ๋ฉ ํด์ ๋ณด๋ด์
self.send(text_data=json.dumps({
'message': message
}))
Django url ์ค์ ๊ณผ ์ ์ฌํ๊ฒ ์๋ํ๋ ๊ฒ์ด routes๋ค. urlpatterns ๋์ channel_routing, url() ๋์ route()์ ์ ์ํ๊ฒ ๋๋ค. ์ด ๋ consumer functions๋ฅผ ์น์์ผ์ ์ฐ๊ฒฐํ ์ ์ ์ฃผ๋ชฉํ์!
์ฑ๋์ด ๊ฐ๋ฐ ์๋ฒ์ ์ฐ๊ฒฐ๋๋ฉด, ProtocolTypeRouter
๊ฐ ์ฐ๊ฒฐ์ด ์ด๋ค ํ์
์ธ์ง ์กฐ์ฌํ๋ค. ๋ง์ฝ socket ์ฐ๊ฒฐ(ws:// or wss://)์ด๋ฉด, ์ฐ๊ฒฐ์ AuthMiddlewareStack๋ก ๋ณด๋ธ๋ค. AuthMiddlewareStack
์ ์ฐ๊ฒฐ ๋ฒ์๋ฅผ ์ต๊ทผ์ ๊ฒ์ฆ๋ ์ ์ ๋ก ํ์ฅํ๊ณ , ๋ค์ ์ฐ๊ฒฐ์ URLRouter๋ก ๋ณด๋ธ๋ค. URLRouter๋ urlpatterns๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํน์ consumer์๊ฒ ์ฐ๊ฒฐ๋ http path๋ฅผ ์กฐ์ฌ ํ๋ค.
# chatapp/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer),
]
# rootapp/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import chat.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
์ผ๋ฐ์ ์ธ HTTP ์ฐ๊ฒฐ๊ณผ ์น์์ผ ์ฐ๊ฒฐ์ ๊ตฌ๋ถํ๊ธฐ ์ํด, path prefix์ /ws/์ ๊ฐ์ ๊ฒ์ ๋ฃ์ด์ฃผ๋๊ฒ ์ข๋ค. ์๋ํ๋ฉด production ํ๊ฒฝ์์ ์ฑ๋์ ๋์ ๋ ํน์ ์ค์ ์ ๋ ์ฝ๊ฒ ๋ง๋ค์ด์ฃผ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฑ๋ ๋ ์ด์ด๋ ์ปค๋ฎค๋์ผ์ด์ ์์คํ ์ ์ผ์ข ์ด๋ค. ์ฌ์ฉ์๋ค์ด ์๋ก ์๊ธฐํ๊ธฐ ์ํด ์ฌ๋ฌ consumer instances๋ฅผ ๋ง๋ค๊ฒ ํด์ค๋ค. ๋ชจ๋ consumer instance๋ ์๋์ผ๋ก ์์ฑ๋ unique channel name์ด ์๊ณ , ์ฑ๋ ๋ ์ด์ด๋ฅผ ํตํด ํต์ ํ ์ ์๋ค.
๊ฐ์ ๋ฐฉ์ ์๋ ์ฌ๋๋ค์ด ์๋ก ์ํตํ๊ฒ ๋ง๋ค๊ธฐ ์ํด์๋, consumers.py์์ ๋ง๋ ChatConsumer class์ instance๋ฅผ ์ฌ๋ฌ ๊ฐ ๋ง๋ค์ด์ผ ํ๋ค. ๊ทธ๋์ ๋ชจ๋ ChatConsumer๊ฐ ์ฑํ ๋ฐฉ์ ์ด๋ฆ์ ๊ธฐ๋ฐ์ผ๋ก ํ ๊ทธ๋ฃน์ ์ฑ๋์ ์ถ๊ฐํ๋๋ก ๋ง๋ค๊ณ , ๊ฐ์ ๋ฐฉ์ ์๋ ๋ค๋ฅธ ChatConsumer๊ฐ ๋ฉ์์ง๋ฅผ ์ ์กํ๋๋ก ํ๊ฐํด์ผ ํ๋ค.
ํํ ๋ฆฌ์ผ์์๋ backing store๋ก์ Redis๋ฅผ ์ฑ๋ ๋ ์ด์ด๋ก ์ฌ์ฉํ๋ค. port 6379์์ Redis server๋ฅผ runํ๊ณ , chennel_redis๋ฅผ ์ค์นํด์ผ ํ๋ค.
$ docker run -p 6379:6379 -d redis:5
$ python3 -m pip install channels_redis
root app settings.py ์ ์ฑ๋ ๋ ์ด์ด๋ฅผ ์ค์ ํ๋ค.
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
์ฑ๋ ๋ ์ด์ด๊ฐ ์๊ฒผ์ผ๋ฉด, consumers.py์์ ์ฝ๋๋ฅผ ๋ค์ ์์ฑํ๋ค. ์๋ ์ฝ๋์ ์ค๋ช ์ ๋ณด์.
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room 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
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
์ฌ๊ธฐ๊น์ง๊ฐ ํํ ๋ฆฌ์ผ์ 1,2์ ๋ํ ๋ด์ฉ์ด๋ค.
ํํ ๋ฆฌ์ผ 3์์ consumer ์ฝ๋๋ฅผ ์์ ๋น๋๊ธฐ์ ์ผ๋ก ๋ง๋๋ ๊ฒ์ด ์๋๋ฐ, ํน์ ํจ์์ inheritํ๋ ํด๋์ค๋ฅผ async๋ก ๋ฐ๊พธ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋งํฌ๋ง ์ฒจ๋ถํ๋ค.
ํํ ๋ฆฌ์ผ 4์์๋ ์ ๋ ๋์์ ํตํด ์๋ ๋ด์ฉ์ ํ์ธํ๋ test code๋ฅผ ๋ง๋ ๋ค.
chating app์ ๋ง๋๋ tutorial๋ก ์น์์ผ์ด ๋ฌด์์ธ์ง ์ดํด๋ฅผ ํ๋๋ฐ, ์ด์ ์น์์ผ์ ๊ฐ์ง๊ณ ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ์๋์ง๊ฐ ๋ฌธ์ ๋ค.
์ผ๋จ ๊ฐ๋ ์ ์์์ผ๋ ์ด๋ป๊ฒ ํ์ฉํด์ผ ํ๋์ง์ ๋ํด ๊ณต๋ถํด์ผ๊ฒ ๋ค ๐ค
Websocket Handshake๋ ์ฌ๋ฏธ์๋ ์ ์ด ์๋ค. ๋ฐ๋ก Upgrade ๋ผ๋ ํค๋์ธ๋ฐ. 'websocket' ์ผ๋ก ์ง์ ๋์ด ์๋ค.
๋ถ๋ช
ํ ์ง๊ธ์ GET ํ์
์ผ๋ก ๋ณด๋ด๊ณ ์์ง๋ง, ์๋ฒ์์ ์ค์์นญ ์์ผ์ค๋ค.
Server -> Client
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
'101 Switching Protocols' ๊ฐ response๋ก ์ค๋ฉด Websocket ์ฐ๊ฒฐ์ด ๋ ๊ฒ์ด๋ค. ๊ทธ ์ดํ๋ก๋ Socket Event๋ฅผ ๋ฑ๋กํ์ฌ Emit (๋ฉ์์ง ์ ์ก) , socket.on (๋ฉ์์ง ๋ฆฌ์ค๋) ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. ํด๋ผ์ด์ธํธ - ์๋ฒ ์๋ฐฉํฅ ํต์ ์ด๋ฏ๋ก ์๋ฒ์์ ์ํ๋ ์์ ์ ํด๋ผ์ด์ธํธ๋ก Emit (๋ฉ์์ง ์ ์ก) ์ ๋ ๋ฆฌ๋ฉด ๋ฐ๋ก ํด๋ผ์ด์ธํธ๊ฐ ์๋ตํ๋ค.
์ถ์ฒ: https://jjshun.tistory.com/46 [์ํ์ ๊ณต๋ถ๋ฐฉ]
์ง์ง์ง