提前响应
oxphp_finish_request() 会立即将完整的 HTTP 响应发送给客户端,同时让你的 PHP 脚本继续运行以处理后台工作。它是 PHP-FPM 中 fastcgi_finish_request() 在 OxPHP 中的对应实现。
工作原理
- 构建响应。 你的脚本像往常一样设置响应头、状态码,并输出响应体。
- 结束请求。 调用
oxphp_finish_request()。OxPHP 会刷新所有输出缓冲区,将请求标记为已完成,并把完整的 HTTP 响应交付给客户端。 - 运行后台工作。 脚本继续执行后台工作,例如发送邮件、写入缓存条目或分发 webhook。
- 后续输出会被丢弃。 调用之后产生的任何输出(
echo、print、var_dump)都会被静默丢弃。 - 该调用是幂等的。 在同一次请求中,
oxphp_finish_request()首次调用返回true,之后的任何调用都返回false。
使用场景
只要你希望立即确认一个请求、同时把非关键工作延后处理,提前响应就能派上用场:
- 发送邮件 —— 立即返回 “accepted”,在后台发送邮件
- 缓存预热 —— 先用缓存数据响应,再重新生成缓存条目
- 分析与日志 —— 先确认请求,再写入详细的分析记录
- Webhook 分发 —— 先向调用方确认已收到,再扇出 webhook 投递
- 图片处理 —— 立即返回一个 URL,再处理原始尺寸的图片
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
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
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
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
});在规划工作进程池规模时,要把后台处理时间考虑进去。如果一个工作进程在每次请求后都要花 3 秒处理响应后的工作,那么它实际能处理的并发请求就会更少。
故障排查
后台工作没有完成
在调用 oxphp_finish_request() 之后,PHP 的 max_execution_time 仍然生效。如果脚本的总执行时间(含后台工作)超过该限制,请求就会被取消,并抛出 Request cancelled (timeout) 致命错误。
解决办法: 提高 max_execution_time(在 php.ini 中,或在脚本里通过 set_time_limit() 设置),或者把耗时较长的后台任务转移到消息队列:
set_time_limit(300);
oxphp_finish_request();
// ... long-running work ...对于经常需要花费几秒以上的工作,可以向 Redis、RabbitMQ 或类似的队列发布一条消息,交由专门的消费者异步处理。
会话更改丢失
会话数据必须在调用 oxphp_finish_request() 之前写入。调用之后所做的更改会被丢弃。
解决办法: 在 oxphp_finish_request() 之前调用 session_write_close():
<?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。 - 首次调用之后的所有输出(
echo、print、var_dump)都会被静默丢弃。 - 在工作进程模式下,工作进程会一直被占用,直到整个回调执行完毕,包括所有响应后的代码。
- 请求超时对
oxphp_finish_request()之后运行的后台代码同样生效。 oxphp_finish_request()和oxphp_stream_flush()互斥:在开始流式传输之前调用oxphp_finish_request()会阻止流式传输,而在oxphp_stream_flush()之后调用它则会关闭该流。