跳到主要内容

12. 内容协商

当响应传达内容时, 无论它表示成功还是错误, 源服务器通常都有不同方式来表示这些信息; 例如, 使用不同格式, 语言或编码. 同样, 不同用户或用户代理可能具有不同能力, 特征或偏好, 这些因素可能影响在可用表示中哪一个最适合交付. 因此, HTTP 提供了内容协商机制.

本规范定义了三种可在协议中显现的内容协商模式: "proactive" negotiation, 即服务器根据用户代理声明的偏好选择表示; "reactive" negotiation, 即服务器提供表示列表供用户代理选择; 以及 "request content" negotiation, 即用户代理根据服务器在过去响应中声明的偏好, 为未来请求选择表示.

其他内容协商模式包括 "conditional content", 其中表示由多个部分组成, 并根据用户代理参数有选择地渲染; "active content", 其中表示包含脚本, 该脚本根据用户代理特征发起附加的 (更具体的) 请求; 以及 "Transparent Content Negotiation" ([RFC2295]), 其中内容选择由中间方执行. 这些模式并不互斥, 每种模式在适用性和实用性方面都有权衡.

注意, 在所有情况下, HTTP 都不了解资源语义. 源服务器随时间推移以及针对不同客户端响应请求时的一致性, 以及由此体现出的资源被观察到的表示随时间推移的 "sameness", 完全由选择或生成这些响应的实体或算法决定.

12.1. 主动协商

当用户代理在请求中发送内容协商偏好, 以鼓励服务器上的算法选择首选表示时, 这称为 "proactive negotiation" (也称为 "server-driven negotiation"). 选择过程基于响应的可用表示 (它可能变化的维度, 例如语言, 内容编码等), 并将其与请求中提供的各种信息进行比较, 这些信息包括下面的显式协商字段以及隐式特征, 例如客户端的网络地址或 User-Agent 字段的部分内容.

当从可用表示中选择的算法难以向用户代理描述, 或者服务器希望在第一个响应中向用户代理发送其 "best guess" 时, 主动协商是有利的 (如果该 "best guess" 对用户而言足够好, 就能避免后续请求的往返延迟). 为了改进服务器的猜测, 用户代理可以发送描述其偏好的请求头字段.

主动协商有严重缺点:

  • 服务器不可能准确确定对任何给定用户而言什么可能是 "best", 因为这需要完整了解用户代理的能力以及响应的预期用途 (例如, 用户是想在屏幕上查看它, 还是打印到纸上?);

  • 让用户代理在每个请求中描述其能力, 既可能非常低效 (因为只有很小比例的响应具有多个表示), 也可能对用户隐私构成潜在风险;

  • 它会使源服务器的实现以及为请求生成响应的算法复杂化; 并且,

  • 它限制了响应对共享缓存的可复用性.

用户代理不能依赖主动协商偏好会被一致遵守, 因为源服务器可能没有为请求的资源实现主动协商, 或者可能认为发送一个不符合用户代理偏好的响应比发送 406 (Not Acceptable) 响应更好.

受主动协商影响的响应中通常会发送 Vary 头字段 (Section 12.5.5), 以指示选择算法使用了请求信息的哪些部分.

下面定义的请求头字段 Accept, Accept-Charset, Accept-Encoding 和 Accept-Language 可供用户代理参与响应内容的主动协商. 这些字段中发送的偏好适用于响应中的任何内容, 包括目标资源的表示, 错误或处理状态的表示, 甚至可能包括协议中出现的各种文本字符串.

12.2. 响应式协商

在 "reactive negotiation" (也称为 "agent-driven negotiation") 中, 内容选择 (无论状态码如何) 由用户代理在收到初始响应后执行. 响应式协商机制可以简单到只是一个指向替代表示的引用列表.

如果用户代理对初始响应内容不满意, 它可以对一个或多个替代资源执行 GET 请求, 以获得不同表示. 对这些替代项的选择可以自动执行 (由用户代理执行), 也可以手动执行 (例如, 由用户从超文本菜单中选择).

