PHP实现Fire and Forget时Socket请求返回400错误排查
PHP Socket实现Fire and Forget模式返回400错误,Guzzle却正常的问题排查
问题概述
我在PHP中实现Fire and Forget模式时,使用Socket发送请求返回HTTP/1.1 400 Bad Request,但用Guzzle实现相同功能却能正常运行,不确定错误源于请求格式还是权限问题。
Socket请求代码
$parts = parse_url($url); try { $socket = $this->openSocket($parts['host'], ($parts['port'] ?? $parts['scheme'] === 'https' ? 443 : 80)); } catch (Exception $e) { $socket = null; } if (!$socket) { return false; } $jsPostData = json_encode($postData, JSON_THROW_ON_ERROR); $contentLength = strlen($jsPostData); $request = "POST {$parts['path']} HTTP/1.1 "; $request .= "Host: {$parts['host']} "; $request .= "Authorization: Bearer " . $bearerToken . " "; $request .= "Content-Length: {$contentLength} "; $request .= "Content-Type: application/json "; $request .= $jsPostData; fwrite($socket, $request); fclose($socket);
生成的请求示例
POST /my_path HTTP/1.1 Host: my_url Authorization: Bearer my_bearer_token Content-Length: 263 Content-Type: application/json {"event":"...."}
错误响应
HTTP/1.1 400 Bad Request Server: awselb/2.0 Date: Fri, 06 Oct 2023 09:43:17 GMT Content-Type: text/html Content-Length: 220
可正常运行的Guzzle代码
try { $guzzle = new Client(); $guzzle->post($url, [ 'timeout' => 1, 'headers' => [ 'Authorization' => "Authorization: Bearer " . $bearerToken, ], 'form_params' => $postData ]); } catch (\GuzzleHttp\Exception\ConnectException $e) { // do nothing, the timeout exception is intended }
问题原因分析
对比两者的请求,核心差异有两点:
- 请求体格式不匹配:Guzzle使用
form_params时,会以application/x-www-form-urlencoded格式发送请求体;而Socket代码设置的是Content-Type: application/json,发送JSON格式的请求体。服务器仅支持表单编码的参数,因此Socket的JSON格式触发400错误。 - HTTP换行符不标准:Socket代码中使用
\n作为换行,HTTP协议标准要求用\r\n,部分服务器可能兼容,但严格来说属于格式问题。
注:Guzzle代码中Authorization头写了重复的Authorization: 前缀,不过Guzzle内部会自动修正这个错误,因此未影响请求。
修正后的Socket代码
将请求体改为表单编码格式,同时修正HTTP换行符:
$parts = parse_url($url); try { $socket = $this->openSocket($parts['host'], ($parts['port'] ?? $parts['scheme'] === 'https' ? 443 : 80)); } catch (Exception $e) { $socket = null; } if (!$socket) { return false; } // 转换为表单编码格式,和Guzzle的form_params行为一致 $postDataEncoded = http_build_query($postData); $contentLength = strlen($postDataEncoded); // 使用HTTP标准的\r\n换行 $request = "POST {$parts['path']} HTTP/1.1\r\n"; $request .= "Host: {$parts['host']}\r\n"; $request .= "Authorization: Bearer " . $bearerToken . "\r\n"; $request .= "Content-Length: {$contentLength}\r\n"; $request .= "Content-Type: application/x-www-form-urlencoded\r\n\r\n"; $request .= $postDataEncoded; fwrite($socket, $request); fclose($socket);
内容的提问来源于stack exchange,提问作者Giacomo M
相关产品推荐
相关产品推荐

