PHP-Curl调用FCM API报411错误,浏览器调用正常问题排查
我来帮你拆解一下这个问题,你遇到的情况其实是本地请求和服务器端请求的环境差异导致的,而且你复制的请求头完全用错了地方,咱们一步步说:
问题回顾
你用PHP-Curl调用FCM HTTP v1接口推送给主题设备,本地跑脚本一直返回411 Length Required错误,提示需要Content-length头;但把脚本放到主机www目录,通过浏览器访问却能成功。复制浏览器的请求头到Curl里也没用,搞不清Nginx在这里的作用,也不知道代码缺啥配置。
先贴一下你原来的代码方便对照:
$topic = 'xxxxx'; $projectId = 'xxxx'; $title = 'hahaha'; $content = 'lol'; $payload = array( 'message' => array( 'topic' => $topic, 'notification' => array( 'title' => $title, 'body' => $content, ) ) ); $json = json_encode($payload); $headers = array( 'Authorization:Bearer '.$this->getFcmApiAccessToken(), 'Content-Type: application/json; UTF-8', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding: gzip, deflate', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8', 'Cache-Control: max-age=0', 'Cookie: hrg_session=30d4sk8ue8jbe795m3aavrgv97ds4n9g', 'Host: test.hrgame.com.hk', 'Proxy-Connection: keep-alive', 'Upgrade-Insecure-Requests: 1', 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36', ); print_r($headers); $ch = curl_init(); curl_setopt($ch,CURLOPT_URL,"https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch,CURLOPT_POST, true ); curl_setopt($ch,CURLOPT_HEADER, 1); curl_setopt($ch,CURLOPT_HTTPHEADER,$headers); curl_setopt($ch,CURLOPT_RETURNTRANSFER, true); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch,CURLOPT_POSTFIELDS, $json); curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($ch); curl_close($ch); var_dump($result);exit;
为什么本地和浏览器调用结果不一样?Nginx起了什么作用?
这俩场景的核心差异是请求发起的环境和路径不同:
- 本地直接跑脚本:是你的PHP-Curl直接向FCM接口发请求,所有请求头、请求体都是你代码里配置的,没有中间层干预。
- 浏览器访问主机脚本:是浏览器先向你的Nginx服务器发请求,Nginx转发给PHP-FPM执行脚本,脚本里的Curl请求是从Nginx所在的服务器发起的。
Nginx在这里的作用不是直接帮你处理FCM的请求,而是服务器环境的默认配置帮你补全了Curl的正确行为:
服务器端的Curl可能默认开启了自动处理Content-Length的配置,或者PHP-FPM/Nginx的环境变量让Curl能正确计算请求体长度并添加头;而你本地的PHP-Curl环境可能因为某些配置(比如旧版本、自定义的php.ini设置)导致自动添加Content-Length的功能失效,或者你代码里的多余头干扰了这个逻辑。
为什么复制浏览器请求头没用?
你复制的是浏览器发给你自己服务器的请求头,和Curl发给FCM的请求头完全是两回事!比如Cookie、Host: test.hrgame.com.hk、Upgrade-Insecure-Requests这些都是浏览器和你的服务器交互用的,FCM接口根本不需要这些头,你把它们加到Curl里反而会让请求格式混乱,甚至被FCM的服务器判定为无效请求。
你的代码到底缺了什么?怎么修复?
问题主要出在多余请求头干扰和没有让Curl自动处理Content-Length上,修复方法很简单:
修正后的代码
$topic = 'xxxxx'; $projectId = 'xxxx'; $title = 'hahaha'; $content = 'lol'; // 构造FCM需要的消息体 $payload = array( 'message' => array( 'topic' => $topic, 'notification' => array( 'title' => $title, 'body' => $content, ) ) ); $json = json_encode($payload); // 只保留FCM必须的请求头!多余的全部删掉 $headers = array( 'Authorization: Bearer ' . $this->getFcmApiAccessToken(), 'Content-Type: application/json; charset=utf-8' ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"); curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 生产环境建议开启SSL验证,测试阶段可以关闭 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); // 重点:不要手动设置Content-Length!Curl会自动根据POST内容计算并添加 // 除非你确定自己能算对长度,否则绝对不要手动加 $result = curl_exec($ch); curl_close($ch); var_dump($result); exit;
额外注意事项
- 不要手动设置Content-Length:Curl会自动根据
CURLOPT_POSTFIELDS的内容计算长度,手动设置很容易出错(比如忘记算json_encode后的字符串长度,或者编码差异导致长度不对)。 - 检查JSON编码是否成功:如果
json_encode($payload)返回false,POST内容为空,也会触发411错误,可以用json_last_error()排查编码问题。 - 生产环境开启SSL验证:
CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST设为false只是测试用,正式环境一定要设为true,避免安全风险。 - 确保AccessToken有效:411错误有时候也可能是AccessToken无效导致的间接问题,建议先验证
getFcmApiAccessToken()返回的Token是否正确。
内容的提问来源于stack exchange,提问作者dabeige

