| RFC 9457 | HTTP API 的问题详情 | 2023 年 7 月 |
| Nottingham 等 | 标准跟踪 | [页] |
本文档定义了一种“问题详情”,用于在 HTTP 响应内容中承载机器可读的错误详情, 以避免为 HTTP API 定义新的错误响应格式。¶
本文档废止 RFC 7807。¶
这是一份互联网标准跟踪文档。¶
本文档是互联网工程任务组 (IETF) 的成果。它代表了 IETF 社群的共识。它已经 接受公开审查,并已由互联网工程指导组 (IESG) 批准发布。关于互联网标准的更多 信息可见 RFC 7841 第 2 节。¶
关于本文档当前状态、任何勘误以及 如何提供反馈的信息,可在 https://www.rfc-editor.org/info/rfc9457 获取。¶
Copyright (c) 2023 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
HTTP 状态码([HTTP] 的第 15 节)并不总是能够传达足够有用的错误信息。虽然使用 Web 浏览器的人通常能够理解 HTML [HTML5] 响应内容, 但 HTTP API 的非人类消费者很难这样做。¶
为解决这一不足,本规范定义了简单的 JSON [JSON] 和 XML [XML] 文档格式,用于描述所遇到问题的具体情况 ——“问题详情”。¶
例如,考虑一个响应,指出客户端的账户没有足够的余额。API 的设计者可能决定使用 403 Forbidden 状态码,以告知通用 HTTP 软件(如客户端库、缓存和代理)该响应的一般语义。 API 特定的问题详情(例如服务器拒绝该请求的原因以及适用的账户余额) 可以承载在响应内容中,以便客户端能够适当地采取行动(例如, 触发向账户转入更多余额)。¶
本规范用 URI [URI] 标识具体的“问题类型”(例如“余额不足”)。HTTP API 可以使用其控制下的 URI 来标识它们专有的问题,也可以复用既有 URI 以促进互操作性 并利用共同语义(见第 4.2 节)。¶
问题详情可以包含其他信息,例如一个用于标识该问题具体发生实例的 URI (实际上是为“上周四 Joe 没有足够余额的那次”这一概念提供标识符), 这可用于支持或取证目的。¶
问题详情的数据模型是一个 JSON [JSON] 对象;当序列化为 JSON 文档时,它使用 "application/problem+json" 媒体类型。附录 B 定义了一种等价的 XML 格式,它使用 "application/problem+xml" 媒体类型。¶
当它们在 HTTP 响应中传达时,问题详情的内容可以使用 主动协商来协商;见 [HTTP] 的第 12.1 节。 特别是,人类可读字符串所使用的语言(例如 title 和 description 中的字符串) 可以使用 Accept-Language 请求头字段([HTTP] 的第 12.5.4 节)协商, 尽管这种协商仍可能导致返回非首选的默认表示。¶
问题详情可以与任何 HTTP 状态码一起使用,但它们最自然地适配 4xx 和 5xx 响应的语义。请注意,问题详情(自然地)并不是在 HTTP 中传达 问题详情的唯一方式。例如,如果响应仍然是某个资源的表示, 通常更适合用该应用自身的格式来描述相关详情。同样,已定义的 HTTP 状态码覆盖了许多场景,无需传达额外详情。¶
本规范的目标是为需要通用错误格式的应用定义这种格式, 使它们不必定义自己的格式,或者更糟糕地,被诱使重新定义 现有 HTTP 状态码的语义。即使某个应用选择不使用它来传达错误, 审视其设计也可以帮助指导在既有格式中传达错误时所面对的设计决策。¶
本文档中的关键词 "MUST"、"MUST NOT"、 "REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、 "RECOMMENDED"、"NOT RECOMMENDED"、 "MAY" 和 "OPTIONAL", 只有在它们像这里所示那样以全大写形式出现时,才应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释。¶
问题详情的规范模型是一个 JSON [JSON] 对象。当序列化在 JSON 文档中时,该格式由 "application/problem+json" 媒体类型标识。¶
例如:¶
在这里,余额不足问题(由其 type 标识)在 "title" 中指出 403 的原因,用 "instance" 标识该问题的具体发生实例,在 "detail" 中给出 针对此发生实例的详情,并添加了两个扩展:"balance" 传达账户余额,"accounts" 列出可以为账户充值的链接。¶
在经过相应设计时,特定于问题的扩展可以传达同一问题类型的多个 实例。例如:¶
这里虚构的问题类型定义了 "errors" 扩展,它是一个数组, 描述每个验证错误的详情。每个成员都是一个对象,包含用于描述问题的 "detail", 以及用于通过 JSON Pointer [JSON-POINTER] 定位请求内容中问题位置的 "pointer"。¶
当 API 遇到多个不共享同一类型的问题时,RECOMMENDED 在响应中表示最相关或最紧急的问题。 虽然可以创建通用的“批处理”问题类型来传达多个不同类型的问题, 但它们并不能很好地映射到 HTTP 语义。¶
还要注意,即使客户端没有在 Accept 中列出 "application/problem+json" 类型,API 也以该类型作出了响应, 这是 HTTP 允许的(见 [HTTP] 的第 12.5.1 节)。¶
问题详情对象可以具有以下成员。如果成员的值类型 与指定类型不匹配,则该成员MUST 被忽略—— 即处理将继续进行,就像该成员不存在一样。¶
"type" 成员是一个 JSON 字符串,包含一个 URI 引用 [URI],用于标识问题 类型。消费者MUST 使用 "type" URI(必要时在解析后) 作为问题类型的主要标识符。¶
当该成员不存在时,其值假定为 "about:blank"。¶
如果类型 URI 是定位器(例如带有 "http" 或 "https" scheme 的那些),解引用它SHOULD 提供该问题类型的 人类可读文档(例如,使用 HTML [HTML5])。但是,消费者SHOULD NOT 自动解引用该类型 URI,除非它们是在向开发者 提供信息时这样做(例如,在使用调试工具时)。¶
当 "type" 包含相对 URI 时,它会按照 [URI] 第 5 节 相对于文档的基 URI 解析。然而,使用相对 URI 可能造成混淆, 并且它们可能不会被所有实现正确处理。¶
例如,如果两个资源 "https://api.example.org/foo/bar/123" 和 "https://api.example.org/widget/456" 都 响应一个等于相对 URI 引用 "example-problem" 的 "type", 则在解析后它们将标识不同的资源 (分别为 "https://api.example.org/foo/bar/example-problem" 和 "https://api.example.org/widget/example-problem")。因此,RECOMMENDED 在可能时在 "type" 中使用绝对 URI,并且在使用相对 URI 时, 应包含完整路径(例如 "/types/123")。¶
类型 URI 允许是不可解析 URI。例如, tag URI scheme [TAG] 可用于 唯一标识问题类型:¶
tag:example@example.org,2021-09-17:OutOfLuck¶
不过,本规范鼓励使用可解析的类型 URI, 因为将来可能会需要解析该 URI。例如,如果某个 API 设计者使用了上面的 URI,后来又采用了一个会解析类型 URI 以发现错误相关信息的工具,那么要利用该能力就需要 切换到可解析 URI,从而为该问题类型创建新的身份,并因此 引入破坏性更改。¶
"status" 成员是一个 JSON number,表示源服务器为该问题发生实例 生成的 HTTP 状态码([HTTP] 第 15 节)。¶
"status" 成员如果存在,只是建议性的; 它传达用于方便消费者的 HTTP 状态码。生成者MUST 在实际 HTTP 响应中使用相同的状态码, 以确保不理解这种格式的通用 HTTP 软件仍然能够正确运行。 有关其使用的进一步注意事项,见第 5 节。¶
当原始状态码已被更改(例如被中介或缓存更改)时, 以及当消息内容在没有 HTTP 信息的情况下被持久化时, 消费者可以使用 status 成员来确定生成者原本使用的状态码。 通用 HTTP 软件仍将使用 HTTP 状态码。¶
"title" 成员是一个 JSON 字符串,包含对问题类型的简短、 人类可读摘要。¶
它SHOULD NOT 随同一问题从一次发生 到另一次发生而变化,除非是为了本地化(例如使用主动内容协商; 见 [HTTP] 第 12.1 节)。¶
"title" 字符串是建议性的,仅为不了解并且无法发现 type URI 语义的用户而包含(例如,在离线日志分析期间)。¶
"detail" 成员是一个 JSON 字符串,包含针对此问题发生实例的 人类可读解释。¶
"detail" 字符串如果存在,应当着重帮助客户端纠正问题, 而不是提供调试信息。¶
消费者SHOULD NOT 解析 "detail" 成员以获取信息;扩展是获取这类信息的更合适且更不易出错的方式。¶
"instance" 成员是一个 JSON 字符串,包含一个 URI 引用, 用于标识问题的具体发生实例。¶
当 "instance" URI 可被解引用时,可以从中获取问题详情 对象。它也可能通过使用主动内容协商,以其他格式返回关于问题发生实例的信息 (见 [HTTP] 第 12.5.1 节)。¶
当 "instance" URI 不可被解引用时,它充当该问题发生实例的 唯一标识符;该标识符可能对服务器具有意义,但对客户端是不透明的。¶
当 "instance" 包含相对 URI 时,它会按照 [URI] 第 5 节 相对于文档的基 URI 解析。然而,使用相对 URI 可能造成混淆, 并且它们可能不会被所有实现正确处理。¶
例如,如果两个资源 "https://api.example.org/foo/bar/123" 和 "https://api.example.org/widget/456" 都 响应一个等于相对 URI 引用 "example-instance" 的 "instance", 则在解析后它们将标识不同的资源 (分别为 "https://api.example.org/foo/bar/example-instance" 和 "https://api.example.org/widget/example-instance")。因此,RECOMMENDED 在可能时在 "instance" 中使用绝对 URI,并且在使用相对 URI 时, 应包含完整路径(例如 "/instances/123")。¶
问题类型定义MAY 使用特定于该问题类型的 其他成员来扩展问题详情对象。¶
例如,上面的余额不足问题定义了两个这样的扩展—— "balance" 和 "accounts",用于传达额外的、特定于问题的信息。¶
类似地,“验证错误”示例定义了一个 "errors" 扩展, 它包含一个列表,列出发现的各个错误发生实例,并带有详情以及指向 每个错误位置的指针。¶
消费问题详情的客户端MUST 忽略任何 不识别的此类扩展;这允许问题类型演进并在未来包含额外信息。¶
创建扩展时,问题类型作者应谨慎选择其名称。 为了可用于 XML 格式(见附录 B),它们需要符合 [XML] 的第 2.3 节中的 Name 规则。¶
当 HTTP API 需要定义一个表示错误条件的响应时, 通过定义新的问题类型来做到这一点可能是适当的。¶
在这样做之前,重要的是要理解它们适合做什么, 以及哪些内容最好留给其他机制处理。¶
问题详情不是底层实现的调试工具;相反,它们是一种暴露 HTTP 接口本身更多细节的方式。新问题类型的设计者需要仔细考虑 安全考虑事项(第 5 节),特别是通过错误消息暴露实现内部细节 从而暴露攻击向量的风险。¶
同样,真正通用的问题——即可能适用于 Web 上任何资源的条件—— 通常最好表达为普通状态码。例如,“不允许写访问”问题可能是不必要的, 因为对 PUT 请求响应 403 Forbidden 状态码已经是不言自明的。¶
最后,应用可能有更适合的方式在其已经定义的格式中承载错误。 问题详情旨在避免必须建立新的“故障”或“错误”文档格式, 而不是替代现有的特定领域格式。¶
尽管如此,可以使用 HTTP 内容协商为既有 HTTP API 添加对 问题详情的支持(例如,使用 Accept 请求头来表示对此格式的偏好; 见 [HTTP] 第 12.5.1 节)。¶
新的问题类型定义MUST 记录:¶
问题类型定义MAY 指定在适当情况下使用 Retry-After 响应头([HTTP] 第 10.2.3 节)。¶
问题类型 URI SHOULD 解析为 HTML [HTML5] 文档,该文档解释如何解决该 问题。¶
问题类型定义MAY 指定问题详情对象上的额外成员。 例如,扩展可以使用类型化链接 [WEB-LINKING] 指向机器可用来解决问题的另一个资源。¶
如果定义了这类额外成员,它们的名称SHOULD 以字母开头(ALPHA,如 [ABNF] 附录 B.1 所述),并且SHOULD 由 ALPHA、DIGIT ([ABNF] 附录 B.1)和 "_" 组成(以便它可以序列化为 JSON 以外的格式),并且它们 SHOULD 至少为三个字符。¶
例如,如果你正在为在线购物车发布 HTTP API, 你可能需要指出用户余额不足(上面的示例),因此无法完成购买。¶
如果你已经有能够容纳这些信息的应用特定格式, 那么最好使用它。但是,如果没有,你可以使用一种问题详情格式——如果你的 API 基于 JSON,则使用 JSON;如果它使用 XML,则使用 XML。¶
为此,你可以在注册表(第 4.2 节)中查找是否已有适合你目的的 已定义 type URI。如果有可用的,你可以复用该 URI。¶
如果没有可用的,你可以铸造并记录一个新的 type URI (该 URI 应当受你控制并且随时间保持稳定)、一个适当的标题以及将与之一起使用的 HTTP 状态码,并说明它的含义以及应如何处理。¶
本规范定义了“HTTP Problem Types”注册表, 用于常见、广泛使用的问题类型 URI,以促进复用。¶
该注册表的策略是 Specification Required,按 [RFC8126] 第 4.6 节。¶
在评估请求时,指定专家应考虑社区反馈、该问题类型定义得有多好, 以及本规范的要求。供应商特定、应用特定和部署特定的值不能被注册。 规范文档应以稳定、免费可得的方式发布 (理想情况下位于某个 URL),但不必是标准。¶
注册MAY 对 type URI 使用前缀 "https://iana.org/assignments/http-problem-types#"。 请注意,这些 URI 可能无法被解析。¶
注册请求应使用以下模板:¶
有关注册请求发送位置的详细信息,见 <https://iana.org/assignments/http-problem-types> 处的注册表。¶
本规范注册一个 Problem Type,即 "about:blank", 如下所示。¶
当 "about:blank" URI [ABOUT] 用作问题类型时,表示该问题除了 HTTP 状态码的语义之外没有额外语义。¶
使用 "about:blank" 时,title SHOULD 与该代码推荐的 HTTP 状态短语相同 (例如,404 对应 "Not Found",依此类推),不过它MAY 被本地化以适应客户端偏好(通过 Accept-Language 请求头表达)。¶
请注意,根据 "type" 成员的定义方式(第 3.1 节),"about:blank" URI 是该成员的默认值。因此,任何没有携带显式 "type" 成员的问题详情对象 都隐式使用该 URI。¶
定义新的问题类型时,必须仔细审查所包含的信息。 同样,在实际生成问题时——无论它如何序列化——也必须审查所给出的详情。¶
风险包括泄露可被利用来破坏系统、访问系统或损害 系统用户隐私的信息。¶
鼓励提供发生实例信息链接的生成者避免通过 HTTP 接口提供 实现细节,例如堆栈转储,因为这可能暴露服务器实现及其数据等的敏感细节。¶
"status" 成员复制了 HTTP 状态码本身可用的信息, 因而带来了二者不一致的可能性。它们的相对优先级并不明确, 因为不一致可能表示(例如)中介在传输过程中更改了 HTTP 状态码 (例如通过代理或缓存)。通用 HTTP 软件(如代理、负载均衡器、防火墙 和病毒扫描器)不太可能知道或尊重该成员中传达的状态码。¶
在“Media Types”注册表下的“application”注册表中,IANA 已更新 "application/problem+json" 和 "application/problem+xml" 注册,使其引用本文档。¶
IANA 已按照第 4.2 节中的规定创建“HTTP Problem Types”注册表, 并按照第 4.2.1 节填入 "about:blank"。¶
本节给出一个用于 HTTP 问题详情的非规范性 JSON Schema [JSON-SCHEMA]。 如果它与规范正文之间存在任何不一致,则以后者为准。¶
# NOTE: '\' line wrapping per RFC 8792
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "An RFC 7807 problem object",
"type": "object",
"properties": {
"type": {
"type": "string",
"format": "uri-reference",
"description": "A URI reference that identifies the \
problem type."
},
"title": {
"type": "string",
"description": "A short, human-readable summary of the \
problem type."
},
"status": {
"type": "integer",
"description": "The HTTP status code \
generated by the origin server for this occurrence of the problem.",
"minimum": 100,
"maximum": 599
},
"detail": {
"type": "string",
"description": "A human-readable explanation specific to \
this occurrence of the problem."
},
"instance": {
"type": "string",
"format": "uri-reference",
"description": "A URI reference that identifies the \
specific occurrence of the problem. It may or may not yield \
further information if dereferenced."
}
}
}
¶
使用 XML [XML] 的 基于 HTTP 的 API 可以使用本附录中定义的格式来表达问题详情。¶
XML 格式的 RELAX NG schema [ISO-19757-2] 如下:¶
default namespace ns = "urn:ietf:rfc:7807"
start = problem
problem =
element problem {
( element type { xsd:anyURI }?
& element title { xsd:string }?
& element detail { xsd:string }?
& element status { xsd:positiveInteger }?
& element instance { xsd:anyURI }? ),
anyNsElement
}
anyNsElement =
( element ns:* { anyNsElement | text }
| attribute * { text })*
¶
请注意,该 schema 仅作为文档使用,而不是捕获 XML 格式 所有约束的规范性 schema。可以使用其他 XML schema 语言来定义一组 类似的约束(取决于所选 schema 语言的特性)。¶
该格式的媒体类型为 "application/problem+xml"。¶
扩展数组和对象序列化到 XML 格式时,会将包含一个或多个子元素的元素 视为表示一个对象,但只包含一个或多个名为 "i" 的子元素的元素除外, 后者被视为数组。例如,上面的示例在 XML 中如下所示:¶
此格式使用 XML namespace,主要是为了允许将其嵌入到其他 基于 XML 的格式中;这并不意味着它可以或应当使用其他 namespace 中的元素或属性进行扩展。 RELAX NG schema 明确只允许来自 XML 格式中所用单一 namespace 的元素。 任何扩展数组和对象MUST 仅使用该 namespace 序列化为 XML 标记。¶
使用 XML 格式时,可以在 XML 中嵌入一条 XML 处理指令, 指示客户端使用所引用的 XSL Transformations (XSLT) 代码 [XSLT] 转换 XML。 如果该代码将 XML 转换为 (X)HTML,则可以提供 XML 格式,同时让能够执行该 转换的客户端显示在客户端渲染并展示的人类友好型 (X)HTML。请注意, 使用这种方法时,建议使用 XSLT 1.0,以最大化能够执行 XSLT 代码的客户端数量。¶
在某些情况下,将问题详情嵌入到这里所描述格式以外的格式中可能是有利的。 例如,使用 HTML [HTML5] 的 API 也可能希望使用 HTML 来表达其问题 详情。¶
可以通过将现有序列化之一(JSON 或 XML)封装进其他格式, 或通过将问题详情的模型(如第 3 节中指定) 转换为该格式的约定,把问题详情嵌入其他格式。¶
例如,在 HTML 中,可以通过在 script 标签中封装 JSON 来嵌入一个问题:¶
<script type="application/problem+json">
{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
"balance": 30,
"accounts": ["/account/12345",
"/account/67890"]
}
</script>
¶
或者,通过定义到 Resource Description Framework in Attributes (RDFa) [RDFA] 的映射来实现。¶
本规范不就如何在其他格式中嵌入问题详情提出具体建议; 适当的嵌入方式取决于正在使用的格式以及该格式的应用。¶
本次修订作出了以下更改:¶