BackgroundTasks¶
Note
FastAPI の BackgroundTasks は、 Starlette の BackgroundTasks を継承しただけの薄いラッパー。「レスポンス後にタスクを実行する」という実体は Starlette 側にある。
add_task も super().add_task(...) を呼んでるだけで、振る舞いは変えてない。 FastAPI が足してるのは引数の型ヒントと docstring くらいで、 from fastapi import BackgroundTasks で使えるようにしてくれてるという感じ。
BackgroundTasks を使うと、レスポンスを返した後に実行する処理を登録できる。
時間のかかる処理をクライアントに待たせたくないとき(メール送信など)や、レスポンスを返し終わってからじゃないとできない後始末(一時ファイルの削除など)に向いてる。
cf. https://fastapi.tiangolo.com/tutorial/background-tasks/
使い方はシンプルで、パスオペレーション関数の引数に background_tasks: BackgroundTasks を書いて、 add_task で「呼びたい関数」と「その引数」を登録するだけ。登録された関数はレスポンスが返った後に実行される。
メール送信を待たせない¶
例えば、 SMTP での送信は時間がかかるので、これをリクエストの中で同期的にやるとクライアントが送信完了まで待たされてしまう。 add_task でバックグラウンドに回せば、レスポンスはすぐ返して、メールはその後で送れる。
下は、ユーザーに通知メールを送るエンドポイント。
import logging
import emails
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
logger = logging.getLogger(__name__)
def send_email(email_to: str, subject: str, body: str) -> None:
message = emails.Message(
subject=subject,
html=body,
mail_from=("noreply", "noreply@example.com"),
)
response = message.send(to=email_to, smtp={"host": "mail.example.com", "port": 25})
logger.info(f"send email result: {response}")
@app.post("/notify/{email}")
def notify(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(
send_email,
email_to=email,
subject="Notification",
body="お知らせしたい内容がたくさんあります",
)
return {"message": "Notification will be sent"}
add_task(send_email, ...) で send_email を後回しにしてる。第 1 引数が呼びたい関数( send_email )、それ以降が send_email に渡すキーワード引数。
send_email の中で SMTP 送信が終わるのを待たずに、 return がすぐ実行される。クライアントには「送るよ」というレスポンスを先に返して、実際の送信はその後でやる。
Info
もしデータベースに、該当のメールアドレスを持つユーザーがいるか確認してからメールを送信するようにしているときは、セキュリティの観点からも BackgroundTasks を使うべき。
メール送信を同期的にやってしまうと、「ユーザーがいる → 送信する → レスポンスが遅い」「ユーザーがいない → 送信しない → レスポンスが速い」というレスポンス時間の差が生まれる。攻撃者はこの差を測ることで、どのメールアドレスが登録済みかを推測できてしまう(タイミング攻撃によるメール列挙)。
BackgroundTasks で送信をレスポンスの後ろに回せば、ユーザーの有無にかかわらずレスポンスはすぐ返るので、時間差から登録状況を読まれにくくなる。あわせて、レスポンスのメッセージもユーザーの有無で変えないようにしておくと、より読まれにくい。
OWASP Forgot Password Cheat Sheet には、
Ensure that the time taken for the user response message is uniform.
と書かれていて、レスポンスにかかる時間を一定にすることが推奨されてる。
アカウント新規登録時の確認メールやパスワードリセットの通知など、メールを送る箇所では同じパターンが使える。 パスワードリセット機能を実装する場合は、 OWASP Forgot Password Cheat Sheet は確認しておくべき。
注意点¶
- バックグラウンドの処理で例外が出ても、レスポンスはもう返した後なのでクライアントには伝わらない。ログを残すなどして気づけるようにしておく。