代表性面试主题

后端面试:如何用 RFC 9457 统一 HTTP API 错误?

后端中等
Offer.cc 编辑团队发布 更新

题干

一个多租户 API 的错误响应来自网关、应用和异步任务,客户端难以稳定解析。请基于 RFC 9457 设计统一错误契约,并说明类型注册、字段扩展、批量校验、重试和兼容策略。

面试官考察点

一个多租户 API 的错误响应来自网关、应用和异步任务,客户端难以稳定解析。请基于 RFC 9457 设计统一错误契约,并说明状态码、媒体类型、错误类型 URI、批量校验、重试、日志脱敏和版本兼容。

背景与约束

  • 同一请求可能经过 CDN、API 网关、业务服务和任务队列。
  • 客户端需要区分可修正的输入错误、权限问题、限流和暂时性故障。
  • 错误详情不能泄露堆栈、密钥、租户隔离信息或内部主机名。
  • 旧客户端仍会解析已有字段,不能突然把所有失败改成新的业务状态码。

先分离 HTTP 语义与业务细节

HTTP 状态码表达请求在协议层的结果,Problem Details 负载解释具体原因。400、401、403、404、409、429 和 5xx 仍按语义选择,不能把所有失败都包装成 200。错误类型用稳定 URI 标识可机器识别的类别,title 可供展示,detail 只能描述当前实例。

建立最小且可扩展的字段契约

核心字段包括 typetitlestatusdetailinstance。业务扩展字段应使用明确命名空间,例如 errors 表示字段级问题、retryAfter 表示客户端等待建议。type 的文档页应说明语义、适用状态码和可行动作;客户端不要依赖 title 的自然语言。

让网关与应用共享错误边界

网关生成的超时、认证和限流错误也使用相同媒体类型,但不能伪造应用的业务 type。应用向上游保留可观测的内部错误码,向外只返回允许公开的 Problem Details。跨服务传播时携带关联 ID,不复制敏感的 detail

回答前需要澄清的问题

  • 客户端是按状态码、type 还是旧版 code 字段分支?这决定兼容层和迁移顺序。
  • 是否需要单次响应返回多个字段校验错误?这决定 errors 的结构和顺序保证。
  • 网关是否能读取业务错误,还是只负责传输和生成基础错误?这决定类型 URI 的所有权。

30 秒回答框架

“我会保留正确的 HTTP 状态码,再用 application/problem+json 返回稳定的 typetitlestatusdetail 和可选 instance。客户端按 type 和状态码处理,不能依赖文案;网关只生成它负责的错误类型。字段校验、重试提示、关联 ID 和脱敏规则进入版本化契约,并用兼容矩阵和真实链路验证。”

分步骤深入解答

先定义错误类型注册表:每个类型有 URI、公开字段、允许状态码、客户端动作和安全等级。请求校验失败返回 400,字段问题集中放入 errors;身份缺失和权限不足区分 401 与 403;并发版本冲突用 409;限流返回 429 并在响应头和扩展字段中给出等待建议;未知故障返回 500 或 503,避免把内部异常类型暴露给调用方。

响应头的 Content-Type 必须与负载一致。错误体可以包含 instance 作为一次请求的追踪引用,但不能把完整 URL、SQL、堆栈或租户标识直接放进 detail。服务端日志保存内部原因、关联 ID 和安全审计字段,客户端只看到经过策略过滤的内容。

对于批量校验,errors 使用字段路径到问题列表的结构,并规定是否允许多个问题、字段路径语法和最大数量。客户端未知扩展字段时应忽略;服务端新增字段只能向后兼容,改变既有 type 语义则发布新 URI。异步任务失败通过任务资源状态返回 Problem Details,不把队列内部异常直接同步给用户。

高质量示范回答

“我会维护一个错误类型注册表,并让网关、同步服务和异步任务都输出 RFC 9457 兼容的 application/problem+json。状态码表达协议语义,type 表达稳定类别,detail 只描述当前请求。字段校验使用受限的 errors 扩展;429 同时提供可解析的等待提示;500 和 503 使用通用公开类型,内部堆栈只进日志。迁移时保留旧 code,通过契约测试、客户端兼容矩阵和脱敏审计逐步切换。”

常见错误

  • 用 200 搭配业务失败字段,让缓存、监控和重试器误判成功。
  • 让客户端按 titledetail 文案分支,导致翻译和措辞变更即破坏兼容。
  • 每个服务随意定义 type,同一语义出现多个 URI,无法统一统计。
  • detail 中返回堆栈、SQL、内部域名或完整租户标识。
  • 把网关的超时伪装成业务错误,客户端因此错误重试或错误提示。

错误表现与修正

如果客户端无法判断错误是否可重试,通常是状态码、类型和重试建议没有形成一致契约。修正时先建立类型注册表,再让各层映射到有限的公开类型;对未知类型提供安全默认行为,并用日志关联 ID 追踪内部原因。

生产化实现

在共享库或边缘适配层集中序列化 Problem Details,保留服务边界上的状态码选择。通过 schema 校验限制扩展字段长度、数组数量和 URI 格式;将敏感字段过滤放在序列化前,而非只依赖网关。对 429、503 和网络超时分别定义指数退避、抖动和幂等条件,避免重试风暴。

验证清单

契约测试覆盖每个公开 type 的状态码、媒体类型、必需字段和扩展字段;集成测试穿过网关、服务和队列,断言关联 ID 与脱敏结果;兼容测试用旧客户端验证未知字段和保留 code;压测观察错误序列化延迟、日志采样和限流下的重试放大。

追问及应对

为什么不能只定义一个业务错误码?

单一错误码无法表达 HTTP 缓存、认证、限流和重试语义。保留状态码能让通用基础设施正确工作,type 再承载稳定的业务类别,两者职责不同。

type URI 必须可以访问吗?

规范允许相对或绝对 URI;团队应选择稳定、可文档化的形式。若提供文档页,应避免把页面可用性作为客户端处理错误的前提。

如何避免错误体本身造成信息泄露?

建立公开字段白名单、长度限制和敏感模式扫描;将内部异常映射为通用类型,并在日志中保留关联 ID。安全测试要覆盖跨租户查询、权限失败和异常堆栈路径。

评分标准

  • 语义准确:能区分 HTTP 状态码与 Problem Details 字段。
  • 契约设计:给出稳定 type、扩展字段和版本策略。
  • 边界意识:覆盖网关、异步任务、脱敏和重试风暴。
  • 实现可落地:有注册表、序列化边界、兼容矩阵和测试。
  • 风险控制:不泄露内部细节,也不把未知类型当作可重试。

合规检查

确认状态码、类型字段、脱敏策略和重试边界在回答中保持一致。

面试作答要点

先说状态码仍表达 HTTP 语义,再说明 type 是机器稳定标识、detail 不是契约键;随后补充网关边界、字段校验、重试、脱敏和兼容验证。

一句话总结

统一错误的关键是让状态码负责协议语义、type 负责稳定分类、扩展字段负责可行动细节,并用安全和兼容测试守住边界。

公开来源

同类题目