1 개요
django concumer. 장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.
1.1 유의
- 동기 코드와 비동기 코드는 구현시간이 다름. 장고의 내용을 반영하면 곧장 새로고침되어 브라우저에 반영되지만, 컨슈머의 반영은 실시간 쓰레드로 이루어지기 때문에 동기 코드와 반영시간이 다른데, 더 늦기도 하다.(서버가 켜져서 동기페이지는 잘 나오는데, 비동기는 반영이 안되어 에러가 난 것으로 오해하게 되는 경우가 잦다.)
2 형태
항목 | SyncConsumer | AsyncConsumer |
---|---|---|
목적 | 장고 ORM을 사용하는 경우.(동기식으로 쓰레드 안에서 실행)
기본적으로 이걸 사용하길 권장한다. |
비동기 코드를 수행하기 위해.(성능적인 면에서 우위)
비동기 라이브러리들을 사용하거나 성능을 향상시켜줄 수 있을 때 사용하길 권장한다. 데이터베이스 사용도 가능하다.(database_sync_to_async를 참고하자.) |
비동기에서 동기코드를 쓰면 다른 이벤트를 못받는건가?????
아마 쓰레드를 쓰지 않고 순차적으로 작동하는 듯한데, 이게 왜 문제가 되는지 모르겠다;;; | ||
DB를 쓰기 위한 방법이 없진 않다.
굳이 비동기방식에서 DB를 사용하고자 한다면 database_sync_to_async를 사용하면 된다. |
2.1 Generic consumer
뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다.
3 컨슈머 각 항목에 대하여
3.1 Consumer.py
컨슈머는 클래스로 구현되어 connect, disconnect 등으로 구성된다.
항목 | 설명 | 예시 |
---|---|---|
컨슈머 정의 | 클래스로 구현한다. | from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync # 필요에 따라.
class 컨슈머이름(WebsocketConsumer):
from channels.generic.websocket import AsyncWebsocketConsumer
class 컨슈머이름(AsyncWebsocketConsumer):
|
3.1.1 connect
항목 | 설명 | 예시 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
사전 설정 | 각종 변수들의 조작을 먼저 한다.
스코프로 받아서 변수들을 조작하는데, 그 구조는 다음과 같다.
|
self.group_name = self.scope["url_route"]["kwargs"]["room_name"] | ||||||||
그룹 참여 | 그룹을 지정하여 참여시킨다. | async_to_sync(self.channel_layer.group_add)(
self.group_name, # 위에서 설정하여 넣어준다.
self.channel_name # 자동으로 고유하게 지정된다.
)
await self.channel_layer.group_add(self.group_name, self.channel_name)
| ||||||||
연결 | 위 설정을 토대로 연결한다.
연결 이후 필요한 명령을 넣기도 한다. 연결과 동시에 다음과 같이 신호를 보내기도 한다.notifications = get_notification()
if notifications:
async_to_sync(self.channel_layer.group_send)(
"center_name", {
"type": "notify",
"data": notifications
}
)
|
self.accept()
await self.accept()
| ||||||||
서브프로토콜 | 서브프로토콜을 지정해 연결할 수도 있다. | self.accept('subprotocol")
# self.scope['subprotocols'] 을 통해 관련 정보를 알 수 있다. |
3.1.2 disconnect
항목 | 설명 | 예시 |
---|---|---|
정의 | 연결을 끊기만 할 테니, 기본 설정은 간단하다.
적당히 끊어질 때의 명령을 넣기도 한다. |
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.group_name, self.channel_name)
|
종료에러코드 | receive에서 특정 과정을 거쳐 self.close(code=1234) 등으로 커스텀에러를 낼 수도 있다. | disconnect에서 코드를 담아 상황에 맞게 작동, 반환한다. |
3.1.3 receive
타인으로부터 메시지를 받는 게 아니라, 자바스크립트로부터 받는 메시지와 관련된 함수이다.(웹소켓에서 메시지를 받을 때.)
항목 | 설명 | 예시 | ||||||
---|---|---|---|---|---|---|---|---|
정의 | 본인의 자바스크립트에서 웹소켓으로 메시지를 보낸 경우.
자바스크립트에서 데이터가 넘어올 땐 json으로 변환되어 오기 때문에 우측과 같이 구성해야 한다.
type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.
|
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# 나 뿐 아니라 다른 사람들도 보게끔.
self.send(text_data=json.dumps({
'message': message
}))
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
# 나 뿐만 아니라 다른사람들도 보도록.
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat_message",
"message": message}
)
|
3.1.4 이벤트
다른 사용자들로부터 메시지를 받는 등의 이벤트가 나타났을 때.
항목 | 설명 | 예시 |
---|---|---|
정의 | 함수의 이름은 메시지를 보낼 때의 type와 동일하게 짓는다.
type에 따라 다른 함수가 실행된다. (event가 들어오면 자동으로 실행되는 듯하다.) 웹소켓(자바스크립트)으로 보낼 땐 json으로 변환하여 보내주어야 한다. |
def chat_message(self, event): # 변수명으로 event 대신 text를 사용하기도 한다.
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
async def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))
|
보내는 함수의 차이. |
3.1.5 자바스크립트에서
항목 | 설명 | 예시 |
---|---|---|
웹소켓 생성 | 스크립트의 초반에 우측과 같이 주소를 지정하여 소켓을 생성한다.
roomname과 같은 변수는 위에서 받아 넘겨주어야 한다.
이벤트의 활용은 아래 예시를 참고하자. |
const chatSocket = new WebSocket(
'ws://' + window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);
|
message | 웹소켓이 데이터를 수신할 때 발생 | chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
};
|
close | chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
| |
메시지 보내기 | 만든 소켓으로 메시지 보낼 때.
JavaScript 값이나 객체를 JSON 문자열로 변환해야만 한다. |
chatSocket.send(JSON.stringify({
'message': message
}));
|
4 Scope
뷰에 request가 있다면 컨슈머엔 scope가 있다.
request와 동일하게 사용자가 입력한 값, 기타정보들을 사전형태로 담고 있다.(담은 키도 request와 거의 동일하다고 보면 된다.)
4.1 속성
속성 | 설명 | 활용 |
---|---|---|
method | GET인가 POST인가의 정보. | if self.scope['method'] == POST: |
user | 요청자의 user계정이 담긴다. | self.scope['user'] |