JSON、表单与图片接口的响应规范:成功形态、错误契约与缓存头怎么分别定
一句话回答
三类端点的成功响应形态不同:JSON 返回可解析结构,浏览器表单返回重定向或 HTML,图片返回字节流。因此错误契约应按端点类型分别定义:JSON 与 API 表单用结构化错误体,浏览器表单返回 HTML 或重定向,图片端点出错时不得返回图片占位内容并配 200 状态码。HTTP 状态码是唯一的主状态通道,业务 code 不应与之并行表达同一含义。
核心要点 TL;DR
- 成功响应是结构还是字节流,决定了错误响应能否复用同一个壳。
- HTTP 状态码应作为主状态;再叠加业务 code 会让客户端出现两套判断。
- 图片端点出错时返回 JSON 错误体,不要返回占位图加 200。
三类端点不应该共用一套响应格式。JSON 端点的成功体需要可解析的结构,浏览器表单的成功响应往往是一次重定向,图片端点的成功体是字节流。如果强行把它们塞进同一个 {code, msg, data} 包装,每一类调用方都得额外拆一层,图片还要先判断响应到底是不是图片。
先定四条共同约定#
- 状态码是权威结果:HTTP 状态码表达成功、客户端错误或服务端错误,响应体里的业务码只能是补充,不能与它冲突。
- 错误体结构稳定:字段名和类型不随具体错误变化,客户端才能写一个通用分支。
- 内容协商明确:请求的
Accept决定返回 JSON 还是 HTML,服务端不为无法满足的类型伪造 200。 - 缓存语义写进文档:哪些响应可缓存、靠什么校验,要和实际返回的头一致。
JSON 接口:成功体就是资源本身#
成功时直接返回资源或集合,不要在外面再包一层 data。分页信息可以放在响应顶层(如 items 与 next_cursor),也可以用 Link 头,但必须二选一后写进文档。
错误体建议采用 Problem Details 的字段集:type、title、status、detail、instance。相关规范(RFC 9457 等)的编号与最新状态本文未联网核验,落地前请查官方文档。
// 未执行的示例,仅示意字段结构
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://example.com/problems/validation-error",
"title": "Validation failed",
"status": 422,
"detail": "email 字段格式不合法",
"instance": "/v1/users",
"errors": [
{ "field": "email", "code": "invalid_format" }
]
}
表单接口:先分清是谁在提交#
浏览器表单与脚本调用是两种不同的调用方,响应设计不应混用。
- 浏览器提交:成功后用 303 重定向到结果页,避免刷新重复提交;校验失败返回 422 并渲染带字段级错误的页面,而不是跳转到一个通用错误页。
- 脚本提交(
application/x-www-form-urlencoded或multipart/form-data):成功和失败都返回 JSON,与 JSON 端点共用同一套错误体。 - multipart 上传:部分文件失败时,要么整体失败并列出失败项,要么返回 207 并在体内逐项标注结果,不要静默丢弃。
图片接口:成功是字节流,失败不是#
图片接口最常见的错误是在失败时返回一张占位图并配 200。这会让调用方的重试和缓存逻辑都失效。
- 成功:
200加准确的Content-Type(image/png、image/webp等),不要靠客户端猜格式。 - 失败:返回
4xx或5xx,错误体用 JSON;被<img>引用时浏览器不会展示错误体,这不构成返回图片的理由。 - 缓存:给出
ETag或Last-Modified,并正确处理If-None-Match返回304;带Content-Length更利于客户端判断截断。 - 格式协商:根据
Accept在 webp、avif、png 之间选择,无法满足时降级到服务端确实支持的格式,并在响应头体现实际格式。
三类端点对照#
| 维度 | JSON | 表单 | 图片 |
|---|---|---|---|
| 成功体 | 结构化资源 | 重定向或 JSON | 字节流 |
| 错误体 | problem+json | HTML 或 problem+json | problem+json |
| 失败状态码 | 4xx / 5xx | 422、303 组合 | 4xx / 5xx |
| 缓存依据 | 按资源语义 | 通常不缓存 | ETag / Cache-Control |
边界与待核实#
错误体加字段属于兼容变更,修改字段含义或状态码映射不属于。旧客户端的迁移窗口、网关是否会改写 Content-Type、CDN 对 304 的处理方式,都需要在实际链路上核实,本文未做此类测试。具体规范的版本状态以官方发布为准。
常见问题(FAQ)
JSON 接口还要不要返回 {code, msg, data}?
如果 HTTP 状态码已经表达了成功与失败,再放一个语义相同的 code,就会造成两套真相。更稳妥的做法是:状态码表达结果类别,响应体只承载资源或错误详情。已有客户端依赖 code 时,保留它作为兼容字段即可,但要在文档里写明哪一个是权威来源。
图片接口返回 404 时应该给 JSON 还是空响应?
取决于调用方。给浏览器 img 标签用时,错误体基本不会被展示;给脚本或 SDK 用时,JSON 错误体更易解析。常见做法是按 Accept 头协商:接受 application/json 的请求返回结构化错误,其余情况返回空体加正确状态码,两种情况都保留同一状态码。