KernelRanch
返回文章列表

POST 重定向后正文去哪了:实测 303、307 和 308

2026-11-13 #http#redirects#nodejs

网关迁移提交接口后,上游一直收到 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 读取结果
接口临时迁移,目标端接管这次 POST307,转发目标继续收取相同方法和正文
接口永久迁移,希望保留请求方法308,并检查客户端和缓存会如何处理永久重定向

RFC 9110 允许客户端在 302 后把 POST 改为 GET。本机 Node fetch 选择了改写,因此接口迁移不能依赖 302 保留正文。重放上传时,字符串可以再次读取,流式请求体则可能无法重放。先测试客户端库能否自动跟随,再核对跨域鉴权头与实际路由。

小结

日志分别记录第一跳状态和最终收到的方法。如果还要验证正文,在测试端捕获并比较正文长度或测试字符串。

数据来源与检索时间

检索时间 2026-11-13。

来源URL对应内容
RFC 9110 §15.4https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4不同重定向状态的语义
WHATWG Fetchhttps://fetch.spec.whatwg.org/#http-redirect-fetch方法改写、正文重放和跨来源规则

核验说明

  • Node.js v24.19.0 对四种状态码实测。请求体为可重放字符串,使用本地同源服务。