早期レスポンス

oxphp_finish_request() は、完全な HTTP レスポンスを即座にクライアントへ送信し、その後も PHP スクリプトをバックグラウンド処理のために実行し続けます。これは PHP-FPM の fastcgi_finish_request() に相当する OxPHP の機能です。

仕組み

  1. レスポンスを構築します。 スクリプトは通常どおりヘッダー、ステータスコードを設定し、レスポンスボディを出力します。
  2. リクエストを完了します。 oxphp_finish_request() を呼び出します。OxPHP はすべての出力バッファをフラッシュし、リクエストを完了済みとしてマークし、完全な HTTP レスポンスをクライアントへ配信します。
  3. バックグラウンド処理を実行します。 スクリプトはメール送信、キャッシュエントリの書き込み、Webhook の送信といったバックグラウンド処理のために実行を続けます。
  4. 以降の出力は破棄されます。 呼び出しの後に生成された出力(echo、print、var_dump)は暗黙のうちに破棄されます。
  5. 呼び出しは冪等です。 oxphp_finish_request() は最初の呼び出しでは true を返し、同一リクエスト内での 2 回目以降の呼び出しでは false を返します。

ユースケース

早期レスポンスは、リクエストを即座に受理しつつ、重要でない処理を後回しにしたいあらゆる場面で役立ちます。

  • メール送信 — 即座に「受理」を返し、メールはバックグラウンドで送信する
  • キャッシュのウォーミング — キャッシュ済みデータでレスポンスを返し、その後キャッシュエントリを再生成する
  • アナリティクスとロギング — リクエストを受理し、その後で詳細なアナリティクスレコードを書き込む
  • Webhook の送信 — 呼び出し元に受信を確認し、その後で Webhook 配信をファンアウトする
  • 画像処理 — URL を即座に返し、その後でフルサイズ画像を処理する

PHP の例

基本的な使い方

php
<?php header('Content-Type: application/json'); echo json_encode(['status' => 'accepted', 'id' => uniqid()]); // Send response now — client receives the full response at this point oxphp_finish_request(); // Background work runs here; client is no longer waiting file_put_contents('/tmp/audit.log', date('c') . " request processed\n", FILE_APPEND); send_notification_email($user);

二重呼び出しを防ぐ

oxphp_finish_request() は 2 回目以降の呼び出しで false を返します。複数のレイヤーがこの関数を呼び出す可能性があるミドルウェアの多いアプリケーションでは、戻り値を確認してください。

