Express はレスポンスを返してからも処理が続行できる

Express において、res.json() とか res.send() とかを呼んだ後も処理が継続できることを知った。リクエストに対するレスポンスの用意ができたら迅速に res.send() を呼んでレスポンスしてしまい、残処理はそのあとゆっくりやれる、というワケだ。てっきり res.send() とかしたらそのプロセスは終了してしまって後続の処理は動かないものと思っていたが、非同期処理でもきちんと動くし、問題なさげだった。

経緯としては、LINE Messaging API からメッセージを受け取る Webhook サーバを作った時に、迅速にレスポンスを返す必要があって調べていた。

LINE developersから、件名 : 「[LINE developers] BusinessConnect: Error detected」で送られていくる request_timeout - (Request timeout) は、ボットアプリのサーバからレスポンスが1秒以内に返されなかった場合に通知されます。

上のサイトにも書かれているとおり、Webhook サーバは1秒以内にレスポンスを返さないと、LINE Developers からエラー検知のメールが飛んでしまうのだ。

これまでは、

  1. リクエストの署名などを検証する
    • 不正なリクエストの場合は 400 を返して終了
  2. 受け取ったメッセージは「応答処理用の別サーバ」に流す (ちゃんとメッセージを受け取れたことを検知したくて別サーバのレスポンスを待っていた)
  3. メッセージの受け流しが終わったら res.send() を実行し終了

という順番で処理していたが、これだと「応答処理用の別サーバ」との通信が完了するのを待ってからレスポンスするため、1秒近くかかることもあった。

しかし、LINE からのリクエストに対しては、「メッセージを別サーバに横流しできたか」とか「メッセージに対して返信したかどうか」とかいった「処理結果」は一切関係がない。LINE に 400 などをレスポンスしたところで通信リトライしてくれるワケでもないので、メッセージの横流し処理が終了したかどうかは待たなくて良いのだ。

というワケで、

  1. リクエストの署名などを検証する
    • 不正なリクエストの場合は 400 を返して終了
  2. 正常なリクエストを受け取れたので res.send() を実行する
  3. 受け取ったメッセージを「応答処理用の別サーバ」に流す

という処理順に変更し、リクエスト受信から0.1秒以内に LINE サーバにレスポンスすることにした。

もっといえば、別にリクエストヘッダの署名が誤っている時に 400 とかを返したところで、LINE が再度メッセージを送ってくれたりするワケではないので、

  1. とりあえずリクエストが来たから res.send() を実行する
  2. リクエストの署名などを検証する
    • 不正なリクエストの場合は異常終了とする
  3. 受け取ったメッセージを「応答処理用の別サーバ」に流す

という処理にしてしまっても良いかもしれない。

よくあるリクエスト・レスポンスの関係とは違い、リクエスト元に何のデータも返す必要がない「Webhook」な作りなので、こういった処理にしてしまって問題がないのだ。