提前响应

oxphp_finish_request() 会立即将完整的 HTTP 响应发送给客户端,同时让你的 PHP 脚本继续运行以处理后台工作。它是 PHP-FPM 中 fastcgi_finish_request() 在 OxPHP 中的对应实现。

工作原理

  1. 构建响应。 你的脚本像往常一样设置响应头、状态码,并输出响应体。
  2. 结束请求。 调用 oxphp_finish_request()。OxPHP 会刷新所有输出缓冲区,将请求标记为已完成,并把完整的 HTTP 响应交付给客户端。
  3. 运行后台工作。 脚本继续执行后台工作,例如发送邮件、写入缓存条目或分发 webhook。
  4. 后续输出会被丢弃。 调用之后产生的任何输出(echoprintvar_dump)都会被静默丢弃。
  5. 该调用是幂等的。 在同一次请求中,oxphp_finish_request() 首次调用返回 true,之后的任何调用都返回 false

使用场景

只要你希望立即确认一个请求、同时把非关键工作延后处理,提前响应就能派上用场:

  • 发送邮件 —— 立即返回 “accepted”,在后台发送邮件
  • 缓存预热 —— 先用缓存数据响应,再重新生成缓存条目
  • 分析与日志 —— 先确认请求,再写入详细的分析记录
  • 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() 在第二次及后续调用时返回 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 秒处理响应后的工作,那么它实际能处理的并发请求就会更少。

故障排查

后台工作没有完成

在调用 oxphp_finish_request() 之后,PHP 的 max_execution_time 仍然生效。如果脚本的总执行时间(含后台工作)超过该限制,请求就会被取消,并抛出 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() 之前写入。调用之后所做的更改会被丢弃。

解决办法:oxphp_finish_request() 之前调用 session_write_close()

php
<?php $_SESSION['last_seen'] = time(); session_write_close(); // Persist session before finishing oxphp_finish_request(); // Send response
调用 oxphp_finish_request() 后响应体为空

如果你在任何 echo 输出之前就调用了 oxphp_finish_request(),客户端收到的响应体将为空。请先构建并输出响应,然后再调用该函数。

说明

  • 在同一次请求中,oxphp_finish_request() 首次调用返回 true,后续调用返回 false
  • 首次调用之后的所有输出(echoprintvar_dump)都会被静默丢弃。
  • 在工作进程模式下,工作进程会一直被占用,直到整个回调执行完毕,包括所有响应后的代码。
  • 请求超时对 oxphp_finish_request() 之后运行的后台代码同样生效。
  • oxphp_finish_request()oxphp_stream_flush() 互斥:在开始流式传输之前调用 oxphp_finish_request() 会阻止流式传输,而在 oxphp_stream_flush() 之后调用它则会关闭该流。

另请参阅

  • 工作进程模式 —— 常驻的 PHP 进程,以及提前响应如何与请求循环交互
  • 超时 —— 请求超时如何应用于后台工作
  • PHP 函数 —— oxphp_finish_request() 及其他内置函数的完整参考