header('Location')触发HTTP协议级重定向,浏览器收到302响应头后立即丢弃原响应体,导致后续echo、flush失效;可行方案是前端JS跳转或SSE/轮询。

PHP实时输出时header跳转会中断输出
会断。只要执行了 header('Location: ...'),PHP 就会立即终止当前脚本(除非 http_response_code() 已发 2xx/3xx 且输出缓冲未清空),后续的 echo、flush()、ob_flush() 全部失效——浏览器收不到跳转前的实时内容。
为什么用ob_start() + flush()也挡不住跳转中断
根本原因不是缓冲区没刷,而是 header('Location') 触发的是 HTTP 协议级重定向:服务端一旦发出 302 响应头,客户端(浏览器)就立刻发起新请求,原响应体被丢弃。即使你提前 ob_start()、echo 了内容并调用 flush() 和 ob_flush(),只要跳转头已发送,浏览器就不会再等后续字节。
- 常见错误写法:
echo "处理中..."; flush(); ob_flush(); header('Location: /done');→ 用户只看到白屏或跳转后页面,看不到“处理中” -
set_time_limit(0)和ignore_user_abort(true)无法挽回,它们影响的是 PHP 脚本生命周期,不改变 HTTP 响应流的不可逆性 - 部分 Nginx/Apache 配置(如
fastcgi_buffering off)能改善流式输出,但对Location跳转无效
真正可行的替代方案:前端 JS 跳转 + 后端流式输出
把跳转逻辑从服务端移到前端,让 PHP 持续输出、浏览器持续接收,最后由 JS 控制跳转时机。
示例结构:
立即学习“PHP免费学习笔记(深入)”;
setTimeout(() => window.location.href = '/report', 500);"; ?>
- 关键点:
ob_implicit_flush(true)+ob_end_flush()确保每次echo后自动刷新,避免被缓冲卡住 - 必须禁用所有输出缓冲层:检查
output_buffering是否为Off(php.ini 或ini_set('output_buffering', 'Off')) - Apache 下需关闭
mod_deflate或设置SetEnv no-gzip 1;Nginx 下确保gzip off,否则压缩会阻塞流式输出 - 前端可加
防 IE 兼容模式吞掉 flush
更健壮的做法:用 SSE 或轮询代替“假实时”
如果需要真实进度反馈(比如上传、长任务),硬靠 echo + flush 很脆弱:连接可能被代理、CDN、浏览器主动断开,且无法捕获失败状态。
- SSE(Server-Sent Events):PHP 后端保持连接,持续
echo "data: ...\n\n",前端用EventSource接收,完成后 JS 跳转 —— 支持重连、天然流式 - 轮询(Polling):前端定时
fetch('/status')查进度,返回 JSON,状态为"done"时跳转 —— 兼容性最好,无连接维持压力 - 别碰
header('Refresh: 1; URL=...'):它本质是 HTML meta 刷新,仍会中断当前响应流,且不准时、不可控
真正的难点不在“怎么输出”,而在“怎么让浏览器相信这是连续响应而不是一次跳转”。一旦混淆了协议层(HTTP redirect)和内容层(HTML/text stream)的职责,所有 flush 都是徒劳。











