明确重定向、缺失路由和允许的方法
本文目录
请智能体“修好旧 URL”之前,先列出这些 URL 会收到哪些请求。迁移后的阅读页面、POST 端点和未知路径,需要不同的结果。为每个请求写明状态码、适用时的目标地址,以及方法、请求体和查询参数的处理规则。
重定向是应用契约的一部分。RFC 9110 定义了 HTTP 状态语义;MDN 重定向指南 概括了方法的处理行为。这些来源不会替你决定哪些查询参数应该在迁移后保留。请把这个决定与状态码一起写进任务说明。
以下示例是虚构的。其中的路径、请求体和预期响应都是教学材料,既不是已部署的配置,也不是 HTTP 测试结果。
先说明路由清单
下面所有请求都以 https://example.invalid 为基准地址。路径均为精确匹配,且区分大小写。不带末尾斜杠的 /guide 是明确列出的别名;这里没有为所有未知路径自动补斜杠的规则。
| 路径 | 支持的方法 | 成功时的结果 |
|---|---|---|
/guide/ |
GET、HEAD | 200;终点指南资源 |
/render/ |
POST | 200;终点,为虚构的只读渲染操作 |
/result/r7/ |
GET、HEAD | 200;终点结果资源,在本组示例数据中存在 |
/old-guide/ |
GET、HEAD | 308,转到 /guide/ |
/guide |
GET、HEAD | 308,转到 /guide/ |
/old-render/ |
POST | 308,转到 /render/ |
/preview-guide/ |
GET、HEAD | 307,转到 /guide/ |
/send-preview/ |
POST | 303,转到 /result/r7/ |
已知路径收到不在其列表中的方法时,返回 405,并用 Allow 响应头精确列出该列表中的方法。本示例不假定另有 OPTIONS 处理器。如果框架提供了这样的处理器,就把它纳入实际清单,并相应修改 Allow。
表外路径返回 404。对于这个虚构应用,这表示没有匹配的路由。在更广泛的 HTTP 语义中,404 也可能表示服务器不愿披露某个表示是否存在;它本身不能证明资源已永久移除。这里的错误响应没有 Location;根据本契约,未知路径的响应也没有 Allow。
先检查方法,再应用别名规则。因此,向仅支持 POST 的 /old-render/ 发出 GET,会得到 405,而不是被重定向到另一个仅支持 POST 的端点。向 /guide/ 发出 POST,也会得到 405;把它改成 GET,会掩盖原本不受支持的操作。
根据下一次请求必须做什么来选择重定向
旧指南和旧渲染别名计划永久迁移,因此本练习选择 308。临时预览别名使用 307。跟随这两种重定向时,客户端必须保留请求方法;这里的 POST 示例还会保留请求体。RFC 9110 §§15.4.8–15.4.9 和 MDN 表格说明了这些区别。
向 /send-preview/ 发出的 POST 有另一个目的:引导客户端获取选定的结果资源。它的 303 响应选择后续发出 GET,不携带原 POST 请求体。RFC 9110 §15.4.4 允许使用 GET 或 HEAD 发起获取请求;这里展示的 GET,是针对这个 POST 场景作出的应用选择,并不是说每个 303 都会把所有方法转换成 GET。
需求是保留 POST 时,不要仅因熟悉就选择 301 或 302。它们历史上可能改变方法的行为,会让预期契约不够清楚。这里选择 308,是为了同时表达永久迁移和方法保留。但它本身不能证明重放某个应用操作是安全的。
虚构的 /render/ 操作是只读的,便于示例集中说明路由。真实的写入端点需要另外说明副作用、重试和重复提交。状态码不能替代这些设计。
单独说明查询参数处理
选定的重定向策略有意限定得很窄:
- 指南目标地址保留一个
lang参数,其值为en或zh。 - 渲染目标地址保留一个
mode参数,其值为short或full。 - 结果目标地址不保留任何查询参数。
- 构造重定向目标时,丢弃其他键,包括虚构的
campaign键。
矩阵只使用单个有效值。对于重复键、lang 或 mode 的不受支持值、错误转义,以及其他编码情况,必须在实施之前补充决定。不要从这些示例中自行推导其行为。
规范端点不会仅仅为了移除多余的查询键而重定向:在本契约中,它们忽略未列出的键。它们接受的 lang 和 mode 有效值,与对应别名相同。对于这里展示的简单保留值,下文会写出精确的 Location。真实实现应使用平台提供的 URL 处理工具,并检查最终编码,而不是拼接任意输入。
这些查询规则属于这个应用。307 或 308 不会自动替你过滤追踪参数,也不会自动保留语言偏好。
逐项核对完整请求
表中的所有请求目标和 Location 值,都相对于前述基准地址书写。因此,Location 列中的 /guide/?lang=zh,表示响应头的精确绝对值 https://example.invalid/guide/?lang=zh。破折号表示该列所指的响应头、请求体或后续请求不存在。POST 请求体按原样展示,为 UTF-8 JSON 字符串。
| ID | 方法与目标 | 请求体 | 状态码 | Location | Allow | 后续请求的方法与请求体 |
|---|---|---|---|---|---|---|
| r01 | GET /old-guide/?lang=zh&campaign=demo |
— | 308 | /guide/?lang=zh |
— | GET;无请求体 |
| r02 | HEAD /old-guide/?lang=en |
— | 308 | /guide/?lang=en |
— | HEAD;无请求体 |
| r03 | POST /old-render/?mode=short&campaign=demo |
{"text":"Hello"} |
308 | /render/?mode=short |
— | POST;请求体完全相同 |
| r04 | GET /preview-guide/?lang=zh&campaign=demo |
— | 307 | /guide/?lang=zh |
— | GET;无请求体 |
| r05 | HEAD /preview-guide/ |
— | 307 | /guide/ |
— | HEAD;无请求体 |
| r06 | POST /send-preview/?campaign=demo |
{"text":"Preview"} |
303 | /result/r7/ |
— | GET;无请求体 |
| r07 | GET /missing/?lang=zh |
— | 404 | — | — | 无 |
| r08 | POST /guide/?lang=zh |
{"text":"Hello"} |
405 | — | GET, HEAD |
无 |
| r09 | GET /old-render/ |
— | 405 | — | POST |
无 |
| r10 | GET /guide?lang=en&campaign=demo |
— | 308 | /guide/?lang=en |
— | GET;无请求体 |
| r11 | GET /guide/?lang=zh |
— | 200 | — | — | 无 |
| r12 | HEAD /guide/?lang=zh |
— | 200 | — | — | 无 |
| r13 | POST /render/?mode=short |
{"text":"Hello"} |
200 | — | — | 无 |
| r14 | GET /result/r7/ |
— | 200 | — | — | 无 |
| r15 | GET /send-preview/ |
— | 405 | — | POST |
无 |
| r16 | POST /unlisted/ |
{"text":"Hello"} |
404 | — | — | 无 |
表中每个后续请求都会到达返回 200 的终点。根据 RFC 9110 §9.3.2,所有 HEAD 响应都不包含响应内容,包括重定向响应。本示例并不要求其他方法的重定向响应内容为空。这里仅固定状态码、Location、Allow 和指定的后续请求行为;其他响应头与错误页措辞,仍是独立决定。
“后续请求”表示客户端选择跟随重定向时,预期发出的下一次请求。它既不声称某个浏览器、库或代理会自动跟随,也不定义它会转发哪些凭据和其他请求头。
使用同一份清单防止回环
这里有三个终点目标:/guide/、/render/ 和 /result/r7/。在这组示例数据中,它们都不会对受支持的请求再次重定向。所有别名都直接指向其中一个终点,因此表中成功的流程最多只需要一次重定向。
相比两条互相独立的规则,例如“到处添加末尾斜杠”和“从规范 URL 移除末尾斜杠”,这样的小型关系图更容易复核。如果那两条规则的范围重叠,同一个请求可能被反复来回跳转。
在真实应用中实施契约之前,要把中间件、代理规则和应用路由放在一起检查。纸面矩阵没有回环,并不能证明其他层不会添加一个回环。除了路径,还要检查最终方法:保留下来的 POST,必须到达支持 POST 的资源。
不要为了让错误数量消失,就把所有未知路径都送到首页。这样会用一个无关的目标替换明确的缺失路由结果,也会抹掉矩阵原本要保留的区别。
复制这份调查请求
审阅给定的虚构路由清单和全部 16 个请求。
不要访问 example.invalid 或任何线上服务。返回拟议的
路由契约;这里不要求修改服务器、代理、DNS 或生产环境。
使用精确且区分大小写的路径。先检查支持的方法,再处理别名。
已知路径 + 不支持的方法:返回 405,并用 Allow 精确列出路由允许的方法。
未知路径:返回 404,不带 Location;不要把未知路径重定向到首页。
将 HTTP 中 404 的更广泛含义,与这组路由示例数据区分开。
永久指南和渲染别名使用 308,临时 preview-guide 别名使用 307,
POST 转到结果资源获取流程使用 303。
渲染的 308 重定向必须保留 POST 及其完全相同的 UTF-8 请求体。
对选定的 POST 303 场景,展示 GET,且不带原 POST 请求体。
307/308 保留 HEAD;HEAD 响应不包含内容。
明确构造每个 Location。指南别名保留 lang=en|zh;
渲染别名保留 mode=short|full;结果重定向不保留查询参数。
重定向时丢弃其他键。规范端点忽略多余键,
不因此重定向。保留键重复、保留键值不受支持,
以及编码错误,都需要补充需求,不能猜测。
为每一行返回状态码、Location 或其缺席、Allow 或其缺席、
后续请求的方法与请求体或无后续请求,以及跟随后预期的终点状态码。
核对每个重定向目标都支持抵达的方法,且在本清单中是终点。
列出框架、代理和客户端行为中的未知项。
将人工指定的预期与实际 HTTP 检查分开。
这些教学示例没有进行过 HTTP 测试。
一份可供审阅的重定向请求,会告诉智能体哪些旧行为要保留、哪些行为要改变,以及哪些请求仍应失败。路由矩阵让这些决定在变成配置之前就清楚可见。