前回に引き続きChannelsを触っていきます。前回はChannelsの有効化までやっていきましたので、今回は実際にChannelsを使ってWebSocketでのチャットを実装していきます。
room viewの作成
まずはChatで部屋名を入力した後、実際にチャットを行う際の画面を作っていきます。mysite/chat/templates/chat/room.html
として以下を作成します。
<!-- chat/templates/chat/room.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Chat Room</title> </head> <body> <textarea id="chat-log" cols="100" rows="20"></textarea><br/> <input id="chat-message-input" type="text" size="100"/><br/> <input id="chat-message-submit" type="button" value="Send"/> </body> <script> var roomName = {{ room_name_json }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); }; chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; 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> </html>
つらつらとJSがかいてありますが、基本的にはページを開いた際にws://hostname:port/ws/chat/<部屋名>
へのコネクションをはります。その上で、inputに入力した内容をchatSocket.send
でサーバに送信し、サーバからWebSocket経由のメッセージがきた場合はchatSocket.onmessage
にあるように、受信した内容をtextarea
に反映させています。
この時点でhttp://localhost:3000/chat/lobby
にアクセスすると画面が表示されますが、当然WebSocketのサーバをまだ作成していないのでチャットはできません。以下の様にDevToolでは接続エラーがでています。
サーバ側にもWebSocketに対応するアプリケーションがない旨を示すエラーが出ています。
service_1 | 2018-03-26 15:00:23,529 - ERROR - ws_protocol - Traceback (most recent call last): service_1 | File "/usr/local/lib/python3.6/site-packages/daphne/ws_protocol.py", line 76, in onConnect service_1 | "subprotocols": subprotocols, service_1 | File "/usr/local/lib/python3.6/site-packages/daphne/server.py", line 184, in create_application service_1 | application_instance = self.application(scope=scope) service_1 | File "/usr/local/lib/python3.6/site-packages/channels/staticfiles.py", line 42, in __call__ service_1 | return self.application(scope) service_1 | File "/usr/local/lib/python3.6/site-packages/channels/routing.py", line 53, in __call__ service_1 | raise ValueError("No application configured for scope type %r" % scope["type"]) service_1 | ValueError: No application configured for scope type 'websocket'
本作業のコミットログは以下です。
consumerの作成
続いてWebSocketを扱うバックエンドを作っていきます。そのためにはまずはconsumer
を作成します。DjangoではURLConfを作成し、アクセスされるURLとその際に呼び出されるview
関数をマッピングします。Channelsはその例でいうところのview
に相当します。WebScoektのアクセスをどのように処理するかを定義するものです。
mysite/chat/consumers.py
として以下を作成します。
from channels.generic.websocket import WebsocketConsumer import json class ChatConsumer(WebsocketConsumer): """ WebSocketでの通信をハンドルする """ def connect(self): # とりあえず無条件で受け入れる # 接続を拒否する場合はself.close()する self.accept() def disconnect(self, close_code): pass def receive(self, text_data=None, bytes_data=None): """ 受け取ったメッセージをそのままオウム返しに戻す """ text_data_json = json.loads(text_data) message = text_data_json['message'] self.send(text_data=json.dumps({ 'message': message }))
すべての接続を受け入れ、受け取ったメッセージをそのまま戻すconsumer
です。これをWebSocketからのアクセス時に呼び出せるようにマッピングさせます。
mysite/chat/routing.py
として以下を作成します。
# chat/routing.py from django.urls import path from . import consumers websocket_urlpatterns = [ path('ws/chat/<str:room_name>/', consumers.ChatConsumer), ]
/ws/chat/部屋名/
というアクセスがあったときに先程つくったconsumers.ChatConsumer
を呼び出すという設定です。URLとのマッピングはDjangoのpath
を使っているためほとんど通常のURLConfと変わりません。
さらにRootのRoutingを定義するため前回作ったmysite/routing.py
を変更します。
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from chat import routing as chat_routing application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( chat_routing.websocket_urlpatterns ) ), })
websocketでのアクセスがあったときにはchat.routing.websocket_urlpatterns
のルールに従ってConsumerを呼び出すようになります。
ここまでで一応WebSocketは使えるようになっています。入力した内容がそのまま戻っているだけな上に他のセッションと共有されていない為わかりづらいですが、上の欄に出てくる内容はサーバがWebSocket経由で戻しているものです。
いわゆるエコーサーバ的なものが出来上がりました。
本作業のコミットログは以下です。
一旦まとめ
とりあえずWebSocketを通じてサーバと通信するところまで完成しました。次回はちゃんとチャットとして機能するようにROOM単位で内容が共有出来るようにしていきます。