StreamResponse 发不出大文件因默认配置将文件全载入内存,须禁用输出缓冲、手动设Content-Length、确保flush生效、避免日志干扰、处理连接中断,并正确编码中文文件名。

StreamResponse 为什么发不出大文件?
因为默认配置下,PHP 的 output_buffering 和 Yii 的中间件(比如 Response 自动 setContentLength)会把整个文件读进内存再吐出去,根本没走流式逻辑。你看到的“卡住”“502”“内存爆掉”,基本都卡在这儿。
- 必须手动禁用输出缓冲:
ob_end_clean()或设output_buffering = Offin php.ini -
StreamResponse不会自动设置Content-Length,但某些代理或浏览器(尤其 Safari)没它就不触发下载进度条——得自己算并调用$response->headers->set('Content-Length', $size) - 别在
beforeAction或过滤器里提前写响应体,否则StreamResponse的底层fopen('php://output')会失败
怎么让 StreamResponse 真正“边读边发”?
核心是绕过 Yii 默认的响应体收集机制,直接接管输出流。不是 new 一个 StreamResponse 就完事,得让它成为最终返回值,并且不被其他层拦截。
- 控制器里必须 return 它,不能只
$response->send():return Yii::$app->response->stream(function () use ($filePath) { $fp = fopen($filePath, 'rb'); while (!feof($fp)) { echo fread($fp, 8192); flush(); // 关键,不然 Web 服务器可能缓存 } fclose($fp); }, ['contentType' => 'application/octet-stream']); - 确保
flush()生效:Nginx 需关fastcgi_buffering off;;Apache 要关mod_deflate或加SetEnv no-gzip 1 - 避免使用
Yii::info()或日志写入响应过程中,file_put_contents 或 error_log 可能触发隐式 flush 干扰流
404 或断连时文件不完整?检查这些点
流式传输没有“事务”,一旦连接中断,客户端就拿到半截文件,而服务端还傻等 fclose —— 这时候靠 PHP 的 connection_aborted() 和 ignore_user_abort(false) 来收尾。
- 在 stream callback 里每读一段后加判断:
if (connection_status() !== CONNECTION_NORMAL) { return; } - 别设
set_time_limit(0)就万事大吉,PHP-FPM 的request_terminate_timeout仍会杀进程,建议设为略大于文件最大传输时间 - 如果用 X-Sendfile 或 X-Accel-Redirect,
StreamResponse根本不生效——那是 Web 服务器直读文件,和 PHP 流式无关,别混用
Content-Disposition 中文名乱码怎么办?
HTTP 头不支持 UTF-8 原样传,filename=中文.pdf 在 Chrome 可能变成下划线,Firefox 直接拒载。得按 RFC 5987 编码。
- 用
rawurlencode()+mb_convert_encoding()构造:filename*=UTF-8''%E4%BD%A0%E5%A5%BD.pdf - 别只设
filename,要同时设filename(ASCII fallback)和filename*(RFC 5987)两个 header - Yii 的
HeaderCollection::set()不自动处理多值,得手写:$response->headers->set('Content-Disposition', 'attachment; filename="report.pdf"; filename*=UTF-8\'\'%E6%8A%A5%E8%A1%A8.pdf');


















