命令行cURL请求成功,但PHP curl_exec返回504网关超时的问题排查
问题场景
我尝试用PHP的curl_setopt发送请求获取数据,但请求加载2分钟后返回504 Gateway Timeout错误,PHP代码如下:
function sendRequest($url, $data, $token = '') { $header[] = "Connection: keep-alive"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_HTTP_VERSION, 'CURL_HTTP_VERSION_1_1'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_VERBOSE, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36"); $result = curl_exec($ch); curl_close($ch); return $result; }
奇怪的是,使用命令行CURL设置相同参数时,能立即成功获取数据:
curl --url "http://x.x.x.x" --header "Connection: keep-alive" --http1.1 --verbose --request "POST" -d "data1=value1" -d "data2=value2" --user-agent "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36"
部署代码的服务器与目标服务器处于同一网络,且两者均未启用防火墙,请问该故障的原因可能是什么?
可能的故障原因
POST数据格式不匹配:命令行用
-d传递的是application/x-www-form-urlencoded格式的键值对,而PHP中如果$data是数组,curl会自动将请求编码为multipart/form-data,目标服务器可能无法解析这种格式,导致请求挂起超时。解决办法是用http_build_query($data)把数组转成表单编码字符串后再传入CURLOPT_POSTFIELDS。CURLOPT_CUSTOMREQUEST的冗余设置冲突:当设置
CURLOPT_POSTFIELDS时,curl会自动把请求方法设为POST,并添加对应Content-Type头部。手动设置CURLOPT_CUSTOMREQUEST = 'POST'会干扰curl的自动处理逻辑,可能导致请求头部不完整,目标服务器无法正确响应。解决办法是删除这一行代码。IPv6解析导致的延迟:PHP curl默认可能优先尝试IPv6连接,若目标服务器没有配置IPv6地址,会等待IPv6连接超时后再 fallback 到IPv4,这会消耗大量时间。而命令行curl可能默认禁用IPv6或快速切换。解决办法是添加
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);强制使用IPv4解析。Expect头部的延迟问题:PHP curl会自动添加
Expect: 100-continue头部,部分服务器对这个头部的处理会导致请求延迟甚至超时,而命令行curl默认不会发送这个头部。解决办法是在自定义头部中添加$header[] = "Expect: ";来禁用该头部。未查看CURL详细日志:虽然开启了
CURLOPT_VERBOSE,但PHP不会直接输出日志内容,无法定位请求过程中具体卡住的环节。可以添加日志输出代码,查看连接、发送、接收的详细过程:
$verboseLog = fopen('php://temp', 'w+'); curl_setopt($ch, CURLOPT_STDERR, $verboseLog); // 执行请求后读取日志 rewind($verboseLog); $verboseOutput = stream_get_contents($verboseLog); fclose($verboseLog); var_dump($verboseOutput);
内容的提问来源于stack exchange,提问作者kneejar

