10.1. 最初のエンドポイント

カメラが何か面白いことをする前に、まずネットワーク上の他の機器がカメラに到達できる必要があります。サーバーが生きていることを証明する最も手軽な方法は、JSONを返すルートが1つだけのHTTPエンドポイントです。

from microdot import Microdot

app = Microdot()

frame_count = 0
trigger_count = 0

@app.get('/status')
async def status(request):
    return {'frames': frame_count, 'triggers': trigger_count}

app.run(host='0.0.0.0', port=80)

IDEで実行します。LAN上の他のマシンから http://<cam-ip>/status を開いてください。ブラウザには次のように表示されます:

{"frames": 0, "triggers": 0}

カウンターはプレースホルダーで、まだ何も書き込んでいませんが、リクエストはネットワークを越え、カメラがそれをルーティングし、ハンドラーを実行してJSONを返しました。

10.1.1. 各行の役割

スクリプトごとに microdot.Microdot インスタンスが1つです。このインスタンスがルーティングテーブル、エラーハンドラー、ライフサイクル(start、serve、stop)を所有します。大規模なアプリは複数のPythonモジュールに分割されますが、それでも1つの app オブジェクトを共有します。

@app.get('/status') はルートデコレーターです。ここでは microdot.Microdot.get() だけを使っています。post()put()delete() は、カメラが書き込みを受け付け始める後のページで登場します。

すべてのルートハンドラーはasyncioコルーチンであり、最初の引数としてリクエストを受け取ります。ハンドラーは request を使う必要はなく、このハンドラーも無視していますが、シグネチャを統一するためにパラメーターは常に存在します。

dictを返すのがJSONを送る最も短い方法です。Microdotはdictを自動的にJSONにシリアライズし、レスポンスに Content-Type: application/json を設定します。文字列を返すと text/plain が送られます。microdot.Response を明示的に返すのが冗長な書き方で、ボディがバイナリの場合やレスポンスにカスタムヘッダーが必要な場合に使います。

app.run(host='0.0.0.0', port=80) はサーバーを起動します。0.0.0.0カメラが持つすべてのインターフェースで待ち受ける という意味で、有線イーサネットとwifi STAの両方が稼働していれば両方で待ち受けます。ポート80はHTTPのデフォルトなので、ブラウザはポート番号を入力する必要がありません。

10.1.2. 1つのリクエストを端から端まで

スマートフォンはカメラへのTCP接続を開いてHTTP リクエストを送り、カメラはそれを解析、ルーティングし、 ハンドラーを実行してレスポンスを返します。

スマートフォンはTCP接続を開き、リクエストラインとヘッダーを書き込んで待ちます。カメラはソケットからバイトを読み取り、それらを microdot.Request オブジェクトに解析し、パスとメソッドをルーティングテーブルと照合し、ハンドラーコルーチンをawaitし、返されたものをシリアライズし、ステータスライン、ヘッダー、ボディをソケットに書き戻してから、接続を閉じる(HTTP/1.0のデフォルト)か再利用します(Connection: keep-alive を伴うHTTP/1.1)。やり取り全体にかかる時間は、ネットワークの往復時間にハンドラーの処理時間を加えた程度です。

10.1.3. ブロッキングに関する注意

run()ブロッキング です。サーバーが停止するまで戻りません。単一目的のサーバーならそれで問題ありません。フレームのキャプチャや他のコルーチンも実行するアプリは、代わりに asyncio.run() の中で start_server() を使い、HTTPサーバーが他のすべてとループを共有できるようにします。

アプリは1つのURLに応答します。