提前响应
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() 会按响应当时的状态(状态码、响应头和响应体)把它发送出去,此后再追加的任何内容都不会被发送。发送响应也意味着响应头已经发出,而一旦如此,PHP 就会拒绝启动会话:session_start() 会报出 Session cannot be started after headers have already been sent 并返回 false,session_regenerate_id() 也会以同样的方式拒绝执行。会话根本没有启动,因此在此之后对 $_SESSION 的写入只是落进了一个普通数组,不会被任何东西保存。在会把警告转换为异常的框架下,这次拒绝会变成一个异常,抛出在一个已经应答完毕的请求里。
解决办法: 在调用之前启动会话,也在调用之前关闭它,让写入和解锁都发生在你能掌控的时间点:
<?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 服务器——需要哪些页面它会自己去读。
把我响应之后才做的活挪到 `oxphp_finish_request()` 后面。通过 OxPHP 文档 MCP 服务器读取 `features/early-response` 页面。
找出这个仓库里客户端正在等、但其实并不需要等的工作(发送邮件、扇出 webhook、写入分析记录),并把那些处理函数改写成先把响应发出去的样子,其中用到的会话都要在该调用之前启动并关闭。