Web Protocol

TedLim·2021년 7월 28일
0

HTTP는 기본적으로 Real-time 이 불가능하다. 클라이언트가 서버에 요청을 날리면 요청을 처리한 후 응답을 주고 이 요청은 끊어지기 때문이다. 서버는 클라이언트에게 통지해줄 수 있는 방법이 없고, 이는 매우 서버를 수동적인 (...) 존재로 만들어버린다.

HTTP 특성을 보완하는 통신 방식

Push Server

클라이언트 발 요청방식이 아니라, 서버가 클라이언트에게 공지전달 하듯이 통지해주는 방법.

Polling

클라이언트가 끊임없이 웹서버에게 새로운 내용이 있는지 물어보는 방식.

  • Long Polling : 서버에게 새로운 내용이 있는지 물어보고 서버는 새로운 내용이 생길때만 대답해준다.

Stream

서버의 응답 헤더 중 content-length 의 존재여부에 따라 연결을 유지하는 방식. Content-length 가 존재하지 않으면 클라이언트는 연결이 끊길때 까지 응답을 받아들인다.

Web Socket

실시간 양방향 통신을 위한 스펙. HTML5 에서 지원하면서 프로토콜화 되었음. 서버와 브라우저가 지속적으로 연결된 TCP 라인을 통해 실시간 통신을 하는 방식.

Django Channel

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)],
        },
    },
}

ASGI

wsgi 의 단점인 await, async를 가능하게 한 통신 방식. django-channels 는 기본적으로 이 통신 방식 위에서 이루어진다.

ProtocolTypeRouter

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
        )
    ),
})

Why need asynchronous?

채팅을 예로 들면, 채팅 유저는 우리의 기대와 상관없는 타이밍에 채팅을 날릴 수 있음. 그때마다 응답을 요청하고 받아올 수는 없으니, 일종의 우편함 역할을 하는 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

Background Worker

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

profile
인공지능과 금융생활에 관심이 많은 개발자입니다 ;)

0개의 댓글