aiohttp で Web サーバを作っていて、HTTP リクエストを受けたら少し重い処理を走らせたい [^job] というとき、そのまま処理を走らせると HTTP レスポンスを返すのが遅くなってしまい、クライアント側でタイムアウトエラーとして処理されてしまうかもしれない。処理結果は返す必要がないとして、いったん HTTP レスポンスを返して HTTP 通信を終わらせて、そのあとで少し重い処理を実行したいときはどうすればいいか。
write_eof
する方法
write_eof
する方法この方法はよくないことがわかったので、追記を参照。
from asyncio import sleep
from aiohttp import web
async def handler(request):
response = web.Response()
await response.prepare(request)
await response.write_eof()
await sleep(1) # レスポンスを返したあとの処理
app = web.Application()
app.router.add_get('/', handler)
if __name__ == '__main__':
web.run_app(app)
Response オブジェクトの作成時に何も指定しなかった場合、HTTP ステータスコードは 200 が返る。
[2020.06 追記] ダメだった
上記方法だと、keep-alive で一定時間コネクションが維持される場合ではたまたま問題なく動いてしまうので気づかなかったが、基本的にうまく動かない。
レスポンスヘッダに Connection: close
を追加して keep-alive を無効にしてみると、 write_eof()
した時点で以降の非同期処理が実行されないことがわかる。
どうやらクライアントとのコネクションが切断された時点でリクエストハンドラの処理はそこで中止されてしまうようだ。(エラーメッセージも何も出ないので調べるのが大変だった)
参考: https://github.com/aio-libs/aiohttp/issues/4773
解決策としては、aiojobs を使った方が良さそう。
from asyncio import sleep
from aiohttp import web
import aiojobs.aiohttp as aiojobs
async def job():
"""レスポンスを返した後に実行したい処理"""
await sleep(1)
async def handler(request):
await aiojobs.spawn(request, job())
return web.Response(text='ok')
app = web.Application()
app.router.add_get('/', handler)
aiojobs.setup(app)
if __name__ == '__main__':
web.run_app(app)