You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HTTP 100 Continue预加载失败排查及Symfony headers_sent重置问题

问题描述

我尝试利用HTTP 100 Continue配合preload头实现浏览器资源预加载,但未得到预期结果。以下是我的示例代码:

header('HTTP/1.1 100 Continue');
header('Link: </assets/style.css>; rel=preload');
header('Link: </assets/app.js>; rel=preload');
header(''); // send empty line to end 100 continue section
flush();

sleep (4); // Emulate some processing while browser is preloading assets

header('HTTP/1.1 200 OK');
echo "aaa
";
echo "bbb
";
flush();

exit;

预期返回的响应:

HTTP/1.1 100 Continue
Link: </assets/style.css>; rel=preload
Link: </assets/app.js>; rel=preload

HTTP/1.1 200 OK

aaa
bbb

但实际返回的响应:

HTTP/1.1 200 OK
Date: Wed, 04 Oct 2023 15:21:24 GMT
Server: Apache/2.4.57 (Ubuntu)
Link: </assets/app.js>; rel=preload
Upgrade: h2,h2c
Connection: Upgrade
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

aaa
bbb

我的疑问:

  • 是否遗漏了什么?仅使用flush()是否不足以发送HTTP 100 Continue头?
  • 当前技术栈为apache + php-fpm,测试命令为curl -i https://localhost/。
  • 此外,我将在Symfony项目中使用该功能,需在发送100 Continue段后重置headers_sent(),否则Symfony的发送函数会因检测到headers_sent()而直接返回,但我不清楚如何在首次flush后重置它。

解决方案

一、HTTP 100 Continue未正确发送的原因及修复

在Apache + PHP-FPM架构下,你的实现存在几个关键问题:

  1. 触发逻辑错误
    HTTP 100 Continue是客户端先发送Expect: 100-continue请求头,服务器才会返回100 Continue响应。你用curl -i测试时未携带该请求头,所以Apache/PHP-FPM不会返回100 Continue。测试需改用:
curl -i -H "Expect: 100-continue" --http1.1 https://localhost/
  1. 输出缓冲机制拦截
    PHP-FPM默认开启output_buffering,加上Apache自身的缓冲,单纯flush()无法立即发送headers。需先关闭PHP输出缓冲:
// 放在所有输出之前执行
ob_end_flush();
ob_implicit_flush(true);
  1. 空行发送方式错误
    用header('')无法生成100 Continue所需的空行,PHP的header()仅用于发送HTTP头,正确做法是直接输出空行并flush:
echo "\r\n";
flush();
  1. HTTP/2或压缩模块干扰
    若Apache开启mod_http2或mod_deflate,会强化缓冲机制,导致100 Continue无法及时发送。测试时可强制用HTTP/1.1(如上述curl命令的--http1.1参数),或临时关闭压缩。

修正后的测试代码:

ob_end_flush();
ob_implicit_flush(true);

// 仅当客户端发送Expect:100-continue时才返回100 Continue
if (isset($_SERVER['HTTP_EXPECT']) && strtolower($_SERVER['HTTP_EXPECT']) === '100-continue') {
    header('HTTP/1.1 100 Continue');
    header('Link: </assets/style.css>; rel=preload');
    header('Link: </assets/app.js>; rel=preload');
    echo "\r\n";
    flush();
}

sleep(4);

header('HTTP/1.1 200 OK');
echo "aaa\r\nbbb\r\n";
flush();
exit;

二、Symfony中headers_sent()的处理方案

headers_sent()是PHP内置函数,检测的是HTTP头是否已发送到客户端,这个状态无法被重置——一旦头被发送,就无法收回。

在Symfony中,推荐使用StreamedResponse来控制输出流程,避免与框架的响应生命周期冲突:

use Symfony\Component\HttpFoundation\StreamedResponse;

$response = new StreamedResponse(function () {
    ob_end_flush();
    ob_implicit_flush(true);

    // 发送100 Continue(仅当客户端请求时)
    if (isset($_SERVER['HTTP_EXPECT']) && strtolower($_SERVER['HTTP_EXPECT']) === '100-continue') {
        header('HTTP/1.1 100 Continue');
        header('Link: </assets/style.css>; rel=preload');
        header('Link: </assets/app.js>; rel=preload');
        echo "\r\n";
        flush();
    }

    // 模拟业务处理
    sleep(4);

    // 发送最终响应
    header('HTTP/1.1 200 OK');
    echo "aaa\r\nbbb\r\n";
    flush();
});

return $response;

StreamedResponse允许你逐步输出内容,完全掌控头和内容的发送时机,不会触发Symfony的headers_sent()检测拦截。


内容的提问来源于stack exchange,提问作者HubertNNN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:25:31