HTTP는 기본적으로 Real-time 이 불가능하다. 클라이언트가 서버에 요청을 날리면 요청을 처리한 후 응답을 주고 이 요청은 끊어지기 때문이다. 서버는 클라이언트에게 통지해줄 수 있는 방법이 없고, 이는 매우 서버를 수동적인 (...) 존재로 만들어버린다.
클라이언트 발 요청방식이 아니라, 서버가 클라이언트에게 공지전달 하듯이 통지해주는 방법.
클라이언트가 끊임없이 웹서버에게 새로운 내용이 있는지 물어보는 방식.
서버의 응답 헤더 중 content-length 의 존재여부에 따라 연결을 유지하는 방식. Content-length 가 존재하지 않으면 클라이언트는 연결이 끊길때 까지 응답을 받아들인다.
실시간 양방향 통신을 위한 스펙. HTML5 에서 지원하면서 프로토콜화 되었음. 서버와 브라우저가 지속적으로 연결된 TCP 라인을 통해 실시간 통신을 하는 방식.
Channel 을 설치하면 Daphne가 자동으로 설치되는데 이를 브라우저와 통신하는 web server 로서 사용한다. HTTP 통신만을 따로 관리하는 Nginx 같은 서버를 두는 설계도 가능하다.
Channels 내부에서는 Redis 를 소켓간 메시지 저장 및 통신(?)을 위한 서버로서 사용한다.
# app/settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
wsgi 의 단점인 await, async를 가능하게 한 통신 방식. django-channels 는 기본적으로 이 통신 방식 위에서 이루어진다.
http 요청은 어떤 app에서 처리하도록 scope를 지정하고, websocket은 다른 어떤 app에서 처리할 수 있도록 scope를 달리합니다. Http를 정의하지 않는 경우 기본적인 django 방식대로 동작한다.
application = ProtocolTypeRouter({
# (http->django views is added by default)
'http' : basis_app,
'websocket': AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
),
})
채팅을 예로 들면, 채팅 유저는 우리의 기대와 상관없는 타이밍에 채팅을 날릴 수 있음. 그때마다 응답을 요청하고 받아올 수는 없으니, 일종의 우편함 역할을 하는 channel layer를 하나 두어서 모든 응답을 받도록 하여 통신하는 방식을 취함.
Channels 에서는 async한 thread를 django에 맞게 sync한 callable 로 바꿔주는 api를 지원한다.
# chat/consumers.py
class ChatConsumer(WebsocketConsumer):
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# room group 에게 메세지 send
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
그렇지만 위 코드는 여전히 async하게 직접 코딩했을때보다는 효율적이지 못하여 아래와 같이 변경할 수 있다.
# chat/consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
채팅방을 굉장히 잘 구현해놓은 예제: https://github.com/andrewgodwin/channels-examples/tree/master/multichat
Client 와 통신하는 것과는 별개로 내부의 worker 가 채널을 통하여 메시지를 전달하는 방법도 가능하다. 이 말인 즉슨, worker와 server 간의 채널을 뚫어준다는 것이며 client는 이를 따로 참조하지는 않는다.
참고 : https://channels.readthedocs.io/en/latest/topics/worker.html
핵심은 내부용 channel 을 따로 두어 "channel"의 scope를 따로 받는 routing 을 해주는 것이다.
# app/routing.py
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': TokenAuthMiddlewareStack(
URLRouter(
channelapp.routing.websocket_urlpatterns
)
),
'channel': ChannelNameRouter({
'background-tasks': BackgroundTaskConsumer,
})
})
이렇게만 하면 'background-tasks'라는 이름의 worker는 채널을 따로 타게 되는데 worker를 켤때 이 이름을 지정해주어야 제대로 된 routing 이 되는 것 같다(?)
python manage.py runworker background-tasks
worker를 view 로 호출할 때 쓸 수도 있다 : https://github.com/jayhale/channels-examples-bg-task