非同期なタスクが作成された場合の話です。
タスクを受け取った web server が 202 を返して、クライアントは 202 を以って処理を完了する(タスクの終了までを見守らない)パターンって結構あると思います。
今回は上記のパターンではなく、"クライアントからタスクが完了するまでを見守り続けること"を FastAPI の BackgroundTasks と Azure Logic Apps で検証して、Logic Apps のタイムアウト時間制約を超えて見守り続けることができたことに関して書きます。
前提: Azure Logic Apps の HTTP リクエストのタイムアウト時間に関して
マルチテナントだと 2 分でタイムアウトします。
https://learn.microsoft.com/ja-jp/azure/logic-apps/logic-apps-limits-and-config?tabs=azure-portal#timeout-duration
タイムアウト時間を超えて見守る"ポーリング アクション パターン"
詳しくは このlearn を見て欲しいのですが、要点は
- クライアントからの HTTP リクエストの設定で非同期設定を ON にする
- タスクを受け取った際、web server から 202 のステータスコードと "タスクのステータスが分かるURL" を返させる
- クライアント は "タスクのステータスが分かるURL" がステータスコードを200を返すまでポーリングし続ける
- "タスクのステータスが分かるURL" が 200 を返すと、Azure Logic Apps の HTTP リクエストの結果も成功になる
です。
web server (FastAPI) 側の実装は以下のようにしました。
BackgroundTasks の実装は こちらの記事 を流用させて頂いております
# import は省略します
class Job(BaseModel):
job_id: int
def __call__(self):
jobs[self.job_id] = self
try:
for _ in range(300):
sleep(1)
del jobs[self.job_id]
finally:
print("時間のかかる処理が終わりました")
jobs: dict[int, Job] = {}
@app.post("/api/{job_id}/", status_code=status.HTTP_202_ACCEPTED)
async def start(job_id: int, background_tasks: BackgroundTasks):
t = Job(job_id=job_id)
background_tasks.add_task(t)
return JSONResponse(
status_code=status.HTTP_202_ACCEPTED,
content={},
headers={"Location": f"https://example.com/api/{job_id}/"},
)
@app.get("/api/{job_id}/")
async def get_status(job_id: int):
if job_id in jobs:
return JSONResponse(
status_code=status.HTTP_202_ACCEPTED,
content={},
headers={"Location": f"https://example.com/api/{job_id}/"},
)
else:
return JSONResponse(status_code=status.HTTP_200_OK, content={"message": f"{job_id}は実行していません"})
Azure Logic Apps から HTTP リクエストを送ってみた結果
Azure Logic Apps から HTTP リクエストを送ってみた結果、 5分後に成功となりました。
余談
202 のステータスコードを受け取った場合、location ヘッダーのURLを見てポーリングを続ける のはRFCなどで決められた挙動なのかどうかが気になりました。調べてみると、RFCの202の仕様の解釈をめぐり、皆さん議論を重ねているみたいです