※はじめからはこちら
今回からいよいよ、Django Channelsを使ってWebsocket部分を実装していきます。今回はサーバサイドを大まかに作っていきます。
Django Channels
Django Channelsは標準のDjangoでは扱いづらいWebsocketなどのプロトコルへDjangoを対応させるためのライブラリです。
Django開発グループが作成しているので安心感があります。今後DjangoでWebsocketを使う必要がある場合はChannels一択になるかと思います。
channelsの有効化
今回利用しているDockerの環境にはChannelsはインストール済ですので不要ですが、実際にインストールする場合は以下を実行します。
$ pip install channels channels-redis
さて、インストールされているchannelsを有効化ていきます。
まずは、channels
のエントリーポイント(最初にアクセスされるファイル)を用意します。
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({})
ProtocolTypeRouter
が最初にアクセスを受け入れる入り口になります。その名前の通りアクセスしてきたプロトコルごとに処理を振り分けます。今はどのプロトコルに対しても振り分けのルールを書いていませんが、デフォルトでHTTP
用のルールが自動設定されているので、まずはこのままで大丈夫です。
続いてsettingsを変更していきます。
index 264d64a..23dc6b3 100644
--- a/application/settings/base.py
+++ b/application/settings/base.py
@@ -39,6 +39,9 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+
+ 'channels',
+
'modules.kanban',
]
@@ -133,3 +136,6 @@ ROOT_URLCONF = 'application.views.urls'
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/accounts/login/'
LOGOUT_REDIRECT_URL = '/'
+
+# ASGIの起点を指定
+ASGI_APPLICATION = 'views.routing.application'
INSTALLED_APPS
にchannels
を'django.contrib.staticfiles'
と'modules.kanban'
の間に追加します。そして、ASGI_APPLICATION
という変数に、先程追加したrouting.py
のapplication
を指定します。
場合によっては編集中にdjangoが再起動して、中途半端な状態でファイルを読み込み停止してしまう可能性もあるので動きがおかしいと思った場合は以下でDockerを再起動します。
$ docker-compose restart
$ docker-compose restart
WARNING: The DJANGO_ENV variable is not set. Defaulting to a blank string.
Restarting workrepo-for-tutorial_service_nginx_1 ... done
Restarting workrepo-for-tutorial_service_1 ... done
Restarting workrepo-for-tutorial_vuejs_1 ... done
Restarting workrepo-for-tutorial_redis_1 ... done
Restarting workrepo-for-tutorial_db_1 ... done
再起動後、 http://localhost:3000/ に再度アクセスして画面がしっかりと表示されてば設定は完了です。見た目上は特に変わっていませんが、Channels上(ASGI上)でアプリケーションが動作するようになっています。
websocketの処理追加
Channels上でWebSocketを使えるようにしていきます。このセクションではws://localhost:3000/ws/board/1
へWebsocketでアクセスできるようにします。
Consumerの作成
いままではAPIごとにViewを作ってきましたが、WebSocketでも同じ様に処理を受け付けるコンポーネントはConsumer
と呼ばれます。まずは簡単なConsumerを作っておきます。
views
配下に新たにws
パッケージを作ります。
$ mkdir application/views/ws
$ touch application/views/ws/__init__.py
続いて、以下のファイルを作成します。
from channels.generic.websocket import JsonWebsocketConsumer
class KanbanConsumer(JsonWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 自身に一意なIDを付与する
self.consumer_id = id(self)
# 後で使う
self.user = None
def connect(self):
# 認証チェック
if not self.scope['user'].is_authenticated:
self.close()
return
self.user = self.scope['user']
# 接続を受け入れる
self.accept()
自身のConsumerを定義する場合はchannels
が提供しているベースクラスを継承して作成します。JSON以外を扱う前提の基底クラスもありますが、JSONをクライアントとやり取りするのであればJsonWebsocketConsumer
を継承しておくのが最も楽です。
__init__
はオーバーライドする必要は必ずしもないですが、Consumerに独自の属性(consumer_id
, user
)をもたせたいので今回はオーバライドしています。
websocketの場合、最初にアクセスしてきた際にこのKanbanConsumer
がインスタンス化され、自身のconnect
メソッドが呼び出されます。今回は、そのタイミングでログイン済であるかをチェックし、未ログインであればself.close()
を呼び出して切断するようにしています。
routingの追加
先程作成したrouting.py
を以下のように編集します。
@@ -1,4 +1,13 @@
-from channels.routing import ProtocolTypeRouter
+from channels.auth import AuthMiddlewareStack
+from channels.routing import ProtocolTypeRouter, URLRouter
+from django.urls import path
+from .ws.kanban_consumer import KanbanConsumer
-application = ProtocolTypeRouter({})
+application = ProtocolTypeRouter({
+ 'websocket': AuthMiddlewareStack(
+ URLRouter([
+ path('ws/boards/<int:board_id>', KanbanConsumer),
+ ])
+ ),
+})
websocket
というキーにマッピングを追加しましたので、WebSocketでのアクセスはここに流れてきます。まずは、AuthMiddlewareStack
が呼び出されて、Django自体が提供しているセッション内のログインユーザ情報にChannelsでもアクセスできるようになります。その後、リクエストされたURLが'ws/boards/<int:board_id>'
にマッチすれば、先程作成されたKanbanConsumer
が呼び出されるようになります。
動作確認
WebSocketの場合、ブラウザから開いて動作確認というのは難しいです(ブラウザから開く場合はHTTPになるので)。wscat
などを使うことが多いようですが、今回はちゃんとログインしたセッションでないと処理できないので、wsrequests
というPythonライブラリを使って確認します。
これはカンバンアプリケーションの動作に必要なライブラリではないですが、動作確認のためにインストールします。(Docker外で大丈夫です)
$ pip install wsrequests
そして、wstest.py
として以下のファイルを作成します。もしログイン情報をtest/test
以外にしている場合は書き換えてください。
from wsrequests import WsRequests
wsr = WsRequests()
# login django
wsr.get('http://localhost:3000/accounts/login/')
wsr.post(
'http://localhost:3000/accounts/login/',
data={
'username': 'test', # Djangoのユーザ名とパスワード
'password': 'test',
'csrfmiddlewaretoken': wsr.cookies['csrftoken'],
'next': '/',
}
)
wsr.connect('ws://localhost:3000/ws/boards/1')
wsr.disconnect()
そして以下のように実行します。
$ python wstest.py
$ python wstest.py
2018-09-22 17:00:13,528 - DEBUG - MainThread - connect websocket [ws://localhost:3000/ws/boards/1]
2018-09-22 17:00:13,545 - DEBUG - MainThread - disconnect websocket [ws://localhost:3000/ws/boards/1]
実行例のように、エラーなくconnect
とdisconnect
が出れば正常です。ここまででChannelsをつかったConsumerの基本的な部分が用意できましたので、次回からは実際の処理を組み込んでいきます。