1번째 줄: |
1번째 줄: |
| == 개요 == | | == 개요 == |
− | https://channels.readthedocs.io/en/latest/index.html<nowiki/>를 참고하였다.
| + | 장고 채널 기능의 활용을 위해 간단한 채팅 앱 만들어보기. |
| | | |
− | 장고는 기본적으로 동기식으로, 채팅을 위해선 비동기식 처리가 필요하다.
| + | === 사전 준비 === |
| + | {| class="wikitable" |
| + | !과정 |
| + | !설명 |
| + | !방법 |
| + | |- |
| + | |장고 |
| + | |장고가 설치되어 있다고 가정한다. |
| + | |[http://id8436.iptime.org:2786/mediawiki/index.php/1.%20%EC%9E%A5%EA%B3%A0%20%EA%B0%9C%EC%9A%94 1. 장고 개요] |
| + | |- |
| + | |채널 설치 |
| + | |채널이 설치되어 있다고 가정한다. |
| + | |[http://id8436.iptime.org:2786/mediawiki/index.php/%EC%9E%A5%EA%B3%A0%20channels 장고 channels] |
| + | |} |
| + | = 기초준비 = |
| | | |
− | 이를 위해 Django Channels가 마련되어 있다.
| + | == 채팅어플 만들기 == |
− | | |
− | ASGI(Async Server Gateway Interface)프로토콜은 WSGI를 계승하여 이와 잘 호환되도록 설계되어 있다.
| |
− | | |
− | ASGI는 비동기 요청인 웹 소켓을 처리하는 이벤트로 connect, send, receive, disconnect가 있다.
| |
− | | |
− | == 기초준비 ==
| |
− | | |
− | === 채팅어플 만들기 ===
| |
| 패키지설치부터 진행하기엔 중간점검이 어려워 틀 만들기를 먼저 수행한다. | | 패키지설치부터 진행하기엔 중간점검이 어려워 틀 만들기를 먼저 수행한다. |
| {| class="wikitable" | | {| class="wikitable" |
21번째 줄: |
27번째 줄: |
| |어플리케이션생성 | | |어플리케이션생성 |
| |채팅을 위한 앱을 생성한다. | | |채팅을 위한 앱을 생성한다. |
− | 앱을 생성하고 __init__.py와 views.py를 제외한 모든 것들을 지운다.
| |
| |django-admin startapp chat | | |django-admin startapp chat |
| |- | | |- |
28번째 줄: |
33번째 줄: |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
| INSTALLED_APPS = [ | | INSTALLED_APPS = [ |
| + | ... |
| 'chat', | | 'chat', |
| </syntaxhighlight> | | </syntaxhighlight> |
| |- | | |- |
− | |URL매핑 | + | |폴더 정리 |
− | |기본이 되는 urls.py 안에서 해당 앱으로 매핑을 시켜준다. | + | | - 앱을 생성하고 생성된 __init__.py와 views.py를 제외한 모든 것들을 지운다. |
− | |path('chat/', include('chat.urls')),
| + | (채팅기능만을 위해선 나머지는 필요 없어, 지워도 된다. 근데 그냥 두자.) |
| + | |
| + | - 탬플릿을 담기 위해 templates/chat 디렉토리를 생성해준다. |
| + | | |
| |- | | |- |
− | |URL매핑2 | + | |urls.py 생성 및 매핑 |
− | |그리고 앱 안의 urls.py 작성. | + | |앱 내에서 사용되는 url을 다루기 위해 urls.py를 생성한다. |
− | |<syntaxhighlight lang="python"> | + | |chat/urls.py의 내용.<syntaxhighlight lang="python"> |
− | from dfango.urls import path | + | from django.urls import path |
| from . import views | | from . import views |
| + | app_name = 'chat' # 보통 앱이름을 써서 url을 구분하지만, |
| + | |
| + | urlpatterns = [ |
| + | path('', views.index, name='index'), |
| + | ] |
| + | </syntaxhighlight>기초 urls.py 수정<syntaxhighlight lang="python"> |
| + | from django.contrib import admin |
| + | from django.urls import include, path |
| | | |
| urlpatterns = [ | | urlpatterns = [ |
− | path('/', views.index, name='index'), | + | path('chat/', include('chat.urls')), |
| + | path('admin/', admin.site.urls), |
| ] | | ] |
| </syntaxhighlight> | | </syntaxhighlight> |
− | |- | + | |} |
− | |view 작성
| + | 여기까지 하고 runserver 후 제대로 작동하는지 확인하자. |
− | |
| |
− | |<syntaxhighlight lang="python">
| |
− | from django.shortcuts import render
| |
| | | |
− | def index(request):
| + | == 채팅룸 구현 == |
− | return render(request, 'chat/index.html', {})
| + | === 채팅 인덱스 구현 === |
− | </syntaxhighlight>
| + | 들어갈 채팅룸을 입력하는 공간. |
| + | {| class="wikitable" |
| + | !과정 |
| + | !설명 |
| + | !방법 |
| |- | | |- |
− | |탬플릿 준비 | + | |채팅 인덱스 탬플릿 작성 |
− | |앱 하위에 templates>chat 디렉터리까지 만든다. | + | |채팅룸을 입력하기 위한 인덱스 탬플릿. |
− | index.html 이라는 이름으로 만들자. | + | templates/chat/index.html로 작성하자. |
| |<syntaxhighlight lang="html+django"> | | |<syntaxhighlight lang="html+django"> |
− | <!-- chat/templates/chat/index.html -->
| |
| <!DOCTYPE html> | | <!DOCTYPE html> |
| <html> | | <html> |
66번째 줄: |
84번째 줄: |
| <title>Chat Rooms</title> | | <title>Chat Rooms</title> |
| </head> | | </head> |
| + | <body> |
| + | What chat room would you like to enter?<br> |
| + | <input id="room-name-input" type="text" size="100"><br> |
| + | <input id="room-name-submit" type="button" value="Enter"> |
| | | |
− | <body>
| |
− | What chat room would you like to enter?<br/>
| |
− | <input id="room-name-input" type="text" size="100"/><br/>
| |
− | <input id="room-name-submit" type="button" value="Enter"/>
| |
− |
| |
| <script> | | <script> |
| document.querySelector('#room-name-input').focus(); | | document.querySelector('#room-name-input').focus(); |
88번째 줄: |
105번째 줄: |
| </html> | | </html> |
| </syntaxhighlight> | | </syntaxhighlight> |
− | |}
| |
− | 여기까지 하고 runserver 후 제대로 작동하는지 확인하자.
| |
− |
| |
− | === 패키지 설치 ===
| |
− | {| class="wikitable"
| |
− | !과정
| |
− | !설명
| |
− | !방법
| |
| |- | | |- |
− | |패키지 설치 | + | |뷰 작성 |
− | |Channels 패키지를 설치한다.
| + | | |
− | |pip install -U channels
| |
− | |-
| |
− | |앱 등록
| |
− | |settings.py에 추가.
| |
− | channels는 runserver 명령을 제어하여 기존 서버를 대체한다.
| |
− | |INSTALLED_APPS 하위, 가장 처음에 <code>'channels'</code>넣는다.<syntaxhighlight lang="python">
| |
− | INSTALLED_APPS = [
| |
− | 'channels', # 다른 서드파티 앱과 충돌할 수 있어 가장 처음에 둔다.
| |
− | ... # 공식 튜토리얼에선 'chat'을 상위로 올리지만, 아래에 있어도 상관 없다.
| |
− | ...
| |
− | 'chat',
| |
− | </syntaxhighlight>
| |
− | |-
| |
− | |라우팅 설정 작성
| |
− | |가장 상위의 디렉터리에 routing.py를 다음과 같이 작성한다. | |
− | (취향에 따라 달리 작성해도 된다.)
| |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
− | from channels.routing import ProtocolTypeRouter | + | from django.shortcuts import render |
| | | |
− | application = ProtocolTypeRouter({
| + | def index(request): |
− | # (http->django views is added by default) | + | return render(request, 'chat/index.html') |
− | })
| |
| </syntaxhighlight> | | </syntaxhighlight> |
− | |-
| |
− | |설정 추가
| |
− | |settings.py 안에 우측의 내용을 추가한다.
| |
− | 라우팅파일의 위치만 잘 잡아주면 된다.
| |
− | |<syntaxhighlight lang="python">
| |
− | ASGI_APPLICATION = 'routing.application' # routing.py 파일의 application을 불러온다.
| |
− | </syntaxhighlight>(라우팅 파일 안의 application을 가져온다.)
| |
| |} | | |} |
| | | |
− | == 채팅룸 구현 == | + | === 채팅룸 구현 === |
| {| class="wikitable" | | {| class="wikitable" |
| !과정 | | !과정 |
| !설명 | | !설명 |
| !방법 | | !방법 |
− | |-
| |
− | |URL매핑
| |
− | |앱 내의 urls.py에 추가.
| |
− | |<syntaxhighlight lang="python">
| |
− | urlpatterns = [
| |
− | ...
| |
− | path('<str:room_name>/', views.room, name='room'),
| |
− | ...
| |
− | ]
| |
− | </syntaxhighlight>
| |
− | |-
| |
− | |뷰 작성
| |
− | |room 뷰 작성
| |
− | mark_safe와 json을 가져온다.
| |
− | |<syntaxhighlight lang="python">
| |
− | from django.shortcuts import render
| |
− | from django.utils.safestring import mark_safe
| |
− | import json
| |
− |
| |
− | def room(request, room_name):
| |
− | return render(request, 'chat/room.html', {
| |
− | 'room_name_json': mark_safe(json.dumps(room_name))
| |
− | })
| |
− | </syntaxhighlight>
| |
| |- | | |- |
| |탬플릿 작성 | | |탬플릿 작성 |
− | |/char/room.html을 만든다. | + | |templates/chat/room.html |
| |<syntaxhighlight lang="html+django"> | | |<syntaxhighlight lang="html+django"> |
| <!DOCTYPE html> | | <!DOCTYPE html> |
170번째 줄: |
131번째 줄: |
| <title>Chat Room</title> | | <title>Chat Room</title> |
| </head> | | </head> |
− |
| |
| <body> | | <body> |
− | <textarea id="chat-log" cols="100" rows="20"></textarea><br/> | + | <textarea id="chat-log" cols="100" rows="20"></textarea><br> |
− | <input id="chat-message-input" type="text" size="100"/><br/> | + | <input id="chat-message-input" type="text" size="100"><br> |
− | <input id="chat-message-submit" type="button" value="Send"/> | + | <input id="chat-message-submit" type="button" value="Send"> |
− | </body> | + | {{ room_name|json_script:"room-name" }} |
| + | <script> |
| + | const roomName = JSON.parse(document.getElementById('room-name').textContent); |
| | | |
− | <script>
| + | const chatSocket = new WebSocket( |
− | var roomName = {{ room_name_json }};
| + | 'ws://' |
| + | + window.location.host |
| + | + '/ws/chat/' |
| + | + roomName |
| + | + '/' |
| + | ); |
| | | |
− | var chatSocket = new WebSocket(
| + | chatSocket.onmessage = function(e) { |
− | 'ws://' + window.location.host +
| + | const data = JSON.parse(e.data); |
− | '/ws/chat/' + roomName + '/');
| + | document.querySelector('#chat-log').value += (data.message + '\n'); |
| + | }; |
| | | |
− | chatSocket.onmessage = function(e) {
| + | chatSocket.onclose = function(e) { |
− | var data = JSON.parse(e.data);
| + | console.error('Chat socket closed unexpectedly'); |
− | var message = data['message'];
| + | }; |
− | document.querySelector('#chat-log').value += (message + '\n'); | |
− | };
| |
| | | |
− | chatSocket.onclose = function(e) {
| + | document.querySelector('#chat-message-input').focus(); |
− | console.error('Chat socket closed unexpectedly'); | + | document.querySelector('#chat-message-input').onkeyup = function(e) { |
− | };
| + | if (e.keyCode === 13) { // enter, return |
− | | + | document.querySelector('#chat-message-submit').click(); |
− | document.querySelector('#chat-message-input').focus();
| + | } |
− | document.querySelector('#chat-message-input').onkeyup = function(e) {
| + | }; |
− | if (e.keyCode === 13) { // enter, return
| |
− | document.querySelector('#chat-message-submit').click();
| |
− | }
| |
− | };
| |
− | | |
− | document.querySelector('#chat-message-submit').onclick = function(e) {
| |
− | var messageInputDom = document.querySelector('#chat-message-input');
| |
− | var message = messageInputDom.value;
| |
− | chatSocket.send(JSON.stringify({
| |
− | 'message': message | |
− | }));
| |
− | | |
− | messageInputDom.value = ''; | |
− | };
| |
− | </script>
| |
| | | |
| + | document.querySelector('#chat-message-submit').onclick = function(e) { |
| + | const messageInputDom = document.querySelector('#chat-message-input'); |
| + | const message = messageInputDom.value; |
| + | chatSocket.send(JSON.stringify({ |
| + | 'message': message |
| + | })); |
| + | messageInputDom.value = ''; |
| + | }; |
| + | </script> |
| + | </body> |
| </html> | | </html> |
| + | </syntaxhighlight> |
| + | |- |
| + | |뷰 작성 |
| + | |뷰에 다음 함수를 추가한다. |
| + | |<syntaxhighlight lang="python"> |
| + | def room(request, room_name): |
| + | return render(request, 'chat/room.html', { |
| + | 'room_name': room_name |
| + | }) |
| + | </syntaxhighlight> |
| + | |- |
| + | |URL |
| + | |다음의 내용을 urls.py에 추가한다. |
| + | |<syntaxhighlight lang="python"> |
| + | path('<str:room_name>/', views.room, name='room'), |
| </syntaxhighlight> | | </syntaxhighlight> |
| |} | | |} |
− | | + | === 참가자(컨슈머) 구현 === |
− | === 참가자 구현 === | |
| {| class="wikitable" | | {| class="wikitable" |
| !과정 | | !과정 |
228번째 줄: |
203번째 줄: |
| 클라이언트로부터 메시지를 받아서 그대로 전달. | | 클라이언트로부터 메시지를 받아서 그대로 전달. |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
| + | import json |
| from channels.generic.websocket import WebsocketConsumer | | from channels.generic.websocket import WebsocketConsumer |
− | import json
| |
| | | |
| class ChatConsumer(WebsocketConsumer): | | class ChatConsumer(WebsocketConsumer): |
250번째 줄: |
225번째 줄: |
| |/chat/routing.py 작성 | | |/chat/routing.py 작성 |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
− | from django.conf.urls import url | + | from django.urls import re_path |
| + | |
| from . import consumers | | from . import consumers |
| | | |
| websocket_urlpatterns = [ | | websocket_urlpatterns = [ |
− | url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer), | + | re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()), |
| ] | | ] |
| </syntaxhighlight> | | </syntaxhighlight> |
| |- | | |- |
| |라우팅 등록 | | |라우팅 등록 |
− | |/routing.py 수정 | + | |channels를 설치하며 작성한 agsi.py 안에 내용을 채워준다. |
| chat 안의 라우팅을 등록해준다. | | chat 안의 라우팅을 등록해준다. |
| | | |
− | 그리고 임포트하는 모듈을 수정해준다. | + | 그리고 이를 사용하기 위한 라이브러리를 불러온다. |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
| + | ...... |
| + | |
| + | from channels.routing import URLRouter |
| + | from channels.security.websocket import AllowedHostsOriginValidator |
| from channels.auth import AuthMiddlewareStack | | from channels.auth import AuthMiddlewareStack |
− | from channels.routing import ProtocolTypeRouter, URLRouter
| |
| import chat.routing | | import chat.routing |
| | | |
− | application = ProtocolTypeRouter({ | + | application = ProtocolTypeRouter( |
− | # (http->django views is added by default)
| + | { |
− | 'websocket': AuthMiddlewareStack(
| + | "http": django_asgi_app, |
− | URLRouter(
| + | "websocket": AllowedHostsOriginValidator( |
− | chat.routing.websocket_urlpatterns
| + | AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns)) |
− | ) | + | ), |
− | ), | + | } |
− | })
| + | ) |
| </syntaxhighlight> | | </syntaxhighlight> |
− | |} | + | |}여기까지 하고 점검해보자. /chat/lobby/ 에 접속한 후 채팅을 치면 떠야 한다. |
− | 여기까지 하고 채팅을 쳐 보면... 채팅이 나와야 정상. | + | |
| + | 같은 브라우저를 띄워두고 한쪽에서 채팅을 치면 다른 쪽에서 안뜨는데, 다른 브라우저에선 안뜬다. 접속한 모두에게 반영될 수 있도록 채널레이어를 구현한다. |
| + | |
| + | = 채널 레이어 구현 = |
| + | consumer 인스턴스는 자동으로 유일한 channel name을 구성하기 때문에 서로 소통하려면 layer가 필요하다. |
| + | |
| + | == 레디스 == |
| + | 레디스 서버를 이용한 실시간 채팅 구현. |
| | | |
− | === 체널레이어 구현 === | + | 꽤 오래된 버전인 5를 사용하는데, 아마도 이는.. 5에서 처음으로 consumer groups이 구현된 버전인데, 이걸로 구현했기 때문이 아닐까 싶다.(최신버전에서도 될진.. 검증이 필요하다.) |
| + | ===사전설정=== |
| {| class="wikitable" | | {| class="wikitable" |
| !과정 | | !과정 |
286번째 줄: |
273번째 줄: |
| !방법 | | !방법 |
| |- | | |- |
− | |패키지 설치 | + | | 서버 설치 |
− | |Channels가 Redis인터페이스를 인식하도록. | + | |설치 후 딱히 옵션을 변경할 내용은 없다. |
| + | 어차피 로컬에서 모든 작동이 수행되기 때문에. |
| + | |
| + | 네이티브로 설치하는 게 아니라 도커를 사용할 거라면 아래로 넘어가자. |
| + | |
| + | 단순 apt-get install redis로 설치해 5 이상의 버전이 되어도 작동한다. |
| + | |[http://id8436.iptime.org:8080/mediawiki/index.php/Radis Redis] 문서 참조. |
| + | |- |
| + | |도커 |
| + | |도커가 설치되어 있다면 아래 명령으로 바로 설치, 서비스가 시작된다. |
| + | |
| + | docker run -p 6379:6379 -d redis:5 |
| + | |아래 문서를 통해 도커 설치를 참고하자. |
| + | http://id8436.iptime.org:8080/mediawiki/index.php/Docker |
| + | |- |
| + | |연동 라이브러리 설치 |
| + | |서버와 채널을 연동할 라이브러리를 설치한다. |
| |pip install channels_redis | | |pip install channels_redis |
| |- | | |- |
− | |settings.py 설정 | + | |세팅 |
− | |ASGI 아래 추가하자.
| + | |settings.py 안에 추가. |
| |<syntaxhighlight lang="python"> | | |<syntaxhighlight lang="python"> |
− | ASGI_APPLICATION = 'routing.application' # routing.py 파일의 application을 불러온다. | + | ASGI_APPLICATION = "mysite.asgi.application" # 이부분은 채널을 설치할 때 작성한 부분. |
| CHANNEL_LAYERS = { | | CHANNEL_LAYERS = { |
− | 'default': { | + | "default": { |
− | 'BACKEND': 'channels_redis.core.RedisChannelLayer', | + | "BACKEND": "channels_redis.core.RedisChannelLayer", |
− | 'CONFIG': { | + | "CONFIG": { |
− | "hosts": [('127.0.0.1', 6379)], | + | "hosts": [("127.0.0.1", 6379)], |
| }, | | }, |
| }, | | }, |
| } | | } |
| + | </syntaxhighlight>을 추가해준다. |
| + | |} |
| + | ==기존 컨슈머 변형== |
| + | 위에서 작성한 컨슈머는 중간 확인을 위한 동기식 함수인데, 비동기식으로 다시 쓰이면 성능이 좋아진다. 아래와 같이 변형하자.<syntaxhighlight lang="python"> |
| + | import json |
| + | |
| + | from channels.generic.websocket import AsyncWebsocketConsumer |
| + | |
| + | |
| + | class ChatConsumer(AsyncWebsocketConsumer): |
| + | async 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 |
| + | await self.channel_layer.group_add(self.room_group_name, self.channel_name) |
| + | |
| + | await self.accept() |
| + | |
| + | async def disconnect(self, close_code): |
| + | # Leave room group |
| + | await self.channel_layer.group_discard(self.room_group_name, self.channel_name) |
| + | |
| + | # Receive message from WebSocket |
| + | 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} |
| + | ) |
| + | |
| + | # Receive message from room group |
| + | async def chat_message(self, event): |
| + | message = event["message"] |
| + | |
| + | # Send message to WebSocket |
| + | await self.send(text_data=json.dumps({"message": message})) |
| + | </syntaxhighlight> |
| + | |
| + | = 서버에 반영 = |
| + | 튜토리얼엔 나오지 않지만(공식문서 한참 뒤에 나온다..), 서버에 반영하게 되면 ws/ 경로에 대하여 새로운 설정을 해주어야 한다.(필자는 이걸 몰라서 이것저것 건드리다가 sql 날려먹었다.. 젠장) |
| + | |
| + | 일반적으로 WSGI 통신을 gunicorn으로 처리한다면, ASGI통신은 daphne으로 처리한다.(daphne에서도 wsgi통신이 가능하지만, 보안상 권장되진 않는다.) |
| + | {| class="wikitable" |
| + | !과정 |
| + | !설명 |
| + | !방법 |
| + | |- |
| + | | 설치 |
| + | |daphne은 channels를 설치하면 자동으로 설치가 된다. |
| + | | |
| + | |- |
| + | |확인 |
| + | |실행 명령은 우측과 같다.(asgi의 경로를 지정해주면 된다.) |
| + | |
| + | 포트번호는 필요에 따라 지정하면 된다. |
| + | |
| + | 해당 포트로 접속해보면 사이트와 채팅이 잘 작동한다. |
| + | |<code>daphne -b 0.0.0.0 -p 8001 config.asgi:application</code> |
| + | |- |
| + | |서버에서 실행 |
| + | |서비스를 위해선 백그라운드로 진행해주어야 한다. |
| + | 실행 여부는 <code>ps -ef | grep daphne</code>로 확인해보자. 실행이 안되면 nohup 떼서 에러메시지 확인. |
| + | |<code>nohup daphne -b 0.0.0.0 -p 8001 config.asgi:application &</code> |
| + | |- |
| + | |웹서버에 반영 |
| + | |/static을 반영하듯, /ws로 시작되는 주소는 다핀으로 넘겨준다. |
| + | 이후 관련 서비스들을 재시작하면 설정들이 반영된다. |
| + | |
| + | 채팅도 물론 성공적...! |
| + | |<syntaxhighlight lang="bash"> |
| + | location /ws { |
| + | proxy_pass http://localhost:8001; |
| + | proxy_http_version 1.1; |
| + | proxy_set_header Upgrade $http_upgrade; |
| + | proxy_set_header Connection "upgrade"; |
| + | } |
| </syntaxhighlight> | | </syntaxhighlight> |
| |} | | |} |
− | [[분류:장고 기능구현(중급)]] | + | 웹소켓에서도 https 인증을 적용할 수 있다.([https://victorydntmd.tistory.com/265 추후에 반영해보자.]) |
| + | |
| + | == 관련에러 == |
| + | |
| + | === The app module <module '앱이름' (<_frozen_importlib_external._NamespaceLoader object at 0x7f2faed9a8f0>)> has multiple filesystem locations (['/./앱경로', '/앱경로']); you must configure this app with an AppConfig subclass with a 'path' class attribute. === |
| + | 생각지도 못한 문제점이었다... 앱은 하나의 모듈처리가 되어 디렉토리 안에 __init__.py가 담기는데, 이게 없는 경우 경로를 제대로 찾지 못해 발생하는 에러이다. 앱 디렉토리 안에 __init__.py를 제대로 넣어주면 해결됨. |
| + | |
| + | = 마무리. 각 요소들이 어떤 역할을 하는지. = |
| + | 순서대로 어떤 경로를 거쳐 작동하는지 살펴보자. |
| + | |
| + | === asgi.py === |
| + | {| class="wikitable" |
| + | !항목 |
| + | !설명 |
| + | |- |
| + | | ProtocolTypeRouter |
| + | |연결의 종류를 파악하여 처리. http로 넘길지, 웹소켓 처리로 넘길지. |
| + | |
| + | * http 연결일 경우 보통 django_asgi_app<code>(get_asgi_application())</code> 가 실행된다. |
| + | * websocket 연결(ws:// 혹은 wss://)일 경우 <code>AuthMiddlewareStack</code>으로 앱별로 <code>routing.py</code>에 적어놓은 코드로 라우팅 한다. |
| + | |- |
| + | | |
| + | | |
| + | * |
| + | |} |
| + | |
| + | === routing.py === |
| + | 들어온 경로와 컨슈머를 연결해준다. urls.py와 view.py의 관계. |
| + | |
| + | 다음과 같은 형태로 컨슈머를 연결한다.<syntaxhighlight lang="python"> |
| + | websocket_urlpatterns = [ |
| + | re_path(r'ws/notification/(?P<center_name>\w+)/$', consumers.NotificationConsumer.as_asgi()), |
| + | ] |
| + | </syntaxhighlight> |
| + | |
| + | === [컨슈머는 컨슈머 문서로 옮김] === |
| + | [[분류:장고 웹소켓]] |