服务器可以选择不发送初始表示, 只发送替代项列表, 从而指示更偏好由用户代理进行响应式协商. 例如, 300 (Multiple Choices) 和 406 (Not Acceptable) 状态码响应中列出的替代项包含关于可用表示的信息, 以便用户或用户代理可以通过做出选择来响应.

当响应会在常用维度上变化 (例如类型, 语言或编码), 当源服务器无法通过检查请求来确定用户代理能力, 以及通常在使用公共缓存来分担服务器负载并减少网络使用量时, 响应式协商是有利的.

响应式协商的缺点是需要向用户代理传输替代项列表, 如果在头部段中传输会降低用户感知延迟, 并且需要第二个请求来获取替代表示. 此外, 本规范没有定义用于支持自动选择的机制, 但也不阻止开发这样的机制.

12.3. 请求内容协商

当服务器在响应中发送内容协商偏好时, 所列偏好称为 "request content negotiation", 因为它们旨在影响后续发往该资源的请求中对适当内容的选择. 例如, Accept (Section 12.5.1) 和 Accept-Encoding (Section 12.5.3) 头字段可以在响应中发送, 用于指示后续发往同一资源的请求内容中首选的媒体类型和内容编码.

类似地, [RFC5789] 的 Section 3.1 定义了 "Accept-Patch" 响应头字段, 它允许发现 PATCH 请求中接受哪些内容类型.

12.4. 内容协商字段特性

12.4.1. 缺失

对于每个内容协商字段, 不包含该字段的请求意味着发送方在该协商维度上没有偏好.

如果请求中存在内容协商头字段, 且根据该字段, 响应的所有可用表示都不能被认为是可接受的, 源服务器可以遵守该头字段并发送 406 (Not Acceptable) 响应, 也可以忽略该头字段, 将响应视为不受该请求头字段的内容协商约束. 但是, 这并不意味着客户端一定能够使用该表示.

Note: 用户代理发送这些头字段会使服务器更容易凭借用户代理的请求特征识别个人 (Section 17.13).

12.4.2. 质量值

本规范定义的内容协商字段使用一个通用参数 "q" (大小写不敏感), 为相关内容类型的偏好分配相对 "weight". 该 weight 称为 "quality value" (或 "qvalue"), 因为同一个参数名称经常在服务器配置中用于为可为资源选择的各种表示的相对质量分配权重.

weight 被规范化为 0 到 1 范围内的实数, 其中 0.001 是最低偏好, 1 是最高偏好; 值 0 表示 "not acceptable". 如果不存在 "q" 参数, 默认 weight 为 1.

weight = OWS ";" OWS "q=" qvalue
qvalue = ( "0" [ "." 0*3DIGIT ] )
/ ( "1" [ "." 0*3("0") ] )

qvalue 的发送方不得在小数点后生成超过三位数字. 用户对这些值的配置也应当以同样方式受到限制.

12.4.3. 通配符值

这些头字段中的大多数会在指明处定义通配符值 (*), 用于选择未指定的值. 如果不存在通配符, 字段中未明确提及的值会被认为不可接受. 在 Vary 中, 通配符值意味着变异不受限制.

Note: 实践中, 在内容协商中使用通配符的实际价值有限, 因为很少有场景需要表达例如 "我对 image/* 的偏好高于或低于 (某个其他具体值)". 通过发送 Accept: */*;q=0, 客户端可以在更偏好的格式不可用时明确请求 406 (Not Acceptable) 响应, 但它们仍然需要能够处理不同响应, 因为服务器被允许忽略它们的偏好.

12.5. 内容协商字段

12.5.1. Accept

"Accept" 头字段可由用户代理用来指定其关于响应媒体类型的偏好. 例如, Accept 头字段可用于指示请求被特别限制为一小组期望类型, 如请求内联图像时那样.

当服务器在响应中发送 Accept 时, 它提供关于后续发往同一资源的请求内容中首选哪些内容类型的信息.

Accept = #( media-range [ weight ] )

media-range = ( "*/*"
/ ( type "/" "*" )
/ ( type "/" subtype )
) parameters

