NGINX+PHP环境下SSE EventStream分块异常及响应头缺失问题
SSE EventStream在NGINX+PHP环境流式传输异常排查与解决
问题现象
本地Apache+PHP环境中,SSE EventStream可正常逐词流式传输大文本,但部署到NGINX+PHP服务器后出现以下异常:
- 初始延迟约为代码设置的单词延迟的10倍
- 流以大分块形式返回,而非预期的逐词推送
- 响应头中缺失
Transfer-encoding: chunked和X-Accel-Buffering: no
现有PHP代码
public function sse($delay = 50) { // Sample paragraph of text $paragraph = "This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec elit sit amet a. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec elit sit amet a"; // Split the paragraph into an array of words $words = explode(' ', $paragraph); // Set up headers for streaming header('X-Accel-Buffering: no'); header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); ini_set('zlib.output_compression', 0); ini_set('implicit_flush', 1); // This function discards the contents of the topmost output buffer and turns off this output buffering. @ob_end_clean(); for ($i = 0; $i < ob_get_level(); $i++) { //for each open buffer @ob_end_flush(); //close it } @ob_implicit_flush(1); //turn implicit flush on // Emit "word" event for each word $id = 1; foreach ($words as $word) { echo "id: $id".PHP_EOL; echo "event: word".PHP_EOL; echo "data: $word".PHP_EOL; echo PHP_EOL; @ob_flush(); flush(); usleep($delay*1000); // Delay between each word (in microseconds) $id++; } @ob_flush(); flush(); }
当前异常响应头
Cache-Control: no-cache; Pragma: no-cache; Content-Type: text/event-stream;charset=UTF-8; ...
已尝试的NGINX配置(部分生效)
proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding on; proxy_buffering off; proxy_cache off; fastcgi_buffering off;
仅proxy_http_version 1.1配置有效:未设置时会一次性返回全部结果,设置后可流式传输但仍存在延迟及大分块问题。
解决方案
1. 调整NGINX核心配置(PHP-FPM场景)
针对PHP-FPM部署,需彻底关闭NGINX侧的缓冲,同时强制传递必要响应头。修改站点配置的location ~ \.php$块:
location ~ \.php$ { # 基础PHP-FPM配置(根据实际环境调整) fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # SSE流式传输关键配置 fastcgi_buffering off; fastcgi_cache off; fastcgi_keep_conn on; # 维持长连接,避免重连损耗 chunked_transfer_encoding on; # 强制添加缺失响应头(若PHP设置未生效) add_header X-Accel-Buffering no always; }
若为反向代理场景(NGINX代理后端PHP服务),需将上述
fastcgi_*配置替换为proxy_*配置,同时保留proxy_http_version 1.1和proxy_buffering off。
2. 优化PHP代码,确保缓冲彻底关闭
原代码的缓冲处理存在冗余,且可能受全局PHP配置影响,修改后如下:
public function sse($delay = 50) { $paragraph = "This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec elit sit amet a. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. This is a sample paragraph of text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla nec elit sit amet a"; $words = explode(' ', $paragraph); // 先禁用所有输出缓冲相关配置 ini_set('output_buffering', '0'); ini_set('zlib.output_compression', '0'); ini_set('implicit_flush', '1'); ob_implicit_flush(1); // 彻底清理所有层级的输出缓冲 while (ob_get_level() > 0) { ob_end_clean(); } // 设置SSE响应头(确保在缓冲清理后设置,避免头被缓冲) header('X-Accel-Buffering: no'); header('Content-Type: text/event-stream; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Connection: keep-alive'); $id = 1; foreach ($words as $word) { // 使用"\n"替代PHP_EOL,避免Windows环境下的\r\n导致分块异常 echo "id: {$id}\n"; echo "event: word\n"; echo "data: {$word}\n"; echo "\n"; // 强制刷新输出 @ob_flush(); flush(); // 若仍存在10倍延迟,检查服务器负载,可尝试用sleep(0.05)替代usleep usleep($delay * 1000); $id++; } // 最后一次刷新,确保所有内容发送完毕 @ob_flush(); flush(); }
3. 验证配置生效
重启NGINX和PHP-FPM服务后,检查响应头是否包含Transfer-encoding: chunked和X-Accel-Buffering: no,同时观察客户端是否逐词接收SSE事件,初始延迟是否恢复至设置的50ms左右。
内容的提问来源于stack exchange,提问作者msawired
相关产品推荐
相关产品推荐

