JSON、表单与图片接口的响应设计规范:三类端点的取舍与边界
三类接口不该共用一套万能返回体:JSON 端点要固定成功形态并给出字段级错误对象;表单端点要区分浏览器提交与 fetch 调用,成功跳转、失败回填并前置拦截超大载荷;图片端点要以二进制为主体,把 Content-Type、长度、缓存与 ETag 当作契约,出错时必须用非 2xx 状态码而不是 200 加 JSON。文章给出状态码对照表、兼容性规则与未执行示例,并说明参考素材未覆盖该主题、不含实测数据。
- 一个端点只保留一种成功响应形态,错误形态单独定义并在文档中写死。
- 状态码表达类别,响应体表达原因与下一步;两者不能互相替代。
- 图片接口最大的坑是用 200 返回 JSON 错误体,调用方无法区分成功与失败。
先说结论:JSON、表单、图片这三类接口不该共用一套“万能返回体”。可维护的做法是守住三条底线——一个端点只有一种成功形态;状态码负责说明“发生了什么类别的问题”,响应体负责说明“为什么、接下来怎么办”;Content-Type 属于契约的一部分,不能被当成实现细节随手改。
需要提前说明:本次提供的参考素材集中在模型规范、DDoS 报告、开发者平台动态等方向,没有覆盖 API 响应设计这一主题,因此下文属于基于通用 HTTP 语义与工程实践的设计建议,不含实测数据、性能数字或对具体产品的评测。涉及阈值、缓存收益、压缩比之类需要按自身流量核实的部分,文中标为待核实。
一、先把三类端点的共同约束定下来#
无论返回 JSON 还是图片字节,以下三点都成立,且一旦破坏,客户端代码会立刻变得脆弱。
- 成功形态唯一。同一个端点,成功时要么总是返回对象,要么总是返回数组,不要“有数据返回数组、没数据返回空字符串”。
- 错误可程序化判断。调用方应当能只靠状态码加一个稳定字段(如
error.code)决定分支,而不是去匹配message里的自然语言。 - 声明解析方式。JSON 带
charset,图片带准确的Content-Type,表单区分application/x-www-form-urlencoded与multipart/form-data。靠猜测 MIME 类型是常见的线上故障来源。
二、JSON 接口:信封、错误对象与值语义#
要不要包一层信封
裸对象(直接返回业务字段)读起来最省事,但后续想加分页游标、request_id、弃用警告时就没有位置了。信封式结构给它留了空间:
{
"data": { "id": "item_demo_01", "name": "示例资源" },
"meta": { "request_id": "req_demo_0001" }
}
(以上为未执行的示例,字段与取值均为占位。)两种风格都能用,真正的风险是中途切换:上线后再把裸对象改成信封,等于对所有调用方做破坏性变更。选一种,然后在文档里固定下来。
错误对象的最小字段集
建议至少包含四项:稳定的机器可读 code、面向人的 message、出错位置(字段名或参数名)、可追踪的 request_id。表单类接口还需要字段级错误集合,才能支持前端逐项回填。
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "invalid_field",
"message": "email 格式不合法",
"field": "email",
"request_id": "req_demo_0002"
}
}
(未执行示例,仅示意结构。)注意 code 一旦对外发布就应当视为契约:可以新增取值,但不要给已有取值换含义。
值语义:null、缺省与空集合
三种情况要区分清楚并写进文档:字段值为空(null)、字段未返回(缺省)、集合有零个元素([])。很多客户端 bug 来自把“缺省”当成“空值”处理。单个资源返回对象、列表端点返回数组或带游标的信封,都是可接受的选择,但要一贯。
三、表单接口:成功跳转、失败回填、超限前置拦截#
表单端点通常服务两类调用方:浏览器直接提交,和脚本用 fetch 提交。这两类对响应的期待完全不同。
- 浏览器直提:成功后用 303 跳转到结果页,避免刷新重复提交;校验失败返回 4xx 并渲染带错误提示与原值的 HTML。
- 脚本提交:按
Accept或独立端点返回 JSON 错误体,字段级错误与 JSON 接口共用同一结构,减少两套解析逻辑。
上传类表单还有两个容易被忽略的点:一是文件体积上限要在读完全部请求体之前判断,直接返回 413,而不是先落盘再拒绝;二是 multipart/form-data 的边界解析失败要返回 400 并说明是哪一部分不合法,否则排查成本很高。具体的体积上限与超时值取决于业务与带宽,属于待按自身情况核实的参数。
四、图片接口:二进制是主体,响应头是契约#
图片端点最常见的错误,是出错时用 200 返回一段 JSON。浏览器把这段字节当图片解码,结果是一张破图,而调用方的监控看到的是成功,故障被静默掉了。
| 响应头 | 作用 | 常见疏漏 |
|---|---|---|
| Content-Type | 声明实际图片格式,如 image/webp | 一律写 image/jpeg,与实际编码不符 |
| Content-Length | 让客户端预知体积,便于进度与截断检测 | 流式输出时缺失,客户端无法判断是否收全 |
| Cache-Control | 表达可缓存性与新鲜期 | 随机图端点误加长缓存,导致重复请求拿到同一张 |
| ETag / Last-Modified | 支持条件请求,命中时返回 304 省流量 | ETag 随进程重启变化,条件请求永远不命中 |
| Vary | 按 Accept 协商格式时区分缓存 | 做了格式协商却没有 Vary,缓存串味 |
HTTP/1.1 200 OK
Content-Type: image/webp
Content-Length: 48213
Cache-Control: public, max-age=86400
ETag: "demo-etag-v3"
Vary: Accept
(此处为二进制字节,示例未实际传输)
(未执行示例,头部取值均为占位。)至于出错时的响应体,有两种可选策略:返回非 2xx 加简短 JSON 错误体,便于程序调用方判断;或者返回固定的占位图加 200,让 <img> 场景不至于出现破图。后者代价是调用方失去区分能力,因此必须写进文档并从监控上单独埋点,不能默认别人知道。
五、状态码与响应形态对照#
下表是一份可直接拿去评审的对照草案,具体取舍以团队文档为准。
| 状态码 | 语义 | 建议响应体 |
|---|---|---|
| 200 | 成功 | 约定的成功形态(JSON 或图片字节) |
| 201 | 资源已创建 | 新资源表示,或配合 Location |
| 204 | 成功但无内容 | 空体,不返回 JSON |
| 304 | 条件请求未修改 | 空体 |
| 400 | 请求语法或参数结构错误 | 结构化错误体 |
| 401 / 403 | 未认证 / 已认证但无权限 | 结构化错误体,不回显敏感信息 |
| 404 | 资源不存在 | 结构化错误体 |
| 409 | 状态冲突(如重复创建) | 结构化错误体,含冲突字段 |
| 413 | 载荷超过上限 | 结构化错误体,说明上限 |
| 415 | 媒体类型不受支持 | 结构化错误体,列出可接受类型 |
| 422 | 语义校验失败 | 字段级错误集合 |
| 429 | 触发限流 | 错误体加 Retry-After |
| 5xx | 服务端问题 | 通用错误体,暴露 request_id 而非堆栈 |
六、兼容性:加字段、扩枚举、别动默认形态#
- 新增字段默认安全,但客户端应被明确告知“忽略未知字段”;反过来,服务端不要假设客户端会读新字段。
- 错误
code与枚举型字段只能增量扩展,客户端必须为未知取值准备兜底分支。 - 默认响应形态(裸对象 vs 信封、图片 vs JSON)一旦公开就不该改;确需变更时用新路径或显式版本参数并行一段时间。
- 鉴权失败与限流响应也要走同一个错误结构,否则调用方的重试逻辑会出现分叉。示例中的密钥一律用
YOUR_KEY之类占位符,不要在任何示例或日志里出现真实凭证。
七、边界与待核实项#
上述内容属于设计建议,不是强制性标准,也不构成对任何具体实现的评价。以下几点需要以自己的环境为准核实:不同客户端(浏览器 <img>、移动端图片库、CDN)对 4xx 图片响应的具体表现;随机图端点的缓存策略与去重收益;表单体积上限与超时的合理取值;以及条件请求在你们的部署方式下能否稳定命中。这些都需要可核验的测试记录才能给出结论,本文没有提供,也不应被当作实测结果引用。