星号 * 字符用于将媒体类型分组为范围, 其中 */* 表示所有媒体类型, type/* 表示该 type 的所有 subtype. media-range 可以包含适用于该范围的媒体类型参数.

每个 media-range 后面可以跟随可选的适用媒体类型参数 (例如 charset), 再跟随一个可选的 "q" 参数来指示相对 weight (Section 12.4.2).

先前规范允许在 weight 参数之后出现附加扩展参数. accept-ext 语法 (accept-params, accept-ext) 已被移除, 因为它定义复杂, 实践中未被使用, 且通过新的头字段更容易部署. 使用 weight 的发送方应该将 "q" 放在最后 (位于所有 media-range 参数之后). 无论参数顺序如何, 接收方应该将任何名为 "q" 的参数作为 weight 处理.

Note: 使用 "q" 参数名来控制内容协商会干扰任何同名的媒体类型参数. 因此, 媒体类型注册不允许名为 "q" 的参数.

示例:

Accept: audio/*; q=0.2, audio/basic

解释为 "我偏好 audio/basic, 但如果任何 audio 类型在质量打八折之后仍是最佳可用项, 就发送给我".

一个更复杂的示例是:

Accept: text/plain; q=0.5, text/html,
text/x-dvi; q=0.8, text/x-c

用文字描述, 这会被解释为 "text/html 和 text/x-c 是同等首选的媒体类型, 但如果它们不存在, 则发送 text/x-dvi 表示; 如果它也不存在, 则发送 text/plain 表示".

媒体范围可以被更具体的媒体范围或具体媒体类型覆盖. 如果多个媒体范围适用于给定类型, 最具体的引用具有优先级. 例如:

Accept: text/*, text/plain, text/plain;format=flowed, */*

具有以下优先级:

  1. text/plain;format=flowed
  2. text/plain
  3. text/*
  4. */*

与给定类型关联的媒体类型质量因子通过查找匹配该类型且优先级最高的媒体范围来确定. 例如:

Accept: text/*;q=0.3, text/plain;q=0.7, text/plain;format=flowed,
text/plain;format=fixed;q=0.4, */*;q=0.5

会导致以下值被关联:

Media TypeQuality Value
text/plain;format=flowed1
text/plain0.7
text/html0.3
image/jpeg0.5
text/plain;format=fixed0.4
text/html;level=30.7

Note: 用户代理可能带有某些媒体范围的一组默认质量值. 但是, 除非该用户代理是无法与其他渲染代理交互的封闭系统, 否则这组默认值应当可由用户配置.

12.5.2. Accept-Charset

"Accept-Charset" 头字段可以由用户代理发送, 用于指示其对文本响应内容中 charset 的偏好. 例如, 该字段允许能够理解更全面或特殊用途 charset 的用户代理, 向能够用这些 charset 表示信息的源服务器发出能力信号.

Accept-Charset = #( ( token / "*" ) [ weight ] )

Charset 名称在 Section 8.3.2 中定义. 用户代理可以将 quality value 与每个 charset 关联, 以指示用户对该 charset 的相对偏好, 如 Section 12.4.2 所定义. 示例:

Accept-Charset: iso-8859-5, unicode-1-1;q=0.8

特殊值 * 如果出现在 Accept-Charset 头字段中, 则匹配字段中其他位置未提及的每个 charset.

Note: Accept-Charset 已弃用, 因为 UTF-8 已经几乎无处不在, 发送用户首选 charset 的详细列表会浪费带宽, 增加延迟, 并使被动指纹识别过于容易 (Section 17.13). 大多数通用用户代理不会发送 Accept-Charset, 除非专门配置为这样做.

12.5.3. Accept-Encoding

