JSON、表单与图片接口的响应设计:先定契约,再定字段
响应设计应先固定媒体类型、状态码和错误体,再确定字段。JSON 接口应使用稳定的机器码和字段级错误;表单接口要区分浏览器导航与脚本调用,成功可 303,验证失败可 422;图片接口成功时返回二进制响应,并带上 Content-Type、ETag、Cache-Control,失败时按协商返回结构化错误,不要用 200 伪装成功。
- 先定契约:媒体类型、状态码语义、错误体形状、缓存策略,最后才定字段命名。
- 图片接口要用 Vary: Accept 区分格式,随机图片要避免被共享缓存固定为同一张。
- 示例均为演示结构,未实际执行;具体框架行为需自行核实。
为 JSON、表单、图片三类接口设计响应时,最容易犯的错误是先定义字段,后补状态码。更稳妥的顺序是:先确定契约,即媒体类型、状态码语义、错误体形状、缓存与协商策略;字段命名是最后一步。下面按三类接口分别说明,示例只用于演示结构,未实际执行。
三类接口共享的响应契约#
- 一个端点只承诺一种主表示,用
Content-Type声明实际类型,用Accept协商可选类型。 - 失败要反映在 HTTP 状态码上,不要把业务失败写成
200加{"code":500}。否则缓存、网关、监控和客户端重试都会判断错误。 - 错误体保持固定形状:稳定机器码、可读消息、可选字段级错误、请求标识。机器码面向程序,消息面向人。
JSON 接口:稳定错误体与明确的缺省语义#
JSON 响应建议区分“成功数据”和“错误”两种顶层结构,不要让客户端从字段是否存在中反推成败。错误体可以采用接近 application/problem+json 的形状:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://example.com/problems/validation-error",
"title": "Validation failed",
"status": 422,
"code": "VALIDATION_ERROR",
"errors": [
{ "field": "email", "code": "INVALID_FORMAT", "message": "邮箱格式不正确" }
],
"request_id": "req_demo_0001"
}
其中 request_id 与 type 均为占位演示值。code 一旦发布就应视为契约,不随文案调整而改名。
另外三个边界条件值得提前规定:null 与字段缺省的区别(有值、无值、未请求);数组字段返回空数组还是 null;时间统一为带时区的字符串(例如 RFC 3339 形式)而非本地时间。分页建议同时给出数据和游标或总数,避免客户端靠“返回条数小于页大小”判断末页。
表单接口:区分浏览器导航与脚本调用#
表单提交有两种调用者:浏览器页面导航和脚本。它们的期望不同,响应设计要显式区分。
- 成功时,对浏览器导航使用
303 See Other加Location,让刷新不会重复提交。 - 验证失败时返回
422,并附带字段级错误;浏览器场景可重新渲染表单(不回填密码等敏感字段),脚本场景返回结构化错误体。 - 对会产生副作用的提交,用幂等键或一次性令牌防止重复执行。
POST /subscribe HTTP/1.1
Content-Type: application/x-www-form-urlencoded
HTTP/1.1 303 See Other
Location: /thanks
上述交互为演示,未在具体框架中验证。若项目采用前端渲染,则表单接口更接近 JSON 接口,此时应统一错误体,不要维护两套形状。
图片接口:二进制响应要带足元数据#
图片成功的响应本身就是二进制,字段说明只能放进响应头。建议至少覆盖媒体类型、缓存策略、实体标识和协商维度。
HTTP/1.1 200 OK
Content-Type: image/webp
ETag: "demo-etag-1"
Cache-Control: public, max-age=86400
Content-Length: 12345
Vary: Accept
以上数值均为占位示例,非实测结果。要点有两个:
- 用
Accept协商格式时,必须回Vary: Accept,否则共享缓存可能把 WebP 响应发给只接受 JPEG 的客户端。 - 随机图或按用户生成的图片,不建议用长 TTL 的
public缓存,否则同一 URL 会被固化成一张图;可考虑短 TTL 或no-store,具体取值按业务核实。
失败时不要返回 200 加一张“占位图”,除非接口文档明确承诺这一行为。更清晰的做法是按协商返回结构化错误体或 HTML 错误页,并在响应头声明真实媒体类型与状态码。
缓存与协商是响应设计的一部分#
| 接口类型 | 成功状态 | 错误体 | 缓存关注点 |
|---|---|---|---|
| JSON | 200 / 201 | problem+json 等结构化体 | 私有数据用 private,公开数据按时效定 TTL |
| 表单 | 303 重定向或 200 重渲染 | 422 + 字段级错误 | 一般 no-store,避免缓存提交结果 |
| 图片 | 200 二进制 | 按协商返回结构化错误 | ETag、Vary、Cache-Control 三者配套 |
这张表是通用建议,不是某一框架或规范的要求。422 的具体语义与客户端兼容性,建议结合所用 HTTP 版本和客户端库核实后再写入契约。响应设计最终要落到一份可核对的文档上:状态码清单、错误码清单、示例响应、缓存规则,缺一项都会在联调时变成猜测。