内容协商
在 HTTP 中,**内容协商**机制用于为同一 URI 的资源提供不同的 表示形式,帮助用户代理指定最适合用户的表示形式(例如,哪种文档语言、哪种图像格式或哪种内容编码)。
注意:您可以在 WHATWG 的维基页面 上找到 HTTP 内容协商的一些缺点。HTML 通过 <source>
元素 等方式提供了内容协商的替代方案。
内容协商原则
一个特定的文档称为**资源**。当客户端想要获取资源时,客户端通过 URL 请求它。服务器使用此 URL 从可用变体中选择一个(每个变体称为**表示形式**),并将特定表示形式返回给客户端。整个资源以及每个表示形式都有一个特定的 URL。**内容协商**确定在调用资源时如何选择特定表示形式。在客户端和服务器之间进行协商有几种方法。
最合适的表示形式通过以下两种机制之一确定
- 客户端的特定 HTTP 标头(**服务器驱动协商**或**主动协商**),这是协商特定资源类型的标准方法。
- 服务器的
300
(多个选择)或406
(不可接受)、415
(不支持的媒体类型)HTTP 响应代码(**代理驱动协商**或**被动协商**),用作回退机制。
多年来,其他内容协商提案,如 透明内容协商 和 Alternates
标头,已被提出。但它们未能获得关注,最终被放弃。
服务器驱动的内容协商
在**服务器驱动内容协商**或**主动内容协商**中,浏览器(或任何其他类型的用户代理)会将几个 HTTP 标头与 URL 一起发送。这些标头描述了用户的首选选择。服务器将这些标头用作提示,并通过内部算法选择最适合的内容来提供给客户端。如果它无法提供合适的资源,它可能会响应 406
(不可接受)或 415
(不支持的媒体类型),并设置它支持的媒体类型的标头(例如,使用 Accept-Post
或 Accept-Patch
分别用于 POST 和 PATCH 请求)。该算法是特定于服务器的,并且没有在标准中定义。请参阅 Apache 协商算法。
HTTP/1.1 标准定义了启动服务器驱动协商的标准标头列表(如 Accept
、Accept-Encoding
和 Accept-Language
)。尽管 User-Agent
不在此列表中,但它有时也用于发送请求资源的特定表示形式。然而,这并不总是被认为是最佳实践。服务器使用 Vary
标头来指示它实际用于内容协商的标头(更准确地说,是关联的请求标头),以便 缓存 可以最佳地工作。
除了这些,还有一个实验性的提案,即向可用标头列表中添加更多标头,称为**客户端提示**。客户端提示会通告用户代理运行在什么样的设备上(例如,台式计算机或移动设备)。
即使服务器驱动内容协商是就资源的特定表示形式达成一致的最常见方式,它也有一些缺点
- 服务器不完全了解浏览器。即使使用客户端提示扩展,它也不能完全了解浏览器的功能。与客户端做出选择的被动内容协商不同,服务器的选择总是有点随意。
- 来自客户端的信息非常冗长(HTTP/2 标头压缩缓解了这个问题),而且存在隐私风险(HTTP 指纹识别)。
- 由于发送了给定资源的多个表示形式,共享缓存效率较低,服务器实现也更加复杂。
Accept
标头
Accept
标头列出了代理愿意处理的媒体资源的 MIME 类型。这是一个用逗号分隔的 MIME 类型列表,每个 MIME 类型都与一个质量因子结合,质量因子表示不同 MIME 类型之间相对偏好程度的参数。
Accept
标头由浏览器或任何其他用户代理定义,并根据上下文而异。例如,获取 HTML 页面或图像、视频或脚本。当获取在地址栏中输入的文档或通过 <img>
、<video>
或 <audio>
元素链接的元素时,它与获取文档时的行为不同。浏览器可以自由使用它们认为最合适的标头值;一个关于 常见浏览器默认值的详尽列表 可供参考。
Accept-CH
标头
注意:这是**实验性**技术**客户端提示**的一部分。Chrome 46 或更高版本提供了初始支持。设备内存值在 Chrome 61 或更高版本中可用。
实验性的 Accept-CH
列出了服务器可以用来选择合适响应的配置数据。有效值为
值 | 含义 |
---|---|
Device-Memory |
Device-Memory |
指示设备 RAM 的近似数量。此值是通过四舍五入到最接近的 2 的幂并将该数字除以 1024 获得的近似值。例如,512 兆字节将报告为 |
Viewport-Width |
指示布局视窗宽度(以 CSS 像素为单位)。 |
Width |
指示资源宽度(以物理像素为单位)(换句话说,是图像的固有尺寸)。
Accept-Encoding
标头
Accept-Encoding
标头定义了可接受的内容编码(支持的压缩)。该值是一个质量因子列表(例如,br, gzip;q=0.8
),表示编码值的优先级。默认值 identity
具有最低优先级(除非另有说明)。
压缩 HTTP 消息是提高网站性能的最重要方法之一。它缩小了传输数据的尺寸,并更有效地利用了可用带宽。浏览器总是发送此标头,服务器应该配置为使用压缩。
Accept-Language
标头
Accept-Language
标头用于指示用户的语言偏好。它是一个带有质量因子的值列表(例如,"de, en;q=0.7
)。通常会根据用户代理的图形界面的语言设置一个默认值,但大多数浏览器允许设置不同的语言偏好。
- 由于 基于配置的熵 增加,可以使用修改后的值来识别用户指纹。不建议更改它,网站不能信任此值来反映用户的真实意图。网站设计人员最好避免通过此标头进行语言检测,因为它会导致糟糕的用户体验。
- 他们应该始终提供一种覆盖服务器选择的语言的方法,例如,在网站上提供语言菜单。大多数用户代理提供一个与用户界面语言相适应的
Accept-Language
标头的默认值。最终用户通常不会修改它,因为他们要么不知道如何修改,要么无法根据他们的计算环境进行修改。
一旦用户覆盖了服务器选择的语言,网站就不应该再使用语言检测,而应该坚持使用明确选择的语言。换句话说,只有网站的入口页面应该使用此标头来选择合适的语言。
User-Agent
标头
注意:尽管此标头有合法用途,可以用来选择内容,但 依赖它来定义用户代理支持的功能被认为是不好的做法。
User-Agent
标头标识发送请求的浏览器。此字符串可能包含一个空格分隔的**产品令牌**和**注释**列表。
**产品令牌**是一个名称,后面跟着一个 '/
' 和一个版本号,如 Firefox/4.0.1
。用户代理可以根据需要包含任意数量的这些令牌。**注释**是一个可选字符串,用括号分隔。注释中提供的信息没有标准化,尽管几个浏览器会向其中添加几个用 ';
' 分隔的令牌。
Vary
响应标头
与前面的 Accept-*
标头(由客户端发送)形成对比的是,Vary
HTTP 标头由 Web 服务器在其响应中发送。它指示服务器在服务器驱动内容协商阶段使用的标头列表。Vary
标头是必要的,因为它可以告知缓存决策标准,以便它可以重新创建决策标准。这使得缓存可以正常工作,同时确保向用户提供正确的内容。
Vary
头部在 HTTP 1.1 版本中被添加,它允许缓存正常工作。为了与服务器驱动的内容协商协同工作,缓存需要知道服务器用来选择传输内容的标准。这样,缓存就能重放算法,并能够直接提供可接受的内容,而无需向服务器发出更多请求。显然,通配符 '*
' 会阻止缓存发生,因为缓存无法知道它背后的元素。有关更多信息,请参见 HTTP 缓存 > 不同响应。
代理驱动协商
服务器驱动的协商有一些缺点:它不能很好地扩展。协商使用每个功能一个头部。如果你想使用屏幕大小、分辨率或其他维度,你需要创建一个新的 HTTP 头部。然后必须在每次请求中发送这些头部。如果头部很少,这不是问题,但是随着头部数量的增加,消息大小最终会影响性能。头部发送得越精确,发送的熵就越多,从而允许更多 HTTP 指纹识别以及相应的隐私问题。
HTTP 允许另一种协商类型:代理驱动的协商或响应式协商。在这种情况下,服务器会返回一个包含指向可用替代资源链接的页面,以应对模糊的请求。用户会看到这些资源,并选择其中一个使用。
不幸的是,HTTP 标准没有指定在可用资源之间选择页面的格式,这阻止了该流程自动执行。除了回退到服务器驱动的协商之外,这种方法几乎总是与脚本一起使用,尤其是与 JavaScript 重定向一起使用:在检查协商标准后,脚本会执行重定向。第二个问题是,需要一个额外的请求才能获取真实资源,从而减慢了用户获得资源的速度。