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接続を開き、リクエストラインとヘッダーを書き込んで待ちます。カメラはソケットからバイトを読み取り、それらを microdot.Request オブジェクトに解析し、パスとメソッドをルーティングテーブルと照合し、ハンドラーコルーチンをawaitし、返されたものをシリアライズし、ステータスライン、ヘッダー、ボディをソケットに書き戻してから、接続を閉じる(HTTP/1.0のデフォルト)か再利用します(Connection: keep-alive を伴うHTTP/1.1)。やり取り全体にかかる時間は、ネットワークの往復時間にハンドラーの処理時間を加えた程度です。
10.1.3. ブロッキングに関する注意¶
run() は ブロッキング です。サーバーが停止するまで戻りません。単一目的のサーバーならそれで問題ありません。フレームのキャプチャや他のコルーチンも実行するアプリは、代わりに asyncio.run() の中で start_server() を使い、HTTPサーバーが他のすべてとループを共有できるようにします。
アプリは1つのURLに応答します。