用户登录

技术文章

外部链接将在新标签页打开。

JSON / 表单 / 图片接口的响应设计:让客户端靠 Content-Type 与状态码判断,而不是靠猜

一句话回答

三类接口的响应形态应由 Content-Type 加状态码共同决定:JSON 接口返回可机读的错误码信封,表单接口面向浏览器给出重定向或带错误的页面,图片接口直接返回二进制并用响应头携带元数据;三者共用请求 ID、正确状态码和 nosniff 等基础约定,具体平台的字段与错误码需查官方文档。

核心要点 TL;DR
  • 客户端应先看 Content-Type 再决定怎么解析,不要假设响应一定是 JSON。
  • 表单接口失败时返回带错误提示的页面或 303 重定向,而不是把 JSON 直接吐给浏览器。
  • 图片接口用响应头携带类型与缓存信息,避免把 base64 塞进 JSON 信封。
  • 请求 ID 与稳定的机器可读错误码,是三类接口共同的排障基础。

结论是:三类接口的响应形态,应由 Content-Type 与 HTTP 状态码共同决定,不需要客户端凭经验和接口名去猜。JSON 接口返回结构化数据和机器可读的错误码;表单接口面向浏览器,成功时重定向,失败时返回带错误提示的页面;图片接口直接返回二进制内容,元数据放在响应头里。三者共用一套错误标识和请求 ID 约定。本文谈的是通用设计取舍,API.PLUS 平台自身的字段命名、错误码表和限流响应头,本次材料未提供,需以官方文档为准(待核实)。

JSON 接口:统一响应信封与可判断的错误#

JSON 接口最容易出问题的地方不是成功响应,而是失败响应形态不固定:有时是纯文本,有时是 HTML 错误页,有时是另一种结构的 JSON。客户端因此被迫写多层兜底判断。

建议的约束是:错误响应同样返回 JSON,并且带一个稳定的机器可读 code,人类可读的 message 只作辅助;request_id 用于和服务端日志对账。这不是标准强制要求,而是一种便于长期维护的设计选择。

// 未执行的示例:仅示意错误响应结构,不代表任何真实接口
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json; charset=utf-8

{
  "error": {
    "code": "INVALID_ARGUMENT",
    "message": "size must be between 1 and 4096",
    "field": "size",
    "request_id": "req_placeholder_0001"
  }
}

状态码要如实反映语义:参数校验失败用 4xx,服务端自身异常用 5xx。把错误包在 200 里会让缓存、监控和重试策略全部失效。

表单接口:以浏览器为真实客户端#

表单接口的使用者通常是浏览器,不是脚本。这意味着响应要符合浏览器行为:

  • 成功后重定向:POST 处理完成后返回 303 See Other 指向结果页,避免用户刷新触发重复提交。
  • 失败后重渲染:校验失败直接返回带错误提示的 HTML 页面,并保留用户已填内容,而不是返回一段 JSON 文本让用户盯着看。
  • 不要两种形态混用:如果同一端点既要服务浏览器又要服务脚本,用 Accept 分流是一种做法,但维护成本更高;更简单的是拆成两个端点。

表单接口的响应形态一旦确定,就不要因为内部实现调整而改变——浏览器端的跳转和回填逻辑会随之失效。

图片接口:二进制回二进制,元数据放头部#

图片接口的响应体应当是图片字节本身,配合准确的 Content-Type(如 image/png)、明确的 Cache-Control 与 ETag。需要下载语义时用 Content-Disposition 指定文件名。

常见反例是把图片 base64 后塞进 JSON 信封:体积约增加三分之一,客户端还要先解码,浏览器也无法直接把它当作图片资源缓存。除非调用方确实需要“一次请求拿到元数据加缩略图”,否则不值得。

同样重要的是失败路径:图片接口出错时不应返回 200 状态码加一张占位图或一段 HTML。客户端按 image/* 解析会得到损坏的图片,问题却很难定位。建议返回 4xx/5xx,并配合 X-Content-Type-Options: nosniff,防止浏览器把错误响应误判成别的类型。

客户端侧的最小判断逻辑#

与之配套的客户端写法应当先读响应头,再决定如何解析。

// 未执行的示例:示意分支顺序,未在真实环境中运行
async function call(url) {
  const res = await fetch(url);
  const type = (res.headers.get('content-type') || '').toLowerCase();

  if (!res.ok) {
    // 不要假设错误一定是 JSON
    const body = type.includes('application/json') ? await res.json() : null;
    throw new Error(body?.error?.code ?? `HTTP ${res.status}`);
  }
  if (type.startsWith('image/')) return res.blob();
  if (type.includes('application/json')) return res.json();
  return res.text();
}

这段代码的要点在于“按 Content-Type 分支”,不要只按接口文档分支。文档描述的是约定,响应头描述的是这一次实际返回的形态,两者不一致时,后者更可信。

三类接口共用的边界#

  • 请求 ID:三类响应都带上,排障时能直接定位到一条服务端日志。
  • 错误可判断:状态码加稳定错误码,不依赖 message 文本匹配——文案随时可能改动。
  • 缓存语义显式:图片接口尤其要写清 Cache-Control,避免中间层按默认策略缓存错误响应。
  • 待核实项:具体的错误码枚举、限流相关响应头和字段命名,需查阅 API.PLUS 官方文档后确认,本文不作推测。

作者:沐小悠

本文约 6 分钟读完 · 关键词:JSON 接口响应设计、表单 POST 303 重定向、图片接口 Content-Type、错误响应规范、HTTP 状态码

更多文章 GET 示例接口

「」标签下的其他文章