FrankenPHP 实战:利用 HTTP 103 Early Hints 预加载资源,优化 PHP 页面首屏性能
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
导读:本文围绕 docs/early-hints.md 展开,深入讲解 FrankenPHP 对 HTTP 103 Early Hints(早期提示)状态码的原生支持。你将掌握如何用 PHP 的
headers_send(103)在慢查询、重计算完成之前提前向浏览器推送 CSS/JS 预加载提示,并了解该能力在普通模式与 Worker 模式下的实现原理与测试验证方式。
什么是 HTTP 103 Early Hints
HTTP 103 Early Hints 是由 IETF 定义的一种中间响应状态码。在服务器尚未生成最终响应(通常因为需要执行慢 SQL、复杂算法或等待第三方 API)时,它可以提前向客户端发送一组提示头,最常见的用法是Link响应头配合rel=preload,让浏览器先并行下载页面依赖的样式表、脚本与字体。
Early Hints 的核心价值在于"提前":浏览器不必等到最终 HTML 生成完毕才开始下载资源,而是在 TTFB(首字节时间)之前就发起关键资源请求,从而显著缩短页面可交互时间。
FrankenPHP 在 PHP 扩展层面对这一机制提供了原生支持——不需要额外的 HTTP 服务器配置,也不需要反向代理介入,直接在 PHP 代码中调用即可。
FrankenPHP 中的最小可用示例
来自 docs/early-hints.md 的官方示例非常精炼:
<?php header('Link: </style.css>; rel=preload; as=style'); headers_send(103); // your slow algorithms and SQL queries 🤪 echo <<<'HTML' <!DOCTYPE html> <title>Hello FrankenPHP</title> <link rel="stylesheet" href="style.css"> HTML;执行流程分三步:
- 先用
header()设置Link: </style.css>; rel=preload; as=style,告诉浏览器需要预加载style.css; - 调用
headers_send(103),FrankenPHP 会立即以103 Early Hints状态码把当前已设置的响应头发送给客户端,同时保持后续最终响应不受影响; - 继续执行慢速逻辑(SQL、算法等),最后输出完整的 HTML 正文。
浏览器收到 103 后,会在服务器仍忙于计算时就开始下载style.css,最终响应到达时样式已就绪,渲染无需等待。
headers_send():FrankenPHP 扩展内置的提示发送函数
headers_send()是 FrankenPHP 通过 PHP 扩展注册的全局函数,其签名在 frankenphp.stub.php 中声明:
function headers_send(int $status = 200): int {}即默认以 200 状态发送当前响应头,但当你传入 1xx 范围内的状态码(如 103)时,行为会切换到"只发中间提示、不终结响应"模式。
其底层实现位于 frankenphp.c,核心逻辑如下(按源码结构概括):
PHP_FUNCTION(headers_send) { zend_long response_code = 200; // ... 解析可选参数 int previous_status_code = SG(sapi_headers).http_response_code; SG(sapi_headers).http_response_code = response_code; if (response_code >= 100 && response_code < 200) { int ret = sapi_module.send_headers(&SG(sapi_headers)); SG(sapi_headers).http_response_code = previous_status_code; // 恢复原状态码 RETURN_LONG(ret); } RETURN_LONG(sapi_send_headers()); }从源码可以看到两个关键设计:
- 1xx 分支走
send_headers而非sapi_send_headers:中间响应只推送头部、不结束请求,因此你可以在调用headers_send(103)之后继续执行 PHP 逻辑并输出正文; - 调用前后保存并恢复
http_response_code:发完 103 提示后,内部状态码会被还原为之前的值,最终响应仍能以正常状态码(如 200)发出,互不干扰。
更完整的实战模板:发送多组提示并清理头
仓库中的真实测试脚本 testdata/early-hints.php 展示了比官方示例更严谨的写法——它示范了如何附带请求上下文头,并在发送 103 后移除 Link 头,避免预加载提示被泄漏到最终响应里:
<?php require_once __DIR__.'/_executor.php'; return function () { header('Link: </style.css>; rel=preload; as=style'); header("Request: {$_GET['i']}"); headers_send(103); header_remove('Link'); echo 'Hello'; };这里header_remove('Link')很重要:103 提示中声明的Link头仅用于中间提示阶段;若不主动移除,某些客户端可能把同一个Link头当作最终响应的一部分重复处理。生产环境建议同样在headers_send(103)之后按需清理提示专用头。
在 Worker 模式下使用 Early Hints
FrankenPHP 的 Worker 模式会在多个请求之间常驻 PHP 进程(详见 docs/worker.md)。得益于 103 响应不终结请求的特性,Early Hints 在普通模式与 Worker 模式下同样可用,官方文档明确说明:
Early Hints are supported both by the normal and the worker modes.
也就是说,即使你的应用运行在常驻内存的 Worker 模式下,也可以放心地使用headers_send(103)——它不会影响 Worker 进程的请求生命周期管理,发送中间提示后仍可继续执行业务代码并输出最终响应。
测试验证:Go 集成测试如何确认 103 行为
仓库中的 Go 测试从客户端视角验证了 Early Hints 的完整行为。frankenphp_test.go 分别针对普通模式与 Worker 模式注册了测试用例:
func TestEarlyHints_module(t *testing.T) { testEarlyHints(t, &testOptions{}) } func TestEarlyHints_worker(t *testing.T) { testEarlyHints(t, &testOptions{workerScript: "early-hints.php"}) }测试通过httptrace.ClientTrace.Got1xxResponse捕获 1xx 中间响应,断言:
- 客户端确实收到了
http.StatusEarlyHints(即 103); - 103 响应中携带的
Link头内容为</style.css>; rel=preload; as=style,同时Request上下文头正确回显请求序号; - 最终响应中
Request头仍然存在,而Link头已经被header_remove移除。
这套测试同时覆盖了普通模块模式(TestEarlyHints_module)和 Worker 模式(TestEarlyHints_worker),从实现层面印证了"两种模式均支持 103"的文档结论,也为你在自己的代码中验证 Early Hints 行为提供了可复用的测试范式。
使用建议与注意事项
综合官方文档、扩展源码与测试脚本,在生产中使用 FrankenPHP 的 Early Hints 时请注意:
- 只预加载关键资源:
rel=preload会与最终 HTML 中实际用到的资源形成双重加载机会,滥用会浪费带宽。应只对首屏必需的 CSS 与关键 JS 发送提示; - 提示头要及时清理:参照 testdata/early-hints.php 的做法,在
headers_send(103)后用header_remove()移除仅供提示阶段使用的头,保持最终响应干净; - 状态码范围:
headers_send()只对100 <= status < 200的输入走中间响应分支(如 103),其余状态码会直接终结响应头发送,详见 frankenphp.c; - 配合 Worker 模式使用:103 提示不终结请求,Worker 常驻进程下行为一致,但请记得在 Worker 中处理头部的状态持久化问题(参考 docs/worker.md 中关于跨请求状态残留的说明)。
通过以上手段,你可以让 PHP 应用在数据库查询与复杂计算期间提前"点亮"浏览器的资源下载管线,把首屏等待时间转化为并行加载时间——这正是 FrankenPHP 原生 103 Early Hints 支持带给现代 PHP 应用的关键性能收益。
【免费下载链接】frankenphp🧟 The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考