"장고 컨슈머"의 두 판 사이의 차이

Pywiki
둘러보기로 가기 검색하러 가기
2번째 줄: 2번째 줄:
 
장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.
 
장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.
  
[실시간 채팅 말미의 내용을 이곳으로 가져와 정리하면 좋겠다.]
+
=== 유의 ===
 +
 
 +
* 에러가 난 것으로 오해 : 장고의 내용을 반영하면 곧장 새로고침되어 브라우저에 반영되지만, 컨슈머의 반영은 실시간 쓰레드로 이루어지기 때문에 더 늦기도 하다.(서버가 켜져서 동기페이지는 잘 나오는데, 비동기는 구현이 안되어 에러가 난 것으로 오해하게 되는 경우가 잦다. 서버에 반영되는 속도는 더 느린 느낌.)
  
 
== 형태 ==
 
== 형태 ==
34번째 줄: 36번째 줄:
 
뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다.
 
뷰에서 함수형 뷰와 제너릭 뷰가 있었듯, 컨슈머도 여러 작업을 알아서 처리해주는 제네릭 컨슈머가 있다. 일반 컨슈머와의 차이는 알아서 접속종료 처리 등을 해준다는 것. 딱히 일반 컨슈머를 쓰는 이유는 찾지 못했다.
  
 +
= 컨슈머 각 항목에 대하여 =
 +
 +
==Consumer.py==
 +
컨슈머는 클래스로 구현되어 connect, disconnect 등으로 구성된다.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|컨슈머 정의
 +
|클래스로 구현한다.
 +
|<syntaxhighlight lang="python">
 +
from channels.generic.websocket import WebsocketConsumer
 +
from asgiref.sync import async_to_sync  # 필요에 따라.
 +
 +
class 컨슈머이름(WebsocketConsumer):
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
from channels.generic.websocket import AsyncWebsocketConsumer
 +
 +
class 컨슈머이름(AsyncWebsocketConsumer):
 +
</syntaxhighlight>
 +
|}
 +
===connect===
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|사전 설정
 +
|각종 변수들의 조작을 먼저 한다.
 +
스코프로 받아서 변수들을 조작하는데, 그 구조는 다음과 같다.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
|-
 +
|self.scope["url_route"]
 +
|args와 kargs를 키로 갖는 사전.
 +
|-
 +
|self.scope["url_route"]["kargs"]
 +
|routing에서 정규표현식으로 사용한 변수.
 +
|-
 +
|self.scope["url_route"]["kargs"]["변수키"]
 +
|변수의 값을 얻어온다.
 +
|}
 +
|self.group_name = self.scope["url_route"]["kwargs"]["room_name"]
 +
|-
 +
|그룹 참여
 +
|그룹을 지정하여 참여시킨다.
 +
|<syntaxhighlight lang="python">
 +
async_to_sync(self.channel_layer.group_add)(
 +
            self.group_name,  # 위에서 설정하여 넣어준다.
 +
            self.channel_name  # 자동으로 고유하게 지정된다.
 +
        )
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
await self.channel_layer.group_add(self.group_name, self.channel_name)
 +
</syntaxhighlight>
 +
|-
 +
|연결
 +
|위 설정을 토대로 연결한다.
 +
연결 이후 필요한 명령을 넣기도 한다.
 +
 +
연결과 동시에 다음과 같이 신호를 보내기도 한다.<syntaxhighlight lang="python">
 +
notifications = get_notification()
 +
        if notifications:
 +
            async_to_sync(self.channel_layer.group_send)(
 +
                "center_name", {
 +
                    "type": "notify",
 +
                    "data": notifications
 +
                }
 +
            )
 +
</syntaxhighlight>
 +
|<syntaxhighlight lang="python">
 +
self.accept()
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
await self.accept()
 +
</syntaxhighlight>
 +
|-
 +
|서브프로토콜
 +
|서브프로토콜을 지정해 연결할 수도 있다.
 +
|self.accept('subprotocol")
 +
<nowiki>#</nowiki> self.scope['subprotocols'] 을 통해 관련 정보를 알 수 있다.
 +
|}
 +
===disconnect===
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|연결을 끊기만 할 테니, 기본 설정은 간단하다.
 +
적당히 끊어질 때의 명령을 넣기도 한다.
 +
|<syntaxhighlight lang="python">
 +
def disconnect(self, close_code):
 +
    async_to_sync(self.channel_layer.group_discard)(
 +
        self.group_name,
 +
        self.channel_name
 +
    )
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
async def disconnect(self, close_code):
 +
    await self.channel_layer.group_discard(self.group_name, self.channel_name)
 +
</syntaxhighlight>
 +
|-
 +
|종료에러코드
 +
|receive에서 특정 과정을 거쳐 self.close(code=1234) 등으로 커스텀에러를 낼 수도 있다.
 +
|disconnect에서 코드를 담아 상황에 맞게 작동, 반환한다.
 +
|}
 +
===receive===
 +
타인으로부터 메시지를 받는 게 아니라, 자바스크립트로부터 받는 메시지와 관련된 함수이다.(웹소켓에서 메시지를 받을 때.)
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|본인의 자바스크립트에서 웹소켓으로 메시지를 보낸 경우.
 +
자바스크립트에서 데이터가 넘어올 땐 json으로 변환되어 오기 때문에 우측과 같이 구성해야 한다.
 +
 +
 +
