[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

Django 2.0 + Channels 2.xを使ってWebSocketを扱う(その1)

現在仕事でWEBサービスを作るときはDjango一択な環境にいます。今度Websocketが必要になったのですがDjangoでWebsocketどうするんだってことで調べたところChannelsというライブラリを使うらしいです。Djangoのグループが作っているので安心感があります。Channelsは日本語でもいくつか記事がありましたが1.x時代のものでそのままでは動かなかったりしたのでチュートリアルを中心に試してみました。

ということで何回かにわけて(たぶん3回?)Django Channelsのチュートリアルをベースにちょっとだけ手を加えた内容をまとめていきます。また最終的には本番環境で動かせるようにnginx経由でwebsocketができるところまで持っていきます。

環境

以下の構成でやっていきます。

  • Python 3.6
  • Django 2.0.3
  • Django Channels 2.0.2

また、どのような環境でも動作するようにDockerでの環境構築をしています。とりあえず動かしたいという場合はdenzow/channel-sampleのmasterブランチdocker-compose upで動かせると思います。

セットアップ

まず、必要なライブラリを導入しDjangoのプロジェクトを作る必要があります。

$ pip install django channels channels_redis
$ django-admin startproject mysite .

さらに、チュートリアルの例ではchatというアプリをstartappで作成し、それを編集する流れになっています。ここまでを終わらせた状態のものをDockerの環境こみでstartブランチとして公開しましたので以下よりDLして展開し、docker-compose upすれば整います。

github.com

denzownoMacBook-Pro:~ denzow$ docker-compose up

実行すると、以下のように3つのコンテナが起動します。

denzownoMacBook-Pro:~ denzow$ docker ps
CONTAINER ID        IMAGE                         COMMAND                  CREATED              STATUS              PORTS                                     NAMES
92ad1eb59de1        channelsample_service_nginx   "/start-nginx.sh"        About a minute ago   Up About a minute   80/tcp, 443/tcp, 0.0.0.0:8000->8000/tcp   channelsample_service_nginx_1
11dd8ba126a0        channelsample_service         "/app/docker/servi..."   About a minute ago   Up About a minute   0.0.0.0:3000->3000/tcp                    channelsample_service_1
77a55e623f6a        channelsample_redis           "docker-entrypoint..."   About a minute ago   Up About a minute   0.0.0.0:6379->6379/tcp                    channelsample_redis_1

channelsample_serviceがDjangoのコンテナで、channelsample_service_nginxがリバースプロキシ用のnginx、channelsample_redisはあとで必要になるredisサーバです。

では、channelsample_serviceに入って作業をしていきます。まずはとりあえず管理用のスーパーユーザを作っておきます。

denzownoMacBook-Pro:~ denzow$ docker exec -it channelsample_service_1 bash
root@11dd8ba126a0:/app# ls
root@11dd8ba126a0:/app# python manage.py createsuperuser

これで、Djang-adminが使えるはずです。ブラウザでhttp://localhost:3000/admin/にアクセスし、作成したユーザ・パスワードでログインできるか確認しておきます。

f:id:denzow:20180325235827p:plain

問題なくログインできたら次に進みます。

chatアプリケーションについて

現時点ではhttp://localhost:3000/chat/にアクセスするとチャットルーム名の入力を促すページが表示されます。

What chat room would you like to enter?

ただし、この時点ではルーム名を入力して進むと404になります。これはchat/urls.pyに以下のように空のルーティングの設定しかないためです。

# chat/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

このファイルはsettings.pyROOT_URLCONFに指定されているmysite.urlsで以下の様にincludeされています。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('chat/', include('chat.urls'))
]

そのため、現在はchat/ + ''のURLしかアクセスできない状態になっていますので、この動作は現時点では想定通りです。

Channelsの有効化

ではChannelsを有効化していきます。変更内容のcommitはこちらです。まずmysite/routing.pyとして以下のファイルを作成します。

from channels.routing import ProtocolTypeRouter


application = ProtocolTypeRouter({
    # (http->django views is added by default)
})

ProtocolTypeRouterはDjangoのURLConfと同じようにリクエストを受け取った際にどのようなコードを実行するかをChannelsに伝えるものです。

では続いてsettings.pyを編集します。

INSTALLED_APPS = [
    'channels',   # <--- 追加
    'chat',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]


# :
# 最下部
# :

# ASGIの起点を指定
# sys.path.append(APP_ROOT_PATH)してるからmysiteは省略できる
ASGI_APPLICATION = 'routing.application'

channelsINSTALLED_APPSに追加し、ASGI_APPLICATIONに先程作成したrouting.pyapplicationを指定します。これでChannelsが有効化されます。

runserverについて

channelsample_serviceコンテナは現時点でpython manage.py runserver 0.0.0.0:3000を実行しています。本番環境等ではuwsgi等のアプリケーションサーバを使用しますが、Channelsが扱うASGIというインターフェースはuwsgiで扱うことができないのです。一方で、channelsを導入した環境ではrunserverがASGI対応に自動的に置き換えられます。実際、起動時の出力を見てみると以下のようにASGI対応であることが表示されています。

service_1        | System check identified no issues (0 silenced).
service_1        | March 24, 2018 - 13:55:26
service_1        | Django version 2.0.3, using settings 'mysite.settings'
service_1        | Starting ASGI/Channels development server at http://0.0.0.0:3000/  <--    ASGI
service_1        | Quit the server with CONTROL-C.

通常時のDjangoのrunserverの出力は以下のようにASGIの文字はありません。

service_1        | System check identified no issues (0 silenced).
service_1        | March 25, 2018 - 14:31:33
service_1        | Django version 2.0.3, using settings 'mysite.settings'
service_1        | Starting development server at http://0.0.0.0:3000/
service_1        | Quit the server with CONTROL-C.

channelsINSTALLED_APPSに追加し、ASGI_APPLICATIONを設定した時点でrunserverがASGI開発ように置き換わっていることがわかります。なお、ASGI_APPLICATIONを定義せずにchannelsを追加するとrunserverがエラーで落ちるので注意します。

一旦まとめ

環境準備からChannelsの有効化までを簡単にまとめました。次回は実際にWebsocketでチャットルームを実装していくところまで進めていく予定です。