JSON、表单与图片接口的响应契约:状态码、Content-Type 与错误体怎么分工
三类接口不必共用同一个响应体,但应共用同一套判断顺序:先由状态码判定成功或失败,再由 Content-Type 决定解析方式,最后才读取响应体字段。文章给出的具体建议包括:JSON 成功体要保持全局一致;错误体至少包含稳定的机器码、面向人的描述和请求标识;图片接口用具体 image 类型加响应头表达元数据与缓存,出错时返回 JSON 错误体而不是占位图;表单提交用 303 重定向或直接返回 JSON 都可行,但校验失败要用 4xx 表达;兼容性上优先加字段、用媒体类型承载版本。
先给结论:JSON、表单、图片这三类接口不必共用同一个响应体,但应共用同一套判断顺序——先看状态码判断成功还是失败,再看 Content-Type 决定怎么解析,最后才在响应体里找字段。这套顺序在整站保持一致,客户端就不必为每个端点写特殊分支。本文讨论的是响应契约的设计取舍,不含任何厂商实测;涉及具体平台的行为,需以其官方文档为准,本文未做核验。
一、先把判断顺序固定下来#
常见的客户端写法是先 JSON.parse 再判断业务码。一旦响应体是图片流、空体或 HTML 错误页,解析阶段就会先抛错,真正的状态信息反而丢失。把顺序倒过来可以避免这种耦合。
- 2xx:成功。响应体结构由 Content-Type 声明,客户端按类型分发。
- 3xx:需要跟随跳转或改变请求方式,表单提交后最常见。
- 4xx:请求侧问题,原样重试通常没有意义;408、429 是例外,且应参考
Retry-After。 - 5xx:服务端问题,能否重试取决于该请求是否幂等,不能只看状态码。
二、JSON 成功响应:信封还是裸对象#
两种都可以,关键是全局一致,不要同一套 API 里一半裸对象一半信封。
| 方案 | 优点 | 代价 |
|---|---|---|
裸对象 {"id": ...} | 直观,便于类型生成与缓存 | 没有固定位置放业务失败信息 |
信封 {"data": ..., "request_id": ...} | 结构稳定,易追加元信息 | 多一层解包 |
若选信封,注意不要让 HTTP 状态码和 body 里的 code 表达互相矛盾的结果:例如鉴权失败返回 200 再在 body 里写“未登录”,会让网关、缓存和监控全部失效。body 里的业务码只应细分 HTTP 已表达的结果。
三、错误体:最少三件事#
一个可用的错误体需要:稳定的机器可读码、面向人的描述、可用于排障的请求标识。
{
"error": {
"code": "invalid_image_format",
"message": "仅接受 image/png、image/jpeg",
"field": "file"
},
"request_id": "req_demo_0001"
}
上例为未执行的演示,域名、标识均为占位。三条约束:code 是契约,不能随文案调整而变更;message 面向人,可本地化;request_id 要能贯穿服务端日志。错误体里不要出现堆栈、SQL 语句或内部主机名。
四、图片接口:二进制与元数据分开#
返回图片时,Content-Type 应是具体的 image/png、image/jpeg,而不是让客户端去嗅探的通用类型;可缓存性用 ETag、Cache-Control、Content-Length 表达。
- 元数据:宽高、格式等信息放响应头,或提供
Accept: application/json的元数据变体。同一 URL 有两种表示时,应带Vary: Accept,否则缓存可能串味。 - 失败:不要返回 200 加一张占位图。应返回 4xx/5xx 加 JSON 错误体,客户端按 Content-Type 分流。
- 下载语义:用
Content-Disposition: attachment; filename="...",文件名需转义。
图片地址是否带签名、有效期多长,属各供应商策略,请查对应文档,本文不给数值。
五、表单提交:重定向还是 JSON#
传统 HTML 表单在 POST 后返回 303 重定向到结果页,可以避免刷新造成重复提交;前后端分离场景下,无论请求体是 application/x-www-form-urlencoded 还是 multipart/form-data,通常直接返回 application/json。两种都合理,取决于由谁渲染结果。
校验失败建议用 400 或 422,把字段级错误放进响应体,不要用 200 加 {"success": false} 掩盖。multipart 上传还要说明体积上限与超限时的返回(通常是 413),具体阈值以服务端配置为准,本文不作断言。
六、变更时的兼容边界#
- 新增字段一般向后兼容;删除字段、改类型、改语义不兼容。版本可用路径前缀或媒体类型(如
application/vnd.example.v2+json)承载。 - 新增状态码前,先确认客户端对未知 4xx 有安全的默认分支。
- 时间统一用 ISO 8601 并带时区;ID 用字符串返回,避免前端大整数精度问题。
适用范围说明#
以上为通用设计建议,不含性能数据与实测记录,代码片段为未执行的演示。API.PLUS 各端点的实际字段、错误码与限流策略,请以其官方文档为准。