転職黙示録 (10) FastAPIのソースを読む 第4回 UvicornとFastAPIの関係

# Uvicoronの日

## 対象コード

 とりあえずHello World的なプログラムを走らせてみる.

```python:main.py
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello World"}

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

## 目的

 サーバーであるUvicornとアプリケーションのFastAPIの関係を考察する.

##Q. appとは何か?

　最初にUvicornが期待するアプリケーションの仕様, つまり[ASGIインターフェース](https://www.uvicorn.org/#the-asgi-interface)とはどんなものか確認しておきましょう. 

* scop
* receive
* send

　ASGIに適合するアプリケーションはこれらの引数を取るcallableなネイティブ・コルーティンであれば良いようです. [Quick Start](https://www.uvicorn.org/#quickstart)にはこんなコードがあります.

```python
async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ]
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })
```

 これがどこかにあるかを探すわけです.

##Q. Uvicornサーバーの起動

　Uvicoronを実行するとServerクラスのrunメソッドをrun関数か呼び出されて起動します.

```python:uvicorn/main.py
def run(app, **kwargs):
    ...
        server.run()
```

 内部ではイベント・ループを始動しています.

```python:uvicorn/main.py
def run(self, sockets=None, shutdown_servers=True):
    self.config.setup_event_loop()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(self.serve(sockets=sockets))
```

　実はconfigのsetup_event_loopでは[uvloop](https://github.com/MagicStack/uvloop)というasyncioとは違うイベント・ループのモジュールが読み込まれています. 前回紹介したポリシーを入れ替えることで実現しているようです. 提供されている機能にはちゃんと使途があるんだなと実感できます.

```python:uvicorn/loops/uvloop.py
def uvloop_setup():
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
```

　後は同じでイベント・ループを起動してrun_until_completeでサーバーを非同期に実行します. serveメソッドでは以下のメソッドを呼び出ししています.

* startup
* main_loop
* shutdown

　startupも重要そうですが, main_loopが本題です. main_loopでリクエストを受け待ち処理します. デバッガーで実行するとクライアントからリクエストを送らないと延々にループを繰り返しますが, 試しにブラウザからアクセスすると[HttpToolsProtocol](https://github.com/encode/uvicorn/blob/15c4e9cfe2d996bd73ed6b542e44160f620b9519/uvicorn/protocols/http/httptools_impl.py#L77)に飛ばされます. 

###Q. HttpToolsProtocolとは?

　 この辺はUvicornというよりasyncioの話と関係するようです.

　何れにしてもこのプロトコルのメソッドが呼ばれるんですが[on_headers_complete](https://github.com/encode/uvicorn/blob/15c4e9cfe2d996bd73ed6b542e44160f620b9519/uvicorn/protocols/http/httptools_impl.py#L228)というメソッドでアプリケーションが実行されたりします.

```python:uvicorn/protocols/http/httptools_impl.py
task = self.loop.create_task(self.cycle.run_asgi(app))
task.add_done_callback(self.tasks.discard)
self.tasks.add(task)
```

###Q. run_asgiとは

　[run_asgi](https://github.com/encode/uvicorn/blob/15c4e9cfe2d996bd73ed6b542e44160f620b9519/uvicorn/protocols/http/h11_impl.py#L373)はタスクとしてスケジューリングされます. つまり一回呼び出し元に戻るわけです. それはどこかというとコールスタックを見るとrun_until_completになります. コールスタックの流れは以下のようになります.

* uvicorn.run(app, host="0.0.0.0", port=8000)
* server.run()
* loop.run_until_complete(self.serve(sockets=sockets))
* run_asgi

　こうしてみると分かりやすいですね:relaxed:
　
　appはFastAPIのインスタンスです. つまりrun_asgiというrunnerコルーティンがFastAPIアプリケーションをイベント・ループを使ってスケジュール実行していることになります. run_asgiは[RequestResponseCycle](https://github.com/encode/uvicorn/blob/15c4e9cfe2d996bd73ed6b542e44160f620b9519/uvicorn/protocols/http/h11_impl.py#L336)というクラスのメソッドでUvicornが提供するモジュールです. RequestResponseCycleは以下のようなメソッドを持ちます.

* run_asgi
* send
* receive

　イニシャライザではscopeをもらっていますので, このクラス自身がASGIインターフェースに必要なデータと処理を一通り持っています. 実際run_asgiでは以下のようにappとのやりとりを開始しますが, その際に自身のメソッドを渡しています. 

```python:uvicorn/protocols/http/h11_impl.py
result = await app(self.scope, self.receive, self.send)
```

　ファイル名からこれがhttp1.1サーバーの実装であることも伺えます. これでとりあえずUvicornサーバーとFastAPI(Starlett)アプリケーションの関係が見えてきました.

###Q. Router/APIRouterとは?

　ここからはFastAPIやStarletteといったアプリケーションの話ですがせっかくだから追いかけて見ます. このコルーティンの呼び出しを辿ってみましょう.

* Starlette
* ServerErrorMiddleware
* ExceptionMiddleware
* Router/APIRouter

　途中例外処理のミドルウェアを挟んでいますが面白いことに[Router](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/routing.py#L486)を継承したクラスもASGIのインターフェースをもつ[callble](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/routing.py#L578)として呼び出し可能になっていることです.

 いよいよルートとスコープの中にある[パスをマッチ](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/routing.py#L586)します. scopeは上から渡されたデータでrouteはルーターが保持しているのでした.

```python
for route in self.routes:
    match, child_scope = route.matches(scope)
    if match == Match.FULL:
        scope.update(child_scope)
        await route(scope, receive, send) # (*)
        return
    elif match == Match.PARTIAL and partial is None:
        partial = route
        partial_scope = child_scope
