StreamedResponse 在 FrankenPHP 下被截断的根本原因是未调用 frankenphp_disable_output_buffering() 且未清除 Content-Length 响应头;必须在 index.php 顶部显式禁用输出缓冲,并移除 Content-Length 头,否则流数据会被内部缓冲或长度限制截断。

StreamedResponse 在 FrankenPHP 下为什么被截断?
根本原因是 FrankenPHP 的 SAPI 层对 ob_flush() 和 flush() 的行为处理与传统 PHP-FPM 不同:它默认启用输出缓冲,且不保证每次 flush() 都能穿透到 HTTP 连接层。即使你在 StreamedResponse 的回调里反复调用 echo + flush(),数据也可能卡在 FrankenPHP 的内部缓冲区中,直到响应结束才一次性发出,或直接被截断(尤其在 chunk 较小、间隔较短时)。
必须禁用 FrankenPHP 的 output buffering
不能依赖 ob_end_clean() 或 @ini_set('output_buffering', 'Off') —— 这些在 FrankenPHP 中无效。正确做法是在 public/index.php 顶部显式关闭:
if (function_exists('frankenphp_disable_output_buffering')) {
frankenphp_disable_output_buffering();
}
这是 FrankenPHP 提供的专用函数,会绕过 PHP 标准缓冲机制,直连底层 HTTP 流。未调用此函数是绝大多数截断问题的根源。
Content-Length 必须设为 ''(空字符串),不能省略
FrankenPHP 对 StreamedResponse 要求严格:如果响应头中存在 Content-Length,它会按该长度截断流;而流式响应本就不知道总长度,所以必须显式清除:
立即学习“PHP免费学习笔记(深入)”;
$response->headers->remove('Content-Length');- 不要设置
Content-Length: 0,那也会触发截断 - 确保没其他中间件(如某些日志或安全组件)偷偷重写该 header
避免在流回调里做耗时同步操作
FrankenPHP 的流式写入是单线程、非抢占式的。若你在 StreamedResponse 的闭包里执行数据库查询、远程 HTTP 调用或大数组遍历,会导致流暂停,FrankenPHP 可能因超时或连接空闲关闭而中断连接:
- 把重逻辑提前做完,只在流回调中做
echo+flush() - 每 chunk 后加
usleep(1000)(1ms)有助于稳定推送,但别过度(>5ms 易拖慢整体吞吐) - 用
set_time_limit(0)防止脚本超时中断流(仅限 CLI/SAPI 环境有效)
最易忽略的是 Content-Length 头残留和 frankenphp_disable_output_buffering() 缺失 —— 这两个点不处理,其余优化全无意义。



















