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

本地与服务器上Guzzle/PHP调用FCM出现411 Content-Length异常

问题:PHP通过Guzzle/原生cURL调用FCM时生产环境返回411 Content-Length required

我在本地用PHP+Guzzle向FCM发送请求完全正常,但部署到生产服务器(Docker环境)后,FCM一直返回错误:411 Content-Length required。

已经确认过本地和生产环境的PHP版本、Guzzle版本、cURL版本完全一致,Docker镜像配置也相同(Dockerfile如下)。尝试手动添加Content-Length头也没有效果。

相关代码

Guzzle实现

$handler = new CurlHandler();
$stack = HandlerStack::create($handler);
$config['handler'] = $stack;
$client = new Client($config);
try {
    $client->post($this->apiUrl, [
        RequestOptions::JSON => $data,
        RequestOptions::HEADERS => [
            'Authorization' => sprintf('key=%s', "key"),
        ]
    ]);
} catch (TransferException $e) {
    throw new \RuntimeException($e->getMessage(), $e->getCode(), $e);
}

尝试原生cURL(exec方式)

$curl = sprintf("curl -i -X POST https://fcm.googleapis.com/fcm/send -H 'Authorization: key=%s' -H 'Content-Type: application/json' -d '%s'", $message->getSender(), $data);
exec($curl, $output);

用原生方式测试结果一样:在生产机器的bash里直接运行curl命令正常,但通过PHP的exec执行就返回411错误。

Dockerfile配置

FROM php:7.1-fpm-alpine
RUN apk --update --no-cache add libmcrypt-dev libmcrypt curl-dev libcurl \
    && docker-php-source extract \
    && docker-php-source delete \
    && docker-php-ext-install mcrypt pdo_mysql mbstring curl
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN composer --version
RUN chmod +x /usr/local/bin/composer
WORKDIR /var/www/project

想请教:

  1. 这个问题可能的原因是什么?
  2. 有没有办法在本地复现这个问题?

解决方案与分析

这个411错误的核心是FCM服务器未接收到有效的Content-Length头,或者请求体传输异常,结合你的环境和测试情况,大概率是以下几个原因:

1. 生产环境网络中间件修改请求头

很多生产环境会部署反向代理(如Nginx、HAProxy)或防火墙,这些中间件可能会剥离或修改请求头——尤其是当Guzzle默认使用chunked编码传输请求体时(Guzzle在无法确定Content-Length时会自动用chunked),FCM对这种编码的兼容性可能存在问题,或者中间件转发时丢失了关键头信息。

解决办法:强制禁用chunked编码,手动控制请求体和头

直接序列化JSON数据,手动计算Content-Length,避免Guzzle自动处理带来的不确定性:

// 先序列化JSON数据,确保内容可控
$jsonData = json_encode($data);
$client->post($this->apiUrl, [
    RequestOptions::BODY => $jsonData,
    RequestOptions::HEADERS => [
        'Authorization' => sprintf('key=%s', "your_fcm_key"),
        'Content-Type' => 'application/json',
        'Content-Length' => strlen($jsonData)
    ],
    // 禁用chunked编码,强制使用HTTP/1.1
    'curl' => [
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_TRANSFER_ENCODING => false,
    ]
]);

2. PHP cURL配置被环境异常覆盖

生产环境的php.ini或Docker镜像可能存在特殊配置,比如curl.cainfo未正确设置导致SSL握手异常(虽然错误是411,但SSL异常可能导致请求体传输中断,服务器返回非预期错误),或者执行时间、内存限制导致请求未完整发送。

解决办法:强制指定SSL证书路径

Alpine镜像的CA证书默认路径是/etc/ssl/certs/ca-certificates.crt,可以在Guzzle配置里强制指定:

'curl' => [
    CURLOPT_CAINFO => '/etc/ssl/certs/ca-certificates.crt',
    CURLOPT_SSL_VERIFYPEER => true,
]

3. exec执行curl时的字符串转义问题

你用sprintf拼接curl命令时,如果$data包含单引号、空格等特殊字符,会导致命令被截断,实际发送的请求体为空,自然触发411错误。

解决办法:用escapeshellarg转义所有参数

$authValue = escapeshellarg(sprintf('key=%s', $message->getSender()));
$postData = escapeshellarg($data);
$curl = "curl -i -X POST https://fcm.googleapis.com/fcm/send -H 'Authorization: $authValue' -H 'Content-Type: application/json' -d $postData";
exec($curl, $output, $returnCode);
// 可以打印返回码和输出排查问题
var_dump($returnCode, $output);

如何在本地复现问题?

你可以模拟生产环境的异常场景:

  • 搭反向代理:在本地用Nginx做反向代理转发FCM请求,配置Nginx剥离Content-Length头,测试是否触发411错误。
  • 模拟异常配置:修改本地PHP的cURL配置,比如禁用chunked编码,或者设置错误的CA证书路径,模拟生产环境的异常配置。
  • 完全复刻生产Docker环境:在本地运行和生产一致的Docker镜像,同时模拟生产的网络策略(比如添加内网代理),看是否能复现问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 03:59:27