PHP用cURL发POST请求的关键是CURLOPT_POSTFIELDS值类型决定Content-Type:传数组或http_build_query结果→application/x-www-form-urlencoded,服务端用$_POST接收;传JSON字符串→须手动设Content-Type: application/json,否则需读php://input。

直接调用 curl_init() + curl_setopt() 是最常用、最可控的方式。关键不是“能不能发”,而是参数设对没设对——尤其是 CURLOPT_POSTFIELDS 的值类型,它直接决定服务端收到的是表单数据还是原始 JSON。
['name' => 'Alice', 'age' => 25])→ 自动设 Content-Type: application/x-www-form-urlencoded,服务端用 $_POST 接收json_encode(['name'=>'Alice']))→ 必须手动加 Content-Type: application/json,否则 PHP 后端收不到 $_POST,得读 php://input
http_build_query() 结果 → 和传数组效果一致,但更显式,适合需要控制编码或含特殊字符的场景curl_setopt($ch, CURLOPT_URL, 'https://api.example.com/login'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, ['username' => 'test', 'password' => '123']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch);
CURLOPT_HTTPHEADER 看似简单,但几个细节极易踩坑:一是 header 数组必须是字符串数组(['Content-Type: application/json']),不能是关联数组;二是如果用了 CURLOPT_POSTFIELDS 数组,cURL 会自动加 Content-Type,覆盖你手动设的;三是某些服务器(如 Nginx)会过滤掉带下划线的 header 名,比如 X-Request-ID 没问题,X_Request_ID 可能被静默丢弃。
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json'])
CURLOPT_HTTPHEADER 数组里,不要拆开调用curl_setopt($ch, CURLOPT_HEADER, true) 查看真实发出的请求头线上环境不设超时等于裸奔,而只设 CURLOPT_TIMEOUT 不够——它控制整个请求周期,但 DNS 解析、TCP 连接、SSL 握手这些前期耗时可能卡死在 30 秒以上。真正健壮的做法是分开设 CURLOPT_CONNECTTIMEOUT_MS(毫秒级)和 CURLOPT_TIMEOUT_MS(推荐都用 *_MS 版本)。
curl_init() + curl_close():复用 $ch 句柄,用 curl_reset($ch) 清除上次状态,减少系统资源开销curl_exec() 返回值:false 表示出错,必须配合 curl_error($ch) 和 curl_errno($ch) 定位(比如 CURLE_COULDNT_RESOLVE_HOST 是 DNS 问题,不是网络不通)CURLOPT_SSL_VERIFYPEER 和 CURLOPT_SSL_VERIFYHOST 为 true,但测试环境可临时关掉避免证书报错可以,但限制极多。它依赖 allow_url_fopen=On(很多生产环境禁用),且配置 header 和超时只能靠 stream_context_create(),写法冗长、错误提示模糊,连重定向都得手动处理。唯一优势是不用装 cURL 扩展。
400 Bad Request 或空响应,基本没法快速定位是 header 缺失、JSON 格式错,还是编码问题$opts = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => json_encode(['id' => 123])
]
];
$result = file_get_contents('https://api.example.com/item', false, stream_context_create($opts));
实际项目里,95% 的 POST 场景用 cURL 更稳。真正容易被忽略的,是 CURLOPT_POSTFIELDS 类型与 Content-Type 的严格对应关系——传错类型,后端就收不到数据,还查不出错在哪。