使用Guzzle发起POST请求持续返回400 Bad Request求助
排查并解决Guzzle POST请求400 Bad Request错误
核心错误点分析
你的请求返回400,主要是以下几个明显问题导致:
- Host头不匹配:请求目标域名是
my-uat.testing.my,但你在headers里硬编码了secure.example.com,服务器会判定请求的域名非法,直接返回400。 - POST参数格式完全错误:
application/x-www-form-urlencoded要求参数用&分隔键值对,你却用|分隔(比如TEST_RETURN_URL=xxx|TEST_RETURN_URL2=xxx),服务器无法解析这种格式。 - 手动设置Content-Length易出错:
strlen($sPost)计算的是字符串长度,若参数包含中文或特殊字符,字节数会和实际传输的不一致,Guzzle会自动处理这个头,不需要手动设置。 - 参数未URL编码:如果参数值里有特殊字符(比如&、=、空格),会破坏表单格式,必须对每个参数值做URL编码。
修正后的代码
public function sendcs($chk_arr) { $TRX_TEST_RETURN_URL = $chk_arr['TEST_RETURN_URL']; $TRX_TEST_RETURN_URL2 = $chk_arr['TEST_RETURN_URL2']; $TRX_TEST_TRANSACTION_ID = $chk_arr['TEST_TRANSACTION_ID']; $TRX_TRX_PRODUCT_ID = $chk_arr['TRX_PRODUCT_ID']; $TRX_CS_PAYMENT_METHOD = 'CS'; $TRX_XYZ_TEST_ID = $chk_arr['XYZ_TEST_ID']; $TRX_TEST_TRANSACTION_TIME = $chk_arr['TEST_TRANSACTION_TIME']; // 生成签名原始字符串(确保和接口要求的拼接规则一致) $strParts = [ trim($TRX_TEST_RETURN_URL), trim($TRX_TEST_RETURN_URL2), trim($TRX_TEST_TRANSACTION_ID), trim($TRX_TRX_PRODUCT_ID), trim($TRX_CS_PAYMENT_METHOD), trim($TRX_XYZ_TEST_ID), trim($TRX_TEST_TRANSACTION_TIME) ]; $str = implode('|', $strParts); // 加载私钥并生成签名 $file = "mchnts/" . env('WEB_XYZ_TEST_ID') . '/privatekey.pem'; if (!Storage::disk('local')->exists($file)) { abort(403); } $certloc = Storage::disk('local')->get($file); $key = openssl_pkey_get_private($certloc); // 注意:确认接口要求的签名算法,例如SHA256,默认是SHA1 openssl_sign($str, $signature, $key, OPENSSL_ALGO_SHA256); $checkSum = strtoupper(bin2hex($signature)); // 构造正确的表单参数(用&分隔,参数值URL编码) $postData = [ 'TEST_RETURN_URL' => urlencode($TRX_TEST_RETURN_URL), 'TEST_RETURN_URL2' => urlencode($TRX_TEST_RETURN_URL2), 'TEST_TRANSACTION_ID' => urlencode($TRX_TEST_TRANSACTION_ID), 'TRX_PRODUCT_ID' => urlencode($TRX_TRX_PRODUCT_ID), 'XYZ_TRANS_TYPE' => urlencode($TRX_CS_PAYMENT_METHOD), 'XYZ_TEST_ID' => urlencode($TRX_XYZ_TEST_ID), 'TEST_TRANSACTION_TIME' => urlencode($TRX_TEST_TRANSACTION_TIME), 'XYZ_TRX_SIGNATURE' => urlencode($checkSum) ]; // 发起Guzzle请求 $client = new Client(); $url = "https://my-uat.testing.my/in"; try { $response = $client->post($url, [ 'headers' => [ // 去掉错误的Host头,Guzzle会自动从URL提取 'Accept' => '*/*', ], 'verify' => false, // 仅测试环境使用,生产环境必须开启SSL验证 'form_params' => $postData, // 直接传数组,Guzzle自动处理编码和头 ]); $body = $response->getBody()->getContents(); return $body; } catch (\GuzzleHttp\Exception\ClientException $e) { // 捕获错误,打印完整响应方便排查 echo $e->getResponse()->getBody()->getContents(); throw $e; } }
额外排查建议
- 确认签名算法:联系接口方确认要求的签名哈希算法(如SHA256),默认的
openssl_sign用的是SHA1,若接口要求其他算法必须指定。 - 验证签名原始字符串:打印
$str确认拼接的内容、分隔符是否和接口文档完全一致,多一个或少一个|都会导致签名无效。 - 开启错误日志:捕获Guzzle的异常时,打印完整的响应内容,很多服务器会在400响应里返回具体的错误原因(比如参数缺失、签名无效)。
- 测试签名有效性:可以用接口方提供的测试工具验证你的签名是否正确,排除签名导致的400。
内容的提问来源于stack exchange,提问作者newdevhere1
相关产品推荐
相关产品推荐

