明滅するプログラマの思索

WEBエンジニアとして勤務している一介の男が、日々気づいたことをまとめるブログです

HTTPレスポンスを返した後も処理を続ける方法

HTTPリクエストを受け取ったWEBサーバは、そのプロセスで処理をすべて実行した後にレスポンスを返却します。
この処理に時間がかかることが予想されるとき(たとえば30秒でタイムアウトするリクエストで40秒かかるとか)、プロセス内で別のプロセスを起動し、処理を委譲するよう実装することが多いです。
PHPであれば、以下のような感じです。

<?php
/*
 * 時間がかかる処理を別のプロセスで実行する
 */
system("/usr/bin/php other_process.php &");

echo 'OK';
exit;

上記では、「時間のかかる処理」を other_process.php という別のスクリプトに記述し、それを system() で呼び出すことで対応しています。
この方法では、HTTPリクエストが大量に発生するような環境で運用した場合、リクエスト数分の別プロセスが起動することになり、ロードアベレージは高数値となるでしょうし、メモリも大量に消費することが予想されます。
また、最初のプロセスで何らかのパラメータを受け取っていたり、SESSIONを使った処理を続行したい場合、別プロセスにもパラメータや session_id() を引き渡す必要が出てきます。

別プロセスを呼び出さずに時間のかかる処理を実行する

以下のコードでは、HTTPレスポンスを先に返却し、プロセスを終了させずに処理を続行させることができます。

<?php
ignore_user_abort(true);       // ブラウザから切断されても処理を中断しないようにする
set_time_limit(0);             // 処理時間を無制限にする
$response = 'OK';              // レスポンスに含める文字列
$length = strlen($response );

ob_start();                    // 出力をバッファにためる
echo $response ;

header("Content-Length: $length");
header("Connection: close");   // HTTP接続を切る
ob_end_flush();
ob_flush();
flush();                       // 溜めてあった出力を解放しフラッシュする


/* ここから先には切断後の処理を記述
・・・
・・・
  1. レスポンスBodyを出力バッファに溜める
  2. header() で Connection: close を指定し、Connectionを強制的に切断する
  3. ob_flush() と flush() で出力を開放する(接続が切れる)
  4. その後に時間のかかる処理を記述

となります。

この方法ならプロセスの増大を防げますし、リクエストパラメータや SESSION 情報もそのまま使うことができます。