메시지를 보낼 때 type는 받는 함수를 지정할 때 쓰인다.(없어도 되긴 함.)
 +
 +
type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.
 +
{| class="wikitable"
 +
!데이터 전송 함수
 +
!설명
 +
|-
 +
|self.send()
 +
|웹소켓(자바스크립트)으로 데이터를 보내 창에 띄운다.
 +
|-
 +
|
 +
*async_to_sync(동기식)
 +
*await self.channel_layer.group_send(비동기식)
 +
|다른 유저들에게 보낼 때 사용한다.
 +
|}
 +
|<syntaxhighlight lang="python">
 +
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
 +
        }))
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
    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}
 +
        )
 +
</syntaxhighlight>
 +
|}
 +
===이벤트===
 +
다른 사용자들로부터 메시지를 받는 등의 이벤트가 나타났을 때.
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|정의
 +
|함수의 이름은 메시지를 보낼 때의 type와 동일하게 짓는다.
 +
type에 따라 다른 함수가 실행된다.
 +
 +
(event가 들어오면 자동으로 실행되는 듯하다.)
 +
 +
웹소켓(자바스크립트)으로 보낼 땐 json으로 변환하여 보내주어야 한다.
 +
|<syntaxhighlight lang="python">
 +
def chat_message(self, event):  # 변수명으로 event 대신 text를 사용하기도 한다.
 +
        message = event['message']
 +
 +
        # Send message to WebSocket
 +
        self.send(text_data=json.dumps({
 +
            'message': message
 +
        }))
 +
</syntaxhighlight>비동기식으로 구성한 경우<syntaxhighlight lang="python">
 +
async def chat_message(self, event):
 +
    message = event["message"]
 +
 +
    # Send message to WebSocket
 +
    await self.send(text_data=json.dumps({"message": message}))
 +
</syntaxhighlight>
 +
|-
 +
|
 +
|보내는 함수의 차이.
 +
|
 +
|}
 +
===자바스크립트에서===
 +
{| class="wikitable"
 +
!항목
 +
!설명
 +
!예시
 +
|-
 +
|웹소켓 생성
 +
|스크립트의 초반에 우측과 같이 주소를 지정하여 소켓을 생성한다.
 +
roomname과 같은 변수는 위에서 받아 넘겨주어야 한다.
 +
 +
 +
웹소켓이 만들어지면 아래 4개의 이벤트 사용이 가능해진다.
 +
*open – 커넥션이 제대로 만들어졌을 때 발생함
 +
*message – 데이터를 수신하였을 때 발생함
 +
*error – 에러가 생겼을 때 발생함
 +
*close – 커넥션이 종료되었을 때 발생
 +
이벤트의 활용은 아래 예시를 참고하자.
 +
|<syntaxhighlight lang="python">
 +
const chatSocket = new WebSocket(
 +
    'ws://' + window.location.host
 +
    + '/ws/chat/'
 +
    + roomName
 +
    + '/'
 +
);
 +
</syntaxhighlight>
 +
|-
 +
|message
 +
|웹소켓이 데이터를 수신할 때 발생
 +
|<syntaxhighlight lang="python">
 +
chatSocket.onmessage = function(e) {
 +
            const data = JSON.parse(e.data);
 +
            document.querySelector('#chat-log').value += (data.message + '\n');
 +
        };
 +
</syntaxhighlight>
 +
|-
 +
|close
 +
|
 +
|<syntaxhighlight lang="python">
 +
chatSocket.onclose = function(e) {
 +
            console.error('Chat socket closed unexpectedly');
 +
        };
 +
</syntaxhighlight>
 +
|-
 +
|메시지 보내기
 +
|만든 소켓으로 메시지 보낼 때.
 +
JavaScript 값이나 객체를 JSON 문자열로 변환해야만 한다.
 +
|<syntaxhighlight lang="python">
 +
chatSocket.send(JSON.stringify({
 +
                'message': message
 +
            }));
 +
</syntaxhighlight>
 +
|}
 
= Scope =
 
= Scope =
 
뷰에 request가 있다면 컨슈머엔 scope가 있다.
 
뷰에 request가 있다면 컨슈머엔 scope가 있다.

2022년 11월 11일 (금) 23:57 판

1 개요

장고 채널에서 사용하는 컨슈머에 대한 설명을 위한 문서.

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.scope["url_route"] args와 kargs를 키로 갖는 사전.
self.scope["url_route"]["kargs"] routing에서 정규표현식으로 사용한 변수.
self.scope["url_route"]["kargs"]["변수키"] 변수의 값을 얻어온다.
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는 받는 함수를 지정할 때 쓰인다.(없어도 되긴 함.)

type의 이름이 chat.message 라면 chat_message로 변경되어 처리된다.

데이터 전송 함수 설명
self.send() 웹소켓(자바스크립트)으로 데이터를 보내 창에 띄운다.
  • async_to_sync(동기식)
  • await self.channel_layer.group_send(비동기식)
다른 유저들에게 보낼 때 사용한다.
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과 같은 변수는 위에서 받아 넘겨주어야 한다.


웹소켓이 만들어지면 아래 4개의 이벤트 사용이 가능해진다.

  • open – 커넥션이 제대로 만들어졌을 때 발생함
  • message – 데이터를 수신하였을 때 발생함
  • error – 에러가 생겼을 때 발생함
  • close – 커넥션이 종료되었을 때 발생

이벤트의 활용은 아래 예시를 참고하자.

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']