Django3.0のリリースを祝してWSGIの精神的後継であるASGIを試してみたがwebsocketは使えなかった


# 祝！Django 3.0 リリース！

2019/12/02 Django 3.0がリリースされました。
Django 1.0がリリースされたのが2008年、2.0のリリースが2017年12月です。そして今年の12/2に3.0がリリースされました。
1から2へのバージョンアップが約10年かけたのに対して、2から3まで2年足らずという驚きのスピード感です。それに比べてこの2年間、私はいったい何をしていたのでしょうか？それではDjango 3.0で個人的に気になった新機能を見てみたいと思います！


# ASGI supportとは

私はこれが気になりました。とは言ってもASGIというものを触ったことがなくて、これを公式サポートしました！と言われても、ほーん、という感じでした。でも俺にはWSGIさんがついているから・・・と。
しかし、調べてみるとどうやらASGIさんは無視できない存在っぽいようにだんだんと思えてきました。

[リリースノート](https://docs.djangoproject.com/en/dev/releases/3.0/)によると、現在、非同期機能はWSGIにはなくてASGIで実行された時のみサポートされているとあるし（でも将来的にはサポートするみたい？）。
[ASGIの公式](https://asgi.readthedocs.io/en/latest/)が言うには、「ASGIはWSGIのspiritual successor（[精神的続編](https://ja.wikipedia.org/wiki/%E7%B2%BE%E7%A5%9E%E7%9A%84%E7%B6%9A%E7%B7%A8)）」らしいですし。
こうしてみると私の強い味方に思えていたWSGIが、ASGIの下位互換なのではないかと思えてきました。

関係ないですけど、精神的続編って言葉、なんかカッコイイですね。ゼノギアスに対するゼノサーガ、ゴールデンアイ007に対するパーフェクトダーク等がそれに該当してくるみたいです。しかし、WSGIやASGIは別にゲームやフィクション作品って訳ではなく、続編という訳は果たして適しているだろうか？と思ったので精神的後継と読んでみようと思います。タイトルにもそう書きました。

# ASGIでDjangoを動かしてみる

そうと決まれば話は早いってことで、試しにDjangoをASGIで動かしてみました。Dockerで環境を作って。

```bash
sudo docker run --rm -it -p 8000:8000 python:3.8 bash
```
python3.8を使います。ところでDjango 3.0はpython 3.6、3.7、および3.8をサポートしています。


```bash
pip install django daphne
```

`django`と一緒に`daphne`もインストールします。`daphne`は`ASGI`用のサーバみたいですね。`WSGI`における`uWSGI`が`ASGI`における`daphne`なのでしょう。きっと。
ちなみにdaphneはダフネと呼ぶらしいです。djangoはジャンゴなのに、daphneのdはちゃんと発音してもらえていいですね。

```
django-admin startproject app
cd app
daphne -b 0.0.0.0 -p 8000 app.asgi:application
```

インストールしたらプロジェクトを作って早速動かしてみます。

# やった！動いた！完！

![django3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24415/ca88ed7f-cb80-7bf2-c6a3-6b1f1f95d5ef.png)

動いたけどこれだと何が嬉しいのかが分からない。まあ、当然ですよね。非同期機能が売りなのに同期通信しかしてないですし。

# Djangoで非同期通信が試せるチャットアプリを構築するようなチュートリアルとかはないだろうか・・・

ありました。
https://channels.readthedocs.io/en/latest/tutorial/index.html
Django channelsが使うとDjangでweb socketが使えるみたいでして、ご丁寧にチャットアプリのチュートリアルがあります。これを写経して作ったチャットアプリをdaphneで動かしてみよう、という魂胆です。
しかし、Django channelsと今回のASGIサポートの関係性がイマイチよく分かってないです。まあ、でも使っているうちに分かってくるかもしれないので深入りしないでおきます。

ここにチュートリアルに沿って作った完成品を[ここ](https://github.com/d9magai/django3-channels-tutorial-chat-application)に置いておきます。
ためしに動かしてみます。`command: python manage.py runserver 0.0.0.0:8000`の状態です。

```bash
sudo docker-compose up -d
```

2窓で動作確認しましたが、ちゃんとチャットが使えますね。

![mov2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24415/85496438-055e-7168-a36d-702b4ed261d2.gif)

では、これを`daphne`で動かしてみます。`command`を次のように書き換えます。

```diff
 services:
     app:
         build: .
-        command: python manage.py runserver 0.0.0.0:8000
+        command: daphne -b 0.0.0.0 -p 8000 mysite.asgi:application
         volumes:
             - .:/srv
         ports:
```

私の予想だとこれでも動くはずですが・・・。

```bash
sudo docker-compose down
sudo docker-compose up -d
```

![ws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24415/7545128b-bb4f-90b2-fc37-3ccd64258ead.png)
んー、動きませんね。

なにやらエラーメッセージを読むとwebsocketは使えないって書いてありますね。

```
172.17.0.1:52480 - - [06/Dec/2019:07:36:28] "GET /chat/a/" 200 1413
172.17.0.1:52488 - - [06/Dec/2019:07:36:28] "WSCONNECTING /ws/chat/a/" - -
2019-12-06 07:36:29,471 ERROR    Exception inside application: Django can only handle ASGI/HTTP connections, not websocket.
  File "/usr/local/lib/python3.8/site-packages/daphne/cli.py", line 30, in asgi
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/asgi.py", line 144, in __call__
    raise ValueError(
  Django can only handle ASGI/HTTP connections, not websocket.
172.17.0.1:52488 - - [06/Dec/2019:07:36:29] "WSDISCONNECT /ws/chat/a/" - -
```

asgi.pyの144行目付近を見てみましょう。

```python

    async def __call__(self, scope, receive, send):
        """
        Async entrypoint - parses the request and hands off to get_response.
        """
        # Serve only HTTP connections.
        # FIXME: Allow to override this.
        if scope['type'] != 'http':
            raise ValueError(
                'Django can only handle ASGI/HTTP connections, not %s.'
                % scope['type']
            )
```

HTTP connections以外だった時は例外を投げるようにしているみたいですね。これは一体どういうことでしょう。

* ASGI supportがまだweb socketに対応していない
* django channelsのチュートリアルのチャットアプリをDjango3.0を使って`daphne app.asgi:application`で動かそうとする考えがそもそも間違ってる

どっちかだと思ますが、良くはわかってません。謎は深まるばかりです。しかし、ASGIでDjangoを非同期対応できるというのは公式でも謳っていることではあるので、今後とも注視していこうと思います。

# おわり

さよならWSGIさん。俺にはASGIさんがいるから・・・