前回の続きです。 kivantium.hateblo.jp
サーバー上で重たい処理をする場合、処理が全て終わってからHTMLを生成しているとかなりレスポンスが悪くなってしまいます。先に結果表示ページのHTMLを表示しておいて、中身を後からWebsocketで順次通信するといい感じになりそうです。Websocket通信したデータを表示するためにVue.jsを使うことにします。
前回からの変更点
前回のHello, worldで使ったディレクトリ構成を編集する形で作業を進めます。
mysite/routing.py
を以下の内容で書き換えます。ws/test/
にアクセスすることでWebsocket通信を行えるように設定しています。
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from django.urls import path from . import consumers websocket_urlpatterns = [ path('ws/test/', consumers.Consumer), ] application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( websocket_urlpatterns ) ), })
mysite/consumers.py
にWebsocket通信で行う処理を記述します。本当は非同期処理をするべきらしいのですが、動く書き方が分からなかったので同期処理で書いています。(参考: django で Websocket - 空のブログ)
import json import time import threading from channels.generic.websocket import WebsocketConsumer class Consumer(WebsocketConsumer): # 接続されたときの処理 def connect(self): # 接続を許可する self.accept() # メッセージ送信関数を新しいスレッドで呼び出す # 本当は非同期で書くべきだがうまく動かなかった self.sending = True self.sender = threading.Thread( target=self.send_message, args=('Hello', )) self.sender.start() # 接続が切断されたときの処理 def disconnect(self, close_code): # スレッドを終了するフラグを立てる self.sending = False # スレッドの終了を待つ self.sender.join() # メッセージを受け取ったときの処理 def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] print("Received message:", message) # メッセージ送信関数 def send_message(self, message): while True: # 終了フラグが立っていたら終了する if not self.sending: break self.send(text_data=json.dumps({ 'message': message, })) time.sleep(1)
mysite/templates/mysite/index.html
にこれと通信するためのHTMLとJavaScriptを書きます。
<!doctype html> <html> <head> <title>test</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <ul > <li v-for="message in messages">[[ message ]]</li> </ul> </div> <script> var vm = new Vue({ el: '#app', // Vueの記号がDjangoと被らないようにする delimiters: ['[[', ']]'], data: { messages: [], ws: new WebSocket( // http or https の判定 // https://www.koatech.info/blog/vue-websocket-sample/ (window.location.protocol == "https:" ? "wss" : "ws") + '://' + window.location.host + '/ws/test/' ), }, created: function() { // Vueオブジェクトにアクセスするために変数化する // https://www.koatech.info/blog/vue-websocket-sample/ const self = this; // メッセージを受け取ったときの処理 self.ws.onmessage = function(e) { const data = JSON.parse(e.data); console.log(data.message) console.log(vm.messages) // メッセージを表示する vm.messages.push(data.message); // サーバーにメッセージを送る self.ws.send(JSON.stringify({ 'message': "Konnichiwa", })); }; // 接続が切断されたときの処理 self.ws.onclose = function(e) { console.error('Websocket has been closed unexpectedly'); }; } }) </script> </body> </html>
mysite/views.py
を次のように書き換えてindex.htmlを表示するようにします。
from django.shortcuts import render def index(request): return render(request, 'mysite/index.html')
最後に、テンプレートを認識させるために mysite/settings.py
の INSTALLED_APPS
に mysite
を追加します。
INSTALLED_APPS = [ ...... 'channels', 'mysite', ]
以上の変更を行ったものをデプロイしてブラウザで閲覧すると1秒ごとにHelloが追加される様子が確認できます。
自分の目的にはこれで十分でしたが、チャットなどのより高度なことをしたい場合はChennelsのドキュメントを読んで下さい。 channels.readthedocs.io