"Accept-Encoding" 头字段可用于指示关于使用内容编码 (Section 8.4.1) 的偏好.

当用户代理在请求中发送时, Accept-Encoding 指示响应中可接受的内容编码.

当服务器在响应中发送时, Accept-Encoding 提供关于后续发往同一资源的请求内容中首选哪些内容编码的信息.

"identity" token 用作 "no encoding" 的同义词, 用于传达不偏好任何编码的情况.

Accept-Encoding  = #( codings [ weight ] )
codings = content-coding / "identity" / "*"

每个 codings 值可以被赋予一个关联的 quality value (weight), 表示对该编码的偏好, 如 Section 12.4.2 所定义. Accept-Encoding 字段中的星号 * 符号匹配字段中未明确列出的任何可用内容编码.

示例:

Accept-Encoding: compress, gzip
Accept-Encoding:
Accept-Encoding: *
Accept-Encoding: compress;q=0.5, gzip;q=1.0
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0

服务器使用以下规则测试给定表示的内容编码是否可接受:

  1. 如果请求中没有 Accept-Encoding 头字段, 用户代理认为任何内容编码都是可接受的.

  2. 如果表示没有内容编码, 则默认可接受, 除非 Accept-Encoding 头字段通过声明 identity;q=0 或在没有更具体 "identity" 条目的情况下声明 *;q=0 来明确排除它.

  3. 如果表示的内容编码是 Accept-Encoding 字段值中列出的内容编码之一, 则它是可接受的, 除非它伴随 qvalue 0. (如 Section 12.4.2 所定义, qvalue 0 表示 "not acceptable".)

一个表示可以用多个内容编码进行编码. 但是, 大多数内容编码是达成同一目的的替代方式 (例如数据压缩). 在具有相同目的的多个内容编码之间选择时, 首选具有最高非零 qvalue 的可接受内容编码.

字段值为空的 Accept-Encoding 头字段意味着用户代理不希望响应中有任何内容编码. 如果请求中存在非空 Accept-Encoding 头字段, 且响应的所有可用表示都没有被列为可接受的内容编码, 源服务器应该发送不带任何内容编码的响应, 除非 identity coding 被指示为不可接受.

当 Accept-Encoding 头字段出现在响应中时, 它指示资源愿意在关联请求中接受哪些内容编码. 字段值的评估方式与请求中相同.

注意, 此信息特定于关联请求; 同一服务器上其他资源支持的编码集合可能不同, 也可能随时间变化或依赖请求的其他方面 (例如请求方法).

由于不支持的内容编码而使请求失败的服务器, 应当以 415 (Unsupported Media Type) 状态响应, 并在该响应中包含 Accept-Encoding 头字段, 以允许客户端区分与内容编码相关的问题和与媒体类型相关的问题. 为了避免与媒体类型相关问题混淆, 如果服务器由于与内容编码无关的原因以 415 状态使请求失败, 不得包含 Accept-Encoding 头字段.

Accept-Encoding 最常见的用途是在 415 (Unsupported Media Type) 状态码响应中, 用于响应客户端乐观使用内容编码的情况. 但是, 该头字段也可用于向客户端指示支持内容编码, 以优化未来交互. 例如, 当请求内容本可以被编码, 但客户端没有使用内容编码时, 资源可以在 2xx (Successful) 响应中包含它.

12.5.4. Accept-Language

"Accept-Language" 头字段可由用户代理用来指示响应中首选的一组自然语言. 语言标签在 Section 8.5.1 中定义.

Accept-Language = #( language-range [ weight ] )
language-range =
<language-range, see [RFC4647], Section 2.1>

每个 language-range 可以被赋予一个关联的 quality value, 表示对该范围所指定语言的用户偏好估计, 如 Section 12.4.2 所定义. 例如:

Accept-Language: da, en-gb;q=0.8, en;q=0.7

表示: "我偏好丹麦语, 但会接受英国英语和其他类型的英语".