php
<?php function finish_and_cleanup(): void { if (!oxphp_finish_request()) { // Already finished — background work was already scheduled return; } // First call — safe to run cleanup flush_metrics_buffer(); close_external_connections(); }

条件付きのバックグラウンド処理

php
<?php header('Content-Type: application/json'); $payload = json_decode(file_get_contents('php://input'), true); $result = handle_request($payload); echo json_encode($result); if ($result['needs_sync']) { oxphp_finish_request(); sync_to_external_service($result); } // No early finish if sync is not needed — script exits normally

ワーカーモード

ワーカーモードでは、PHP ワーカーはスクリプト全体(すべてのバックグラウンド処理を含む)が完了するまで占有され続けます。ワーカーはコールバックが返るまで新しいリクエストを受け付けません。

php
<?php oxphp_worker(function () { $order = json_decode(file_get_contents('php://input'), true); $result = process_order($order); header('Content-Type: application/json'); echo json_encode(['order_id' => $result['id'], 'status' => 'accepted']); oxphp_finish_request(); // Worker is still occupied during this background work send_confirmation_email($result); update_inventory($result); notify_warehouse($result); // Worker becomes available after this point });
Note

ワーカープールのサイズを決める際は、バックグラウンド処理の時間を見込んでおいてください。すべてのリクエストの後にレスポンス後処理へ 3 秒を費やすワーカーは、実質的に同時に処理できるリクエスト数が少なくなります。

トラブルシューティング

バックグラウンド処理が完了しない

PHP の max_execution_time は、oxphp_finish_request() が呼び出された後も引き続き適用されます。バックグラウンド処理を含むスクリプト全体の実行時間が上限を超えると、リクエストは Request cancelled (timeout) の致命的エラーでキャンセルされます。

対処法: max_execution_time を引き上げる(php.ini で、またはスクリプトから set_time_limit() で)か、長時間かかるバックグラウンドタスクをメッセージキューへ移してください。

php
set_time_limit(300); oxphp_finish_request(); // ... long-running work ...

数秒以上を常に要する処理については、Redis、RabbitMQ、あるいは同様のキューへメッセージを発行し、専用のコンシューマーに非同期で処理させてください。

レスポンス送信後はセッションを開始できない

oxphp_finish_request() はその時点のレスポンス(ステータス、ヘッダー、ボディ)をそのまま送信し、それ以降に追加したものは送信されません。送信するということはヘッダーも送信済みになるということであり、そうなった後では PHP はセッションの開始を拒否します。session_start() は Session cannot be started after headers have already been sent を発生させて false を返し、session_regenerate_id() も同じように拒否します。セッションはまったく開始されないため、それ以降の $_SESSION への書き込みは、何も保存しない普通の配列に入るだけです。警告を例外に変換するフレームワークの下では、この拒否は、すでに応答済みのリクエストの中で投げられる例外になります。

対処法: セッションの開始も終了も呼び出しの前に行い、書き込みとロック解除が自分で制御できるタイミングで行われるようにしてください。

php
<?php session_start(); // Before the response goes out, not after $_SESSION['last_seen'] = time(); session_write_close(); // Persist the session and release the save handler's lock oxphp_finish_request(); // Send response // ... work that no longer has a session ...

ワーカーモードでは、リクエストを早期に終了するかどうかにかかわらず、こうしておく価値があります。ワーカーがリクエストの終了時にセッションを閉じるのは、そのワーカーが抱えている他のリクエストがそのセッションを使っていない場合だけです。共有されているセッションは、そのワーカーに次にアイドルの時間ができ、セッションを返却できるときまで閉じられません。それまでの間、保存ハンドラーはロックしたものを保持し続けます。デフォルトのハンドラーの場合、これはそのセッションを他の誰も読めなくする排他ロックです。境界の全体については ワーカーモード を参照してください。

oxphp_finish_request() の呼び出し後にレスポンスボディが空になる

echo による出力の前に oxphp_finish_request() を呼び出すと、クライアントは空のボディを受け取ります。まずレスポンスを構築して出力し、それから関数を呼び出してください。

注意事項

  • oxphp_finish_request() は最初の呼び出しでは true を返し、同一リクエスト内での以降の呼び出しでは false を返します。
  • 最初の呼び出しの後のすべての出力(echo、print、var_dump)は暗黙のうちに破棄されます。
  • ワーカーモードでは、ワーカーはレスポンス後のコードをすべて含むコールバック全体が完了するまで占有され続けます。
  • リクエストのタイムアウトは、oxphp_finish_request() の後に実行されるバックグラウンドコードにも引き続き適用されます。
  • oxphp_finish_request() と oxphp_stream_flush() は排他的です。ストリーム開始前に oxphp_finish_request() を呼び出すとストリーミングができなくなり、oxphp_stream_flush() の後に呼び出すとストリームが閉じられます。

エージェント向けプロンプト

これらのプロンプトは、OxPHP ドキュメント MCP サーバー を接続したエージェントを前提としています — 必要なページはエージェントが自分で読み取ります。

prompt
レスポンス後の処理を `oxphp_finish_request()` の後ろへ移してください。OxPHP ドキュメント MCP サーバー経由で `features/early-response` ページを読んでください。 このリポジトリの中で、クライアントが待たされているのに待つ必要のない処理(メール送信、Webhook のファンアウト、分析データの書き込み)を見つけ、レスポンスが先に出るように、かつセッションがあれば呼び出しの前に開始して閉じるように書き直したハンドラーを見せてください。

関連項目

  • ワーカーモード — 永続的な PHP ワーカーと、早期レスポンスがリクエストループとどう関わるか
  • タイムアウト — リクエストのタイムアウトがバックグラウンド処理にどう適用されるか
  • PHP 関数 — oxphp_finish_request() とその他の組み込み関数の完全なリファレンス