php用curl发json必须设content-type: application/json和accept: application/json,手动json_encode后传字符串给curlopt_postfields,并校验编码结果、检查http状态码。

PHP用cURL发JSON数据,Content-Type必须设对
不设或设错 Content-Type,后端大概率收不到 $_POST 或解析为空。PHP默认把JSON当表单提交,服务端按 application/x-www-form-urlencoded 解析,自然失败。
正确做法是显式设置为 application/json:
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json'
]);
- 只设
Content-Type不够,加Accept能避免某些网关(如Nginx、Cloudflare)拦截“非标准”请求头 - 别用
application/json; charset=utf-8—— 多数API不认分号后的参数,反而触发415错误 - 如果后端是ThinkPHP/Laravel等框架,没设对这个头,
request()->json()或file_get_contents('php://input')可能读不到原始JSON
json_encode()前必须检查数据结构和编码
json_encode() 返回 false 却没判空,发出去的就是空字符串或乱码,接口直接报400。常见原因不是语法错,而是中文、资源句柄、循环引用这些隐性问题。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 传参前先跑一遍:
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); - 立刻检查:
if ($json === false) { throw new Exception('JSON encode failed: ' . json_last_error_msg()); } - 特别注意:数组里混了
resource(比如未关闭的文件句柄)、NaN、INF,json_encode()会静默失败 - 数据库查出的字段含MySQL的
TINYINT(1),PHP可能转成布尔,而有些API拒绝布尔值——提前用(int)强转更稳妥
POST body不能靠CURLOPT_POSTFIELDS自动序列化
直接传数组给 CURLOPT_POSTFIELDS,cURL会把它当 application/x-www-form-urlencoded 发,即使你设了JSON头也没用。这是最常踩的坑。
必须手动转成字符串再传:
$data = ['name' => '张三', 'score' => 95]; $json = json_encode($data); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); // 注意:这里是字符串,不是数组
- 别写
curl_setopt($ch, CURLOPT_POSTFIELDS, $data)—— 看似省事,实际发的是键值对,不是JSON - 如果用了
http_build_query()包一层,结果是name=%E5%BC%A0%E4%B8%89&score=95,后端收到的是URL编码字符串,不是JSON对象 - 大JSON体(>1MB)要考虑内存:用
fopen('php://temp', 'r+')写入再传流,避免一次性加载进内存
调试时别只看curl_exec()返回值
curl_exec() 返回空字符串,不等于请求失败;返回一串JSON,也不代表业务成功。很多API在HTTP 200下仍返回错误体(比如 {"code":401,"msg":"token expired"})。
- 务必配合
curl_getinfo($ch, CURLINFO_HTTP_CODE)检查真实状态码 - 开启错误输出:
curl_setopt($ch, CURLOPT_FAILONERROR, true),让4xx/5xx直接抛异常 - 抓包验证最准:用
curl_setopt($ch, CURLOPT_VERBOSE, true)+fopen('php://stderr'),看实际发出的请求头和body长啥样 - 线上环境禁用
verbose,但至少保留CURLINFO_HEADER_OUT记录请求头,方便出问题时回溯
真正难的不是拼出JSON,是确保它从PHP内存出发,穿过cURL、TCP栈、代理、防火墙,最后被对方API按预期解析——每个环节都可能悄悄改掉你的数据。别信“应该没问题”,每个header、每个encode、每个error check,都得亲手验过。