注意, 一些接收方会将语言标签列出的顺序视为降序优先级的指示, 尤其是对分配了相同 quality value 的标签 (没有值等同于 q=1). 但是, 不能依赖这种行为. 为了保持一致性并最大化互操作性, 许多用户代理会为每个语言标签分配唯一的 quality value, 同时也按 quality 降序列出它们. 关于语言优先级列表的更多讨论见 [RFC4647] 的 Section 2.3.

对于匹配, [RFC4647] 的 Section 3 定义了若干匹配方案. 实现可以为其需求提供最合适的匹配方案. "Basic Filtering" 方案 ([RFC4647], Section 3.3.1) 与先前在 [RFC2616] Section 14.4 中为 HTTP 定义的匹配方案相同.

在每个请求中发送带有用户完整语言偏好的 Accept-Language 头字段, 可能违背用户的隐私预期 (Section 17.13).

由于可理解性高度依赖单个用户, 用户代理需要允许用户控制语言偏好 (通过用户代理自身配置, 或默认采用用户可控制的系统设置). 不向用户提供这种控制的用户代理不得发送 Accept-Language 头字段.

Note: 用户代理在设置偏好时应当向用户提供指导, 因为用户很少熟悉上文描述的语言匹配细节. 例如, 用户可能会假设选择 "en-gb" 后, 如果英国英语不可用, 就会向其提供任何类型的英语文档. 在这种情况下, 用户代理可以建议将 "en" 添加到列表中, 以获得更好的匹配行为.

12.5.5. Vary

响应中的 "Vary" 头字段描述请求消息中除方法和目标 URI 之外的哪些部分可能影响了源服务器选择此响应内容的过程.

Vary = #( "*" / field-name )

Vary 字段值要么是通配符成员 *, 要么是请求 field-name token 列表, 称为选择头字段 (selecting header fields), 它们可能在为此响应选择表示时发挥了作用. 潜在的选择头字段不限于本规范定义的字段.

包含成员 * 的列表表示请求的其他方面可能在选择响应表示时发挥了作用, 可能包括消息语法之外的方面 (例如客户端的网络地址). 接收方无法在不将请求转发给源服务器的情况下确定此响应是否适合后续请求. 代理不得在 Vary 字段值中生成 *.

例如, 包含以下内容的响应:

Vary: accept-encoding, accept-language

表示源服务器在为此响应选择内容时, 可能使用了请求的 Accept-Encoding 和 Accept-Language 头字段 (或它们的缺失) 作为决定因素.

包含字段名列表的 Vary 字段有两个目的:

  1. 告知缓存接收方, 除非后续请求对所列头字段具有与原始请求相同的值 (见 [CACHING] 的 Section 4.1), 或响应复用已由源服务器验证, 否则它们不得使用此响应满足后续请求. 换言之, Vary 扩展了将新请求匹配到已存储缓存条目所需的缓存键.

  2. 告知用户代理接收方, 此响应受内容协商 (Section 12) 约束, 且如果在所列头字段中提供其他值, 后续请求可能会发送不同表示 (主动协商).

当源服务器希望可缓存响应被选择性地复用于后续请求时, 应该在该响应上生成 Vary 头字段. 通常, 当响应内容已被定制以更好地适配这些选择头字段表达的偏好时就是这种情况, 例如源服务器根据请求的 Accept-Language 头字段选择了响应语言.

当源服务器认为内容选择中的变异不如 Vary 对缓存性能的影响重要时, 可以省略 Vary, 特别是在复用已经受到缓存响应指令限制时 (见 [CACHING] 的 Section 5.2).

无需在 Vary 中发送 Authorization 字段名, 因为该字段定义禁止将该响应复用于不同用户 (Section 11.6.2). 同样, 如果响应内容已由网络区域选择或受其影响, 但源服务器希望即使接收方从一个区域移动到另一个区域时缓存响应仍可复用, 那么源服务器无需在 Vary 中指示这种变异.