Skip to main content

12. Content Negotiation (内容协商)

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

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

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

请注意,在所有情况下,HTTP都不知道资源语义. 源服务器对请求的响应的一致性,随时间推移和在内容协商的各个维度上,因此资源的观察到的表示随时间推移的"相同性",完全由选择或生成这些响应的任何实体或算法确定.

12.1. Proactive Negotiation (主动协商)

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

当从可用表示中选择的算法难以向用户代理描述时,或者当服务器希望将其"最佳猜测"与第一个响应一起发送给用户代理时(当该"最佳猜测"对用户来说足够好时,这避免了后续请求的往返延迟),主动协商是有利的. 为了改进服务器的猜测,用户代理可以 (MAY) 发送描述其偏好的请求头部字段.

主动协商有严重的缺点:

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

  • 让用户代理在每个请求中描述其能力可能既非常低效(考虑到只有一小部分响应具有多个表示),又对用户的隐私构成潜在风险;

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

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

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

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

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

12.2. Reactive Negotiation (被动协商)

使用"被动协商" (reactive negotiation) (又称"代理驱动协商" (agent-driven negotiation)),内容的选择(无论状态码如何)由用户代理在接收到初始响应后执行. 被动协商的机制可能就像替代表示的引用列表一样简单.

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

服务器可能选择不发送初始表示(除了替代列表之外),从而指示用户代理的被动协商是首选的. 例如,具有300(Multiple Choices)和406(Not Acceptable)状态码的响应中列出的替代方案包括有关可用表示的信息,以便用户或用户代理可以通过进行选择来做出反应.

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

被动协商存在以下缺点:向用户代理传输替代列表,如果在头部部分中传输,会降低用户感知的延迟,并且需要第二个请求来获得替代表示. 此外,本规范没有定义支持自动选择的机制,尽管它不阻止开发这样的机制.

12.3. Request Content Negotiation (请求内容协商)

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

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

12.4. Content Negotiation Field Features (内容协商字段特性)

12.4.1. Absence (缺失)

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

如果内容协商头部字段存在于请求中,并且响应的可用表示都不能根据它被认为是可接受的,则源服务器可以通过发送406(Not Acceptable)响应来遵守头部字段,或者通过将响应视为不受该请求头部字段的内容协商约束来忽略头部字段. 然而,这并不意味着客户端将能够使用该表示.

注意: 发送这些头部字段的用户代理使服务器更容易通过用户代理的请求特征来识别个人(第17.13节).

12.4.2. Quality Values (质量值)

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

权重被归一化为0到1范围内的实数,其中0.001是最不首选的,1是最首选的; 值0表示"不可接受". 如果不存在"q"参数,则默认权重为1.

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

qvalue的发送者不得 (MUST NOT) 在小数点后生成超过三位数字. 这些值的用户配置应该以相同的方式受到限制.

12.4.3. Wildcard Values (通配符值)

这些头部字段中的大多数(在指示的地方)定义了通配符值(*)来选择未指定的值. 如果不存在通配符,则字段中未明确提及的值被认为是不可接受的. 在Vary中,通配符值意味着变化是无限的.

