题干与适用场景
一个线上 API 的资源路径要永久从 /v1/orders 迁移到 /v2/orders。调用方既有浏览器表单,也有移动端、第三方 SDK 和异步任务;请求可能是 POST,带有较大的 JSON 请求体和幂等键。请设计迁移期间的状态码、客户端行为、观测指标和回滚路径。
这道题考查 HTTP 重定向语义是否被准确理解,以及 API 迁移能否避免重复副作用。RFC 9110 定义了 308 Permanent Redirect;MDN 对 308 的说明强调,客户端在新地址重发时不应修改原请求方法和请求体。301 是永久重定向,但历史客户端在非 GET 请求上的处理存在方法改变的兼容差异。
面试官考察点
面试官会观察你是否先区分永久与临时迁移,再区分是否必须保留方法和请求体。强回答会比较 301、302、307、308,说明为什么带副作用的 POST 不能依赖客户端“猜测”重试,并把幂等键、认证、超时和回滚纳入方案。
还要检查你是否考虑 Location 的可信范围、跨域凭据、缓存传播、SDK 的重定向上限,以及旧客户端不支持 308 时的兼容策略。只背“308 等于 301 加保留 POST”而没有迁移验证,仍然不完整。
回答前需要澄清的问题
迁移的永久性和范围
确认新地址是否已经稳定,以及是否只迁移单个资源、整个 API 前缀还是跨域。若目标仍可能变化,先用 307 表达临时迁移,避免把永久缓存语义过早写入客户端和中间层。
客户端能力与副作用
列出浏览器、移动端版本、SDK、队列消费者和合作方。确认 POST 是否会创建订单、扣款或发送消息,以及调用方是否发送幂等键;无法证明安全重放时,不能把重定向当作无条件重试。
凭据、缓存与回滚
确认新旧域名的认证范围、CORS、代理和 CDN 行为。询问是否允许旧端点继续服务,以及回滚是撤销重定向、切换路由,还是恢复旧版本处理器。
30 秒回答框架
“我先确认迁移是永久还是临时,并盘点所有客户端。对必须保留 POST 方法和请求体的永久迁移,我会优先使用 308;临时迁移用 307。301 不适合作为依赖客户端保留非 GET 语义的契约。旧端点先双写或代理到新处理器,所有副作用使用同一个幂等键,并限制 Location 的域名和凭据转发。灰度期间监控 3xx 跟随率、重复创建、4xx/5xx、请求体大小和 SDK 版本;异常时撤销重定向并保留旧端点处理能力。”
分步骤深入解答
第一步:准确选择状态码
308 表示永久迁移,重定向后的请求方法和请求体保持不变;307 表示临时迁移,也保持方法和请求体。301 表示永久迁移,但历史客户端对 POST 等方法的处理并不都保持原方法,不能把它当作严格的 POST 迁移契约。302 也不应承担这种保证。
第二步:把重放风险当成核心约束
308 可能让客户端再次提交完整请求体,因此新旧端点都要按相同幂等键识别同一次业务命令。服务端在真正执行副作用前校验键与请求摘要;同一键收到不同参数时返回冲突,而不是再次创建订单。超时后的客户端仍需遵守 SDK 的重试预算,不能因为看到 308 就无限跟随。
第三步:分阶段发布与观测
先让新地址直接可用,再让旧地址以可观测的代理或 308 响应导流。按 SDK 版本、来源域名和接口方法做小比例灰度,记录重定向链长度、跟随失败、重复副作用、目标端延迟、认证失败和请求体大小。对不支持 308 的老客户端,可以短期保留旧端点的服务端代理,不要静默改成 301 并假设其行为一致。
第四步:处理安全、缓存和回滚
Location 只能指向经过允许列表校验的目标,跨域跳转前重新评估 Cookie、Authorization 和 CORS,避免把凭据带到不受信任的域。明确 CDN 和客户端缓存的生效范围,先缩短验证窗口,再逐步延长。回滚时先停止发送新重定向,旧端点继续接受相同幂等键;已经写入新端点的结果不能通过简单切回旧路由而“撤销”。
高质量示范回答
我会把它当成协议和迁移题,而不是只选一个数字。永久迁移且必须保留 POST 方法和请求体时选择 308;临时切流选择 307。301 的永久语义适合很多页面地址,但不能给非 GET API 提供我需要的严格方法保留保证。
上线前我让新旧地址都支持同一份认证、请求校验和幂等键协议。旧端点先代理到新处理器并保留完整日志,确保相同键和相同请求摘要只产生一次副作用。随后按客户端版本灰度返回 308,限制目标域名,重新检查跨域凭据,并验证 CDN、SDK 和队列消费者的跟随行为。
监控重定向跟随率、链路长度、重复创建、目标端错误率、认证失败和请求体大小。若老客户端不识别 308,就继续使用旧端点代理,不能偷偷降级成 301。异常时停止 308、保留旧端点和幂等记录,恢复路由后再分析已经完成的业务结果。
常见错误
- 所有迁移都返回 301 → 历史客户端可能改变 POST 方法或丢失请求体 → 对必须保留方法和请求体的永久 API 迁移使用 308,并验证客户端。
- 看到 308 就再次执行副作用 → 重定向、超时和客户端重试可能叠加创建请求 → 使用幂等键、请求摘要和有限重试预算。
- 把 307 当成永久地址 → 临时状态被长期缓存或写进 SDK 配置,后续回滚困难 → 临时切流使用 307,稳定后再决定是否转为 308。
- 忽略 Location 的跨域风险 → Cookie 或 Authorization 可能被带到不受信任目标 → 目标域名允许列表、凭据策略和 CORS 一起验收。
- 只看 3xx 数量 → 不能发现老 SDK 跟随失败或重复业务写入 → 按客户端版本关联跟随率、错误和副作用结果。
追问及应对
追问一:为什么不直接让旧地址返回 200 并在响应体里提示新地址?
这不会让通用客户端、缓存和 SDK 自动迁移,也无法表达资源已经永久移动。可以在过渡期保留旧端点代理,但迁移契约仍应通过明确的状态码和 Location 表达,并记录调用方是否真正切换。
追问二:308 到新域名时能否原样转发 Authorization?
不能默认转发。先判断两个域是否属于同一受信任边界,再让客户端重新获取或显式携带目标域凭据。服务端应拒绝任意用户可控的 Location,避免开放重定向和凭据泄露。
追问三:旧客户端完全不支持 308 怎么办?
保留旧端点的服务端代理或按客户端能力返回兼容响应,直到旧版本退出。代理必须复用请求幂等键并设置截止日期;不能在没有数据的情况下声称所有客户端都会把 308 当成 301 处理。
追问四:回滚是否只需把 308 改回 200?
还要确认新端点已经写入的订单、事件和审计记录。先停止新增重定向并恢复旧端点入口,再让两端读取同一权威状态,避免重复写入或出现新旧版本分叉。回滚完成后通过请求 ID 和幂等键核对已完成副作用。