실시간 알림을 보내기 위해 websocket을 사용하려고 한다.
HTTP와 다르게, 웹소켓 프로토콜은 양방향 커뮤니케이션(bi-directional communication)을 허용한다. 서버와 클라이언트 사이에 소켓연결(클라이언트 — 서버연결이 계속 유지되어 있는상태)을 지원하는 것이다!!
python -m pip install -U channels
나는 notification이라는 앱에서 사용할것이다.
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
...
'channels',
'notifiaction',
)
ASGI_APPLICATION = "프로젝트이름.asgi.application"
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
import json
from channels.generic.websocket import WebsocketConsumer
class NotiConsumer(WebsocketConsumer):
# 웹소켓 연결, 해제
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
# json으로 채팅 메세지 받음.
text_data_json = json.loads(text_data)
message = text_data_json['message']
#json객체를 인코딩해서 보냄.
self.send(text_data=json.dumps({
'message':message
}))
NotiConsumer는 동기적인 웹소켓 컨슈머로, 모든 연결을 승낙하고(accept), 클라이언트로부터 메시지를 받고(receive), 동일한 클라이언트에게 이 메시지들을 다시 돌려줍니다(send). 아직은 이 컨슈머가 같은 채팅방의 다른 클라이언트에게 메시지를 전파해주지는 못한다.
[notification/routing.py]
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/<str:user_id>/', consumers.NotiConsumer.as_asgi()),
]
--------------------------------------------------------------------
[프로젝트/asgi.py]
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import notification.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'leanai.settings')
#application = get_asgi_application()
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket" : AuthMiddlewareStack(
URLRouter(
notification.routing.websocket_urlpatterns
)
),
})
여기까지하고 python manage.py runserver를 실행시키면
채팅 로그들이 뜨긴하지만 다른창에서 실행시키면 첫번째창 로그들이 나오지않는다. 이것을 가능하게 하려면, 동일한 ChatConsumer의 여러 인스턴스들이 서로 소통할 수 있게 해야한다. channels는 컨슈머 간에 이러한 종류의 통신이 가능하도록 추상화된 '채널 레이어'를 제공한다.
채널 레이어는 커뮤니케이션 시스템의 일종이다
채탱앱에서 여러 ChatConsumer가 통신을 주고받게하려면 ChatConsumer가 자신의 채널을 그룹에 등록해한다. 그러면 같은 방 모든 ChatConsumer한테 메시지를 보낼 수 있다.
channel layer 를 구현하기 위해서 백업저장소가 필요한데, 공식문서에서는 Redis를 사용하도록 한다.
$ docker run -p 6379:6379 -d redis:5
$ python3 -m pip install channels_redis
settings에 추가.
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
쉘로 들어가서 채널레이어가 redis랑 통신할 수 있는지 확인
import json
from channels.generic.websocket import WebsocketConsumer
class NotiConsumer(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
)
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
}))
유저가 메시지를 전송하면, 자바스크립트 함수는 이 메시지를 웹소켓을 통해 ChatConsumer로 전송한다. ChatConsumer는 메시지를 받아, 채팅방 이름에 해당하는 그룹으로 전파한다. 같은 그룹안에 있는 (즉, 같은 채팅방 안에 있는) 모든 ChatConsumer는 그룹으로부터 메시지를 전달받아, 웹소켓을 통해 자바스크립트로 이를 돌려주고, 따라서 채팅 로그에 이 메시지가 추가된다.