PHP 实战指南:如何优雅地透传(转发)HTTP 请求头(Header)到下游服务
📚 目录导读(Table of Contents)
- 为什么要透传请求头? —— 认证、链路追踪与微服务网关的核心需求
- PHP 中获取请求头的几种姿势 ——
$_SERVERvsgetallheaders()的坑与选择 - 核心实现:cURL 透传请求头的完整代码示例 —— 含 HTTP 版本、大小写敏感处理
- 进阶方案:使用 Guzzle HTTP 客户端进行请求头透传 —— PSR-7 标准下的优雅写法
- 高频踩坑与解决方案(FAQ 问答) —— 解决
HTTP/2伪头、Authorization丢失等疑难杂症 - 性能与安全考量 —— 防止 Header 注入与敏感信息泄漏
为什么要透传请求头?
在微服务架构或前后端分离项目中,PHP 经常扮演 API 网关(Gateway) 或 BFF(Backend For Frontend) 的角色,客户端发送的 Authorization(令牌)、X-Request-ID(链路追踪ID)、Accept-Language(语言偏好)等请求头,必须原封不动地转发给下游业务服务。如果丢失了这些 Header,下游服务会返回 401 未授权或无法进行日志关联。

PHP 中获取请求头的姿势
在写透传代码前,得先能完整拿到请求头,PHP 提供了两种方式,但都有“坑”:
-
方式 A:
$_SERVER超全局变量(传统但繁琐) 所有 HTTP 请求头在$_SERVER中都会被转为大写并加上HTTP_前缀。X-Custom-Header变成了$_SERVER['HTTP_X_CUSTOM_HEADER'],但Authorization头在某些 SAPI(如 Apache mod_php)下可能不会出现在$_SERVER中,这是一个著名的坑。 -
方式 B:
getallheaders()函数(推荐,但不通用) 这个函数能一次性返回所有 Header 的关联数组,且保留原始大小写(如X-Custom-Header)。 ⚠️ 注意:该函数仅在 Apache 或 Nginx + PHP-FPM 环境下可用,如果你使用 CGI 模式(如 IIS),此函数未定义,需手动从$_SERVER转换。💡 防坑兼容代码:
if (!function_exists('getallheaders')) { function getallheaders() { $headers = []; foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headerName = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); $headers[$headerName] = $value; } } return $headers; } }
核心实现:cURL 透传请求头(零依赖方案)
假设我们要将当前请求转发到 https://api.internal.example.com/v1/data。
<?php
// 1. 获取所有原始请求头(保留大小写)
$headers = getallheaders();
// 2. 过滤掉 Host 头(必须使用 cURL 的 CURLOPT_URL 指定的主机)
unset($headers['Host']);
// 3. 构建 cURL 所需的 Header 数组格式: ["Key: Value", ...]
$curlHeaders = [];
foreach ($headers as $key => $value) {
// 关键:需要拼成 "Name: Value" 的字符串
$curlHeaders[] = $key . ': ' . $value;
}
// 4. 初始化 cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.internal.example.com/v1/data");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 5. 🔥 核心透传动作:设置自定义请求头
curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders);
// 注意:如果是 GET 请求,也可以透传查询参数
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input')); // 透传请求体
}
// 6. 执行并返回响应
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 7. 将下游响应原样返回给客户端
http_response_code($statusCode);
header('Content-Type: application/json'); // 建议从下游响应中获取,此处简化
echo $response;
⚠️ 重要提示: 如果原始请求是 HTTP/1.1 且包含 Connection: keep-alive,透传给下游时必须由 cURL 自行管理连接,建议剔除 Connection 头,避免协议错误。
进阶方案:使用 Guzzle HTTP 客户端(PSR-7 规范)
在 Laravel 或 Symfony 项目中,更推荐使用 Guzzle,它处理了 HTTP/2 伪头问题。
<?php
use GuzzleHttp\Client;
$client = new Client();
// 直接使用 getallheaders() 作为 Guzzle 的 headers 选项(Guzzle 会自动格式化)
$response = $client->request($_SERVER['REQUEST_METHOD'], 'https://api.internal.example.com/v1/data', [
'headers' => getallheaders(), // 一键透传
'body' => file_get_contents('php://input'),
]);
// 转发响应
echo $response->getBody();
高频踩坑与解决方案(FAQ 问答)
❓ 问题 1:我透传了 Authorization 头,但下游还是报 401?
✅ 解决方案:大概率是 CGI 模式下(如 Nginx + PHP-FPM 有时不会把 Authorization 自动放入 $_SERVER),你需要检查 Nginx 配置是否包含:
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
或者直接使用 getallheaders() 获取(它在 FPM 模式下对 Authorization 有效)。
❓ 问题 2:使用 HTTP/2 时,Host 头变成了 authority 伪头,如何处理?
✅ 解决方案:不要手动设置 Host,在 cURL 中,只需设置 CURLOPT_URL,cURL 会自动生成对应的伪头,如果下游需要校验域名,请确保 CURLOPT_URL 中的域名与原始一致。
❓ 问题 3:透传头中包含下划线 ,为什么下游收不到?
✅ 解决方案:这是 PHP 的老规矩。$_SERVER 会把 X_FOO 转为 HTTP_X_FOO,但如果你用 cURL 透传,底层是允许下划线的,但如果你的 Nginx 开启了 underscores_in_headers off;(默认关闭),则 Nginx 层就会丢弃带下划线的头,建议在 Nginx 中设置 underscores_in_headers on; 或统一使用连字符 。
❓ 问题 4:透传请求头时,如何避免敏感信息(如 Cookie)泄漏给所有下游? ✅ 解决方案:白名单机制,不要全量透传,只选择需要的键:
$allowedHeaders = ['Authorization', 'X-Request-ID', 'Content-Type'];
foreach ($allowedHeaders as $key) {
if (isset($headers[$key])) {
$curlHeaders[] = $key . ': ' . $headers[$key];
}
}
性能与安全考量
- Header 注入防御:当透传
Content-Type或自定义头时,务必使用strip_tags()和trim()过滤值,防止注入\r\n构造恶意响应头。 - 超时设置:透传请求头后,下游处理可能变慢,务必设置
CURLOPT_TIMEOUT和CURLOPT_CONNECTTIMEOUT,避免 PHP 进程挂死。 - 日志脱敏:如果必须记录 Header 日志,请将
Authorization和Cookie的值替换为 ,防止日志泄露令牌。
透传请求头是 PHP 网关开发的基础功,掌握 getallheaders() 的兼容规避、cURL 的 CURLOPT_HTTPHEADER 要点,以及针对 HTTP/2 和 Nginx 的特殊处理,就能构建稳定、安全的转发层,建议优先选择框架内置的 HTTP 客户端(如 Guzzle),以减少底层协议差异带来的困扰。