POST 重定向后正文去哪了:实测 303、307 和 308
网关迁移提交接口后,上游一直收到 GET,调用方却记得自己发的是 POST。先关闭自动重定向看第一跳,再按状态码重放请求。303 用于将客户端引向另一个可读取的资源,307 和 308 保留方法与请求体。
1. 在同一个服务里记录跳转前后
保存为 redirect_lab.mjs,运行 node redirect_lab.mjs。
import assert from 'node:assert/strict';
import { once } from 'node:events';
import { createServer } from 'node:http';
const server = createServer(async (req, res) => {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const body = Buffer.concat(chunks).toString('utf8');
if (req.url === '/result') {
res.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify({ method: req.method, body }));
return;
}
const status = Number(req.url.slice(1));
if (![302, 303, 307, 308].includes(status)) {
res.writeHead(404).end();
return;
}
res.writeHead(status, { Location: '/result' }).end();
});
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const origin = 'http://127.0.0.1:' + server.address().port;
try {
for (const status of [302, 303, 307, 308]) {
const url = origin + '/' + status;
const options = { method: 'POST', body: 'quantity=2' };
const first = await fetch(url, { ...options, redirect: 'manual' });
assert.equal(first.status, status);
assert.equal(first.headers.get('location'), '/result');
await first.arrayBuffer();
const final = await fetch(url, options);
assert.equal(final.status, 200);
const received = await final.json();
assert.deepEqual(received,
status < 307 ? { method: 'GET', body: '' } : { method: 'POST', body: 'quantity=2' });
console.log(status, '=>', received.method, JSON.stringify(received.body));
}
} finally {
server.close();
}
本机实测输出如下。
302 => GET ""
303 => GET ""
307 => POST "quantity=2"
308 => POST "quantity=2"
2. 以业务动作选择重定向
| 业务场景 | 选择 |
|---|---|
| POST 已经创建成功,要跳到结果页 | 303,客户端改用 GET 读取结果 |
| 接口临时迁移,目标端接管这次 POST | 307,转发目标继续收取相同方法和正文 |
| 接口永久迁移,希望保留请求方法 | 308,并检查客户端和缓存会如何处理永久重定向 |
RFC 9110 允许客户端在 302 后把 POST 改为 GET。本机 Node fetch 选择了改写,因此接口迁移不能依赖 302 保留正文。重放上传时,字符串可以再次读取,流式请求体则可能无法重放。先测试客户端库能否自动跟随,再核对跨域鉴权头与实际路由。
小结
日志分别记录第一跳状态和最终收到的方法。如果还要验证正文,在测试端捕获并比较正文长度或测试字符串。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| RFC 9110 §15.4 | https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4 | 不同重定向状态的语义 |
| WHATWG Fetch | https://fetch.spec.whatwg.org/#http-redirect-fetch | 方法改写、正文重放和跨来源规则 |
核验说明
- Node.js v24.19.0 对四种状态码实测。请求体为可重放字符串,使用本地同源服务。