注意: 实际上,在内容协商中使用通配符的实用价值有限,因为很少有用地说,例如,"我更喜欢image/*多于或少于(某个其他特定值)". 通过发送Accept: */*;q=0,客户端可以明确请求406(Not Acceptable)响应,如果没有更首选的格式可用,但他们仍然需要能够处理不同的响应,因为服务器被允许忽略他们的偏好.

12.5. Content Negotiation Fields (内容协商字段)

12.5.1. Accept

"Accept"头部字段可以由用户代理用来指定它们关于响应媒体类型的偏好. 例如,Accept头部字段可以用来指示请求特别限于一小组所需类型,如在请求内联图像的情况下.

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

Accept = #( media-range [ weight ] )

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

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

每个media-range后面可以跟随可选的适用媒体类型参数(例如,charset),然后是用于指示相对权重的可选"q"参数(第12.4.2节).

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

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

示例:

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

被解释为"我更喜欢audio/basic,但如果在质量降低80%后它是最佳可用的,请发送给我任何音频类型".

一个更复杂的示例是:

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

将导致关联以下值:

媒体类型质量值
text/plain;format=flowed1
text/plain0.7
text/html0.3
image/jpeg0.5
text/plain;format=fixed0.4
text/html;level=30.7

注意: 用户代理可能会为某些媒体范围提供默认的质量值集. 然而,除非用户代理是不能与其他呈现代理交互的封闭系统,否则此默认集应该可由用户配置.

12.5.2. Accept-Charset

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

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

字符集名称在第8.3.2节中定义. 用户代理可以 (MAY) 将质量值与每个字符集关联,以指示用户对该字符集的相对偏好,如第12.4.2节中定义. 一个示例是:

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

特殊值*,如果存在于Accept-Charset头部字段中,则匹配字段中其他地方未提及的每个字符集.

注意: Accept-Charset已被弃用,因为UTF-8已变得几乎无处不在,并且发送用户首选字符集的详细列表会浪费带宽,增加延迟,并使被动指纹识别变得太容易(第17.13节). 大多数通用用户代理不发送Accept-Charset,除非特别配置这样做.

12.5.3. Accept-Encoding

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

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

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

"identity"令牌用作"无编码"的同义词,以便在不首选编码时进行通信.

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

每个codings值可以 (MAY) 被赋予一个关联的质量值(权重),表示对该编码的偏好,如第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*;q=0,而没有更具体的"identity"条目.

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

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

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

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

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

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

Accept-Encoding最常见的用途是在415(Unsupported Media Type)状态码的响应中,响应客户端对内容编码的乐观使用. 然而,头部字段也可以用于向客户端指示支持内容编码,以优化未来的交互. 例如,当请求内容足够大以证明使用压缩编码但客户端未这样做时,资源可能在2xx(Successful)响应中包含它.

12.5.4. Accept-Language

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

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

每个language-range可以被赋予一个关联的质量值,表示对该范围指定的语言的用户偏好的估计,如第12.4.2节中定义. 例如:

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

意味着:"我更喜欢丹麦语,但也会接受英式英语和其他类型的英语".

请注意,一些接收者将语言标签列出的顺序视为降序优先级的指示,特别是对于分配了相等质量值的标签(没有值与q=1相同). 然而,不能依赖这种行为. 为了一致性和最大化互操作性,许多用户代理为每个语言标签分配唯一的质量值,同时也按质量降序列出它们. 有关语言优先级列表的其他讨论,请参见[RFC4647]的第2.3节.

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

在每个请求中发送带有用户完整语言偏好的Accept-Language头部字段可能与用户的隐私期望相反(第17.13节).

由于可理解性高度依赖于个人用户,因此用户代理需要允许用户控制语言偏好(通过配置用户代理本身或默认为用户可控制的系统设置). 不向用户提供此类控制的用户代理不得 (MUST NOT) 发送Accept-Language头部字段.

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

12.5.5. Vary

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

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

Vary字段值要么是通配符成员*,要么是请求字段名称列表,称为选择头部字段,它们可能在为此响应选择表示中发挥了作用. 潜在的选择头部字段不限于本规范定义的字段.

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

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

Vary: accept-encoding, accept-language

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

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

  1. 通知缓存接收者,除非后续请求对列出的头部字段具有与原始请求相同的值(参见[CACHING]的第4.1节),或者响应的重用已由源服务器验证,否则它们不得 (MUST NOT) 使用此响应来满足后续请求. 换句话说,Vary扩展了将新请求与存储的缓存条目匹配所需的缓存键.

  2. 通知用户代理接收者,此响应受到内容协商的影响(第12节),并且如果在列出的头部字段中提供其他值,则可能在后续请求中发送不同的表示(主动协商).

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

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

不需要在Vary中发送Authorization字段名称,因为字段定义禁止为不同用户重用该响应(第11.6.2节). 同样,如果响应内容已由网络区域选择或影响,但源服务器希望即使接收者从一个区域移动到另一个区域也重用缓存的响应,则源服务器不需要在Vary中指示这种变化.