pythonでサクッとwebサービス作るならFlaskよりsanicがいいよって話

## イントロ

```
           ▄▄▄▄▄
        ▀▀▀██████▄▄▄       _______________
      ▄▄▄▄▄  █████████▄  /                 \
     ▀▀▀▀█████▌ ▀▐▄ ▀▐█ |   Gotta go fast!  |
   ▀▀█████▄▄ ▀██████▄██ | _________________/
   ▀▄▄▄▄▄  ▀▀█▄▀█════█▀ |/
        ▀▀▀▄  ▀▀███ ▀       ▄▄
     ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌
   ██▀▄▄▄██▀▄███▀ ▀▀████      ▄██
▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███     ▌▄▄▀
▌    ▐▀████▐███▒▒▒▒▒▐██▌
▀▄▄▄▄▀   ▀▀████▒▒▒▒▄██▀
          ▀▀█████████▀
        ▄▄██▀██████▀█
      ▄██▀     ▀▀▀  █
     ▄█             ▐▌
 ▄▄▄▄█▌              ▀█▄▄▄▄▀▀▄
▌     ▐                ▀▀▄▄▄▀
 ▀▀▄▄▀
```

そもそもPythonでwebアプリをつくろうという選択肢が辛いですが、それでもPythonでやりたいんだ！という人向けに書きます。

あとsanicの日本語記事が少ないのでTOしていきたい。


## sanicってなに

![image.png](https://qiita-image-store.s3.amazonaws.com/0/45617/111fe66f-f76c-c5c4-843c-7f46a6ffcd68.png)



>Sanic is a Flask-like Python 3.5+ web server that's written to go fast. It's based on the work done by the amazing folks at magicstack, and was inspired by this article.

>On top of being Flask-like, Sanic supports async request handlers. This means you can use the new shiny async/await syntax from Python 3.5, making your code non-blocking and speedy.

>

https://github.com/channelcat/sanic

意訳をすると

1. Flaskライクだよ！
1. uvloop使って高速で動くwebサーバーだよ！
1. Python3.5+だよ！

ということです。(多分)

## Flaskじゃいけないの？

Flaskでもuvloopを使って高速化することはできます。
が、別途ライブラリ入れたりしなくて面倒ではあります。
(そもそもFlask自体多機能化はせずにシンプルに実装して必要に応じでライブラリを入れるという設計理念があったはず)
依存関係やらなにやら色々めんどうなことはできるだけ関わりたくないので標準ではいっているsanicを使えば非同期で高速な処理を行えつつ、Flaskライクで学習コストが低いのが魅力です。

性能がどれくらいすごいのかというのはすでに検証している方がいらっしゃるので画像を引用させていただきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/45617/e53f0ea6-04f5-ecae-288c-a590936a54b1.png)


