POST请求 body 为空导致 400 错误问题修复方案
一、问题背景
在系统网关转发(TransmitService → SystemProxy → yii\httpclient)过程中,
发现 POST 请求在 body 为空时会返回 400 Bad Request,而同一接口:
- curl GET 请求正常
- curl POST(无 body)正常
- 直接访问下游服务正常
仅在 通过网关转发 POST + body 为空 的情况下触发 400。
二、问题现象
1. 错误日志表现
Client error: `POST http://localhost:7005/v1/risk-type/get-code-list`
resulted in a `400` response
2. 对比验证
# GET 正常
curl http://localhost:7005/v1/risk-type/get-code-list
# POST(无 body)正常
curl -X POST http://localhost:7005/v1/risk-type/get-code-list
说明 下游接口本身支持 POST 且不强制 body。
三、问题根因分析(关键)
1. 根因定位
问题不在接口逻辑,而在 yii\httpclient 的请求构造方式:
$request->setMethod('POST')
->setContent(null); // 或 setContent('')
在以下场景会触发问题:
method = POST
Content-Type = application/json
Content-Length = 0
body 实际为空
此时 Nginx / PHP-FPM / 部分后端框架 会将其判定为:
非法 POST 请求(JSON POST 却无 body)
从而直接返回 400 Bad Request
2. 为什么 GET 不受影响
GET 请求 不会校验 body
即使设置 Content-Type,也不会解析 request body
因此 GET 天然安全
3. 为什么 curl 不复现
curl 在以下情况下行为不同:
POST 且未指定
-d实际不会发送
Content-Type: application/jsonContent-Length 处理更宽松
而 yii\httpclient 严格构造 HTTP 请求头
四、修复思路(设计原则)
核心原则
POST / PUT / PATCH 请求,在 body 为空时,不应携带 JSON body 语义
换句话说:
body 为空 → 不应强行 setContent
body 为空 → 请求语义应退化为 GET(如果业务允许)
五、最终修复方案(已验证)
1. 网关层兜底修复(TransmitService)
在请求转发前,对 method + body 做兜底判断:
$rawBody = $request->getRawBody();
$method = $request->getMethod();
// POST / PUT / PATCH 且 body 为空时,降级为 GET
if (in_array($method, ['POST', 'PUT', 'PATCH'], true)
&& ($rawBody === '' || $rawBody === null)
) {
$rawBody = null;
$method = 'GET';
}
然后再传给 systemProxy:
$response = Yii::$app->systemProxy->transmit(
$method,
$system->system_domain,
$uri,
$rawBody,
$headers
);
2. systemProxy transmit 方法(保持原逻辑)
public function transmit(
string $method,
string $domain,
string $uri,
?string $rawBody,
HeaderCollection $headers
) {
$this->client->baseUrl = $domain;
$request = $this->client->createRequest()
->setMethod($method)
->setUrl($uri)
->setHeaders($headers)
->setContent($rawBody)
->addOptions(['timeout' => $this->timeout]);
return $request->send();
}
关键点:不在 vendor 层做业务判断,避免后续升级风险
六、修复效果验证
✅ POST + body 为空 → 正常返回
✅ GET → 正常
✅ POST + JSON body → 正常
✅ multipart/form-data 不受影响
✅ 无需修改下游服务
七、风险评估
已规避风险
❌ vendor 目录侵入式修改
❌ 强行 setContent(null) 的隐式副作用
❌ 吞掉 400 错误导致问题隐藏
可接受前提
当前接口语义:POST ≈ 查询
不依赖 POST body 触发业务逻辑
八、结论
本问题本质是:
HTTP 方法语义 + body + Content-Type 不匹配导致的协议级 400
通过在 网关层做 method + body 语义兜底,可以:
保证请求合法性
兼容历史接口
不污染 vendor 代码
为后续统一 API 规范留出空间
九、后续优化建议(非必须)
统一约定:查询类接口使用 GET
POST 强制要求 body(JSON Schema 校验)
在网关增加 request 规范校验日志(非拦截)
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu