PHP 怎么透传请求头

wen PHP项目 1

PHP 实战指南:如何优雅地透传(转发)HTTP 请求头(Header)到下游服务


📚 目录导读(Table of Contents)

  1. 为什么要透传请求头? —— 认证、链路追踪与微服务网关的核心需求
  2. PHP 中获取请求头的几种姿势 —— $_SERVER vs getallheaders() 的坑与选择
  3. 核心实现:cURL 透传请求头的完整代码示例 —— 含 HTTP 版本、大小写敏感处理
  4. 进阶方案:使用 Guzzle HTTP 客户端进行请求头透传 —— PSR-7 标准下的优雅写法
  5. 高频踩坑与解决方案(FAQ 问答) —— 解决 HTTP/2 伪头、Authorization 丢失等疑难杂症
  6. 性能与安全考量 —— 防止 Header 注入与敏感信息泄漏

为什么要透传请求头?

在微服务架构或前后端分离项目中,PHP 经常扮演 API 网关(Gateway)BFF(Backend For Frontend) 的角色,客户端发送的 Authorization(令牌)、X-Request-ID(链路追踪ID)、Accept-Language(语言偏好)等请求头,必须原封不动地转发给下游业务服务。如果丢失了这些 Header,下游服务会返回 401 未授权或无法进行日志关联

PHP 怎么透传请求头


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];
    }
}

性能与安全考量

  1. Header 注入防御:当透传 Content-Type 或自定义头时,务必使用 strip_tags()trim() 过滤值,防止注入 \r\n 构造恶意响应头。
  2. 超时设置:透传请求头后,下游处理可能变慢,务必设置 CURLOPT_TIMEOUTCURLOPT_CONNECTTIMEOUT,避免 PHP 进程挂死。
  3. 日志脱敏:如果必须记录 Header 日志,请将 AuthorizationCookie 的值替换为 ,防止日志泄露令牌。

透传请求头是 PHP 网关开发的基础功,掌握 getallheaders() 的兼容规避、cURL 的 CURLOPT_HTTPHEADER 要点,以及针对 HTTP/2 和 Nginx 的特殊处理,就能构建稳定、安全的转发层,建议优先选择框架内置的 HTTP 客户端(如 Guzzle),以减少底层协议差异带来的困扰。

抱歉,评论功能暂时关闭!