>[PythonのWebアプリケーションフレームワーク Sanicを試す « Rest Term](https://rest-term.com/archives/3438/)

細かい数値はリンク先を参照してください。
並列処理の高負荷だとsanicじゃなくてuvloopすげーとなりますが、結局はサクッと作ってさっとデプロイするとなるとSanicの方が良いのかなと思います。

メリットばっかり言ってもフェアじゃないのでデメリットとして以下の点があります(Flaskと比べて)

1. 実績が少ない
2. 日本語ドキュメントが無い
3. 結局Flaskに戻る

1は歴史的に浅いので仕方がないの一言に終わります。

2に関しては公式ドキュメントを読めや！英語読めや！でも良いんですが、初学者が触るには日本語ドキュメントがあったほうが入り口が広がって良いことですし、母国語が日本語なので日本語で読んだほうが翻訳リソースを割かなくて嬉しい。

3に関してはコミュニティがFlaskに比べて小さいのであれもやろう、これもやろうとなったときに詰むのでなんだかんだでFlaskに戻ってしまうということがあります。webをPythonでやろうと思ってるpythonianなら自前で実装してしまうことが多いでしょうが、やはりそこがボトルネックになるのは辛いものがあります

誤解を生まないようにここで述べておきますが、Githubのstar数はFlaskの方が圧倒的に上ですが、(僕が勝手に思っているだけですが)アクティブなコミュニティ活性度の指標となるOpenIssueは俄然sanicの方が多いです。

## Hello World

簡単なHello WorldはQiitaの記事を貼って終わりにしたかったんですが、一個もないので書きます。

### install

`pyenv`で環境を離しておいた方がいいですよ。
そのあたりはこの記事の本質ではないので貼って終わりにします。
注意点は最初に書きましたがPythonのバージョンは3.5+にしてください。

[Pyenvの使い方 - Qiita](https://qiita.com/mogom625/items/b1b673f530a05ec6b423)
[pyenvのインストール、使い方、pythonのバージョン切り替えできない時の対処法 - Qiita](https://qiita.com/koooooo/items/b21d87ffe2b56d0c589b)

準備ができたらsanicのインストールです

`pip install sanic`

以上です。


### 表示させてみる

READMEのまんまをコピペします。名前はなんでも良いんですが、ここでは`main.py`とでもしておきましょう

```python:main.py
from sanic import Sanic
from sanic.response import json

app = Sanic()

@app.route('/')
async def test(request):
    return json({'hello': 'world'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)
```

できたら早速動かしてみましょう

```
python main.py
```

http://localhost:8000

<img width="229" alt="スクリーンショット 2018-08-07 15.03.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/45617/6b02998b-592d-7a36-f7a0-2375b8f28c93.png">

Ctr+cでサーバーが止まります

以上です。

## 各パーツの説明

Hello Worldだけだと寂しいので各パーツの説明をしていきます。

かといって全部説明するのは難しいので適時該当箇所の公式ドキュメントのURLを貼っておくので読んでください。

### import

```python:main.py
from sanic import Sanic
from sanic.response import json
```

Sanicをimportしています。
import fromがわからない人むけに説明すると、pythonでライブラリを使うときは `import ライブラリ名`となります。

じゃぁfromって何だよ。となるので `from ライブラリ名 import 関数名` と返します。

上記の例だと `from sanic import Sanic` だとsanicライブラリのSanicを読み込んでいる。(それはそう)
二段目は`sanic`の中にある`response`クラスを呼び出して、その中の`json`という関数を呼び出している。
具体的にいうとGithubでいうところの↓を呼び出している。
https://github.com/channelcat/sanic/blob/master/sanic/response.py

あれ？ここの挙動おかしいぞ？となったらソースコードを眺めてみよう。呼び出されている関数がどういう挙動をしているか理解できればその疑問も解決するはず。

### インスタンス

```python:main.py
app = Sanic()
```

インスタンスってなに？と言われると時間が無限にたりないので各自ググってください。
雑でもいいなら"importしたSanicをappに入れている"という理解で3割ぐらい合ってます


### デコレーター

```python:main.py
@app.route('/')
```

なかなか見慣れない＠ですが、これらはデコレーターというものです。
デコレーターに関しては記事を貼って終わりにします。
[Pythonのデコレータについて - Qiita](https://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e)
[Pythonのデコレータを理解するための12Step - Qiita](https://qiita.com/_rdtr/items/d3bc1a8d4b7eb375c368)

### Routing

雑で良いなら、括弧で囲まれた場所がRoutingを指しているというところだけ理解できればなんとかなります。
例えばさっきのmain.pyを以下のように書き換えてみます。

```python:main.py
from sanic import Sanic
from sanic.response import json

app = Sanic()

@app.route('/')
async def test(request):
    return json({'hello': 'world'})

@app.route('/api/')
async def apiindex(request):
    return json({'API': 'Index'})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)
```

先程のように `python main.py` を行い http://localhost:8000 にアクセスしてみましょう。

先ほどと同じく `{"hello":"world"}` が出たと思いますが、 次は　http://localhost:8000/api にアクセスしてみましょう

<img width="244" alt="スクリーンショット 2018-08-07 15.34.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/45617/8e19d1cc-64c5-88e2-c7d1-69f2ad887e97.png">

`@app.route('/api/')`の真下に書かれた`json({'API': 'Index'})`が返ってきてきます。

このように`@app.route()`の括弧の部分をURIとして指定してあげればそれに応じたリクエスが返ってきます。


https://sanic.readthedocs.io/en/latest/sanic/routing.html

#### 動的なRouting

数ページの静的なページだったらこれで問題ないのですが、動的なRoutingの必要が出たときにその分の@app.route()を作るのはしんどいので変数に入れてあげましょう。

```python:main.py
from sanic import Sanic
from sanic.response import json
from sanic.response import text

app = Sanic()

@app.route('/')
async def test(request):
    return json({'hello': 'world'})

@app.route('/api/')
async def apiindex(request):
    return json({'API': 'Index'})

@app.route('/api/<path>')
async def path_handler(request, path):
    return text('path = ' + path)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)
```

しれっと`from sanic.response import text`が加わってますが後ほど説明します

大きな追加点は

```python:main.py
@app.route('/api/<path>')
async def path_handler(request, path):
    return text('path = ' + path)
```

で、見たとおりなんですが`('')`内において、`<>`でくくる事によってその囲った部分が変数となります。

<img width="292" alt="スクリーンショット 2018-08-07 15.51.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/45617/d36c578b-6a51-1df9-db62-a5a4ca1f0398.png">

https://sanic.readthedocs.io/en/latest/sanic/routing.html#request-parameters

### POSTや型

型っぽいのを宣言できたり、POSTを受け取るときもこの部分を編集します。

```python:integer
@app.route('/number/<integer_arg:int>')
async def integer_handler(request, integer_arg):
    return text('Integer - {}'.format(integer_arg))
```


```python:Post
@app.post('/post')
async def post_handler(request):
    return text('POST request - {}'.format(request.json))
```
https://sanic.readthedocs.io/en/latest/sanic/routing.html#http-request-types

### static file
これだけあれば大抵のことができそうですが、少し問題点が。

現状、URLと返すリソースが1対1となっています。

webサイトを作ろうとした時CSSやjsを書くと思うのですが、このままだとcssやjsまで1回ずつ書かないといけなくなってしまいます。

そこでstaticなファイルはフォルダでまとめて置いておく機能があるので利用しましょう。

以下のようなフォルダ構成を想定します

```
.
├── index.html
├── main.py
└── static
    ├── css
    └── js
        └── main.js
```


雑に`app.static('/static', './static')`を貼るだけで返したhtmlに

```html
<script type="text/javascript" src="/static/js/main.js"></script>
```

と記述すると読み込んでくれます。

https://sanic.readthedocs.io/en/latest/sanic/static_files.html
https://github.com/channelcat/sanic/blob/master/sanic/static.py


### WebSocket

力が尽きたのでパスします

https://sanic.readthedocs.io/en/latest/sanic/routing.html#websocket-routes
https://github.com/channelcat/sanic/blob/master/sanic/websocket.py


## Response

ようやくサーバーに問い合わせて返ってくる内容について触れます。
富士山で言うなら五合目あたりでしょうか。

main.pyのこの部分

```python:main.py
return json({'hello': 'world'})
```

sonicでは以下の8つのfile responseをサポートしています


[Plain Text](https://sanic.readthedocs.io/en/latest/sanic/response.html#plain-text)
[HTML](https://sanic.readthedocs.io/en/latest/sanic/response.html#html)
[JSON](https://sanic.readthedocs.io/en/latest/sanic/response.html#json)
[File](https://sanic.readthedocs.io/en/latest/sanic/response.html#file)
[Streaming](https://sanic.readthedocs.io/en/latest/sanic/response.html#streaming)
[Redirect](https://sanic.readthedocs.io/en/latest/sanic/response.html#redirect)
[Raw](https://sanic.readthedocs.io/en/latest/sanic/response.html#raw)

公式ドキュメントには

```
from sanic import response


@app.route('/text')
def handle_request(request):
    return response.text('Hello world!')
```

とありますが、個人的には

```
from sanic.response import text


@app.route('/text')
def handle_request(request):
    return text('Hello world!')
```

の方がキレイかなと思います。(やっている動作自体に変わりはないです)

https://github.com/channelcat/sanic/blob/master/sanic/response.py#L116

jsonに関してjson文字列を`response.json()`に入れるとダブルクォーテーションの前にバックスラッシュが入るバグっぽいのが手元で起こって泣く泣くtextで返したりしています。


## Deploy

```
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)
```

はい、ここです。

`if __name__ == '__main__':`ってなんですか

[[Python] if \_\_name__ == '\_\_main__' について理解しよう - Qiita](https://qiita.com/wslife/items/edf7a2309f3421723550)
[29.4. \_\_main__ — トップレベルのスクリプト環境 — Python 3.6.5 ドキュメント](https://docs.python.jp/3/library/__main__.html)

要はこの`main.py`が実行されたときにしか呼ばれませんよ〜ということです。

`app.run()`
こいつがメインなんですが、オプションは以下のとおりです

|オプション|デフォルト値|説明|
|:-:|:-:|:-:|
|host|"127.0.0.1"| サーバー名|
|port|8000| ポート番号|
|debug|False|デバッグ出力の可否|
|ssl|None|SSL対応させるか|
|sock|None|websocketの受け入れ|
|workers|1|動かすワーカー数|
|loop|None|asyncioでループ処理させるか|
|protocol|HttpProtocol|プロトコル|

https://sanic.readthedocs.io/en/latest/sanic/deploying.html
https://github.com/channelcat/sanic/blob/master/sanic/app.py#L651

## サンプル集とか

公式のGithubにいっぱいサンプルがあるのでそれっぽいやつを眺めてみると良いのではないでしょうか

https://github.com/channelcat/sanic/tree/master/examples
https://github.com/channelcat/sanic/wiki/Extensions
https://github.com/channelcat/sanic/wiki/Examples

## まとめ

Flaskを触ったことある人ならお気づきですが大体Flaskと同じです。

個人的には初心者はある程度ナウい機能がデフォで入ってるsanicを使って限界を感じたらFlaskに行けばいいじゃないんですかね。

あと好きなsonicは、東京エンカウントで悠木碧さんが好きなゲームを聞かれて答えるモノマネをした杉田智和の「そ゛に゛っ゛く゛ー゛」です。

次回はsanicのテンプレート(Jinja2)で動的ページ作ったり、sanic × vue.jsでイケイケ爆速SPA作ったりする記事を書ければなと思います。

最後にREADMEから注意すべき部分を引用して終わります

> ## TODO
> http2
> ## Limitations
> No wheels for uvloop and httptools on Windows :(