```

　フルパスがマッチした場合はアスタリスクで示したrouteが呼び出されます. つまりRouteもASGIなcallableのようです.

###Q. Routeがアプリケーションを持つのはなぜか?

　コルーティンチェーンはまだ終わりではなくもう一回同様の処理が続きます.

```python:starlette/routing.py
await self.app(scope, receive, send)
```
　このappは[request_response関数](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/routing.py#L31)の内部で定義された[app関数](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/routing.py#L38)です. この関数のコメントによるとASGIアプリケーションのインスタンスとはrequest_response関数が返すコルーティンとして定義されているようです.

```python
Takes a function or coroutine `func(request) -> response`,
and returns an ASGI application.
```

　コルーティンだった場合は高階コルーティンということになります. 個人的になぜこの[appインスタンス](https://github.com/tiangolo/fastapi/blob/f803c77515662cd2382674d9fc8df1e6641b3ba7/fastapi/routing.py#L294)をルーターが持っているのかがちょっと引っかかりました.

　request_responseの引数であるfuncは[get_app関数](https://github.com/tiangolo/fastapi/blob/f803c77515662cd2382674d9fc8df1e6641b3ba7/fastapi/routing.py#L68)が返す[appコルーティン](https://github.com/tiangolo/fastapi/blob/f803c77515662cd2382674d9fc8df1e6641b3ba7/fastapi/routing.py#L84)です.

　コメントの通りリクエストを受け取ってレスポンスを返すというHTTPサーバーの基本的な処理を担っているようです. 実際のリクエストを処理しているのはこのRouteがもつappで, Routeはこのオブジェクトに処理を委譲するという考え方をのようです.

　でこいつは[別スレッドで実行](https://github.com/tiangolo/fastapi/blob/f803c77515662cd2382674d9fc8df1e6641b3ba7/fastapi/routing.py#L113)されるようです.

```python
raw_response = await run_in_threadpool(dependant.call, **values)
```

 [run_in_threadpool](https://github.com/encode/starlette/blob/65b280fcb590c69d0ed44b8452522b5277a19e7a/starlette/concurrency.py#L12)はstarletteが提供する関数です. 内部では[run_in_executor](https://docs.python.org/3/library/asyncio-eventloop.html)で処理を別スレッドに投げるようです. いわゆるワーカーというやつでしょうか. イベント・ループが進むとraw_messageには以下のようなメッセージが格納されます.

![Screenshot 2019-10-03 at 9.56.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362766/99f5afe9-1308-f1d8-8e50-d86e1536f8cc.png)

## 結論

　Uvicornのドキュメントにあるように基本的にasync def app(scope, receive, send)というシグニチャをもつコルーティンを呼び出すわけですが実際のアプリはミドルウェアを挟んでいたり階層状になっています. この辺は一種のコールバックを入れ子にしてるのと同じ構造という感じはしますが, デバッガーは必須でしょうが処理が分割されているので読みやすいですね. 何れにしても呼び出しの一番下はルートで, そこで別スレッドでリクエストの処理を担うアプリケーションが実行されるようです.

## 分からなかったところ:no_good:

### HttpToolsProtocolはどこで生まれるのか?

 HttpToolsProtocolのイニシャライザが呼ばれるんですがそれがどこか分からなかったです.

### クライアントからの要求の厳密な流れ

 上の疑問ともつながりますがクライアントからリクエストが渡されるわけですが, ここの流れがふんわりとして理解できません. なんとなくブラウザを開いてデバッガーを進めるとリクエストを処理するコルーティンが実行されるという感じでスッキリしません. Serverクラスの[startupメソッド](https://github.com/encode/uvicorn/blob/b2e34580b35702deaabc8b6dc7d11b286049810a/uvicorn/main.py#L329)でその辺がごにょごにょされているようなんですがよく分かりませんでした.

### 別スレッドで実行される処理とは?

　上で示したrun_in_threadpoolに渡されるコールバックが謎です. dependantとあるのですがよく分かりませんでした.