内容协商
在 HTTP 中,内容协商 是一种机制,用于向同一 URI 提供资源的各种表示,以帮助用户代理指定最适合用户的表示(例如,文档语言、图像格式或内容编码)。
注意: 你可以在 WHATWG 的一个 wiki 页面中找到 HTTP 内容协商的一些缺点。HTML 通过例如 <source>
元素提供了内容协商的替代方案。
内容协商的原则
特定文档称为资源。当客户端想要获取资源时,客户端通过 URL 请求它。服务器使用此 URL 选择可用的变体之一——每个变体称为一个表示——并向客户端返回特定的表示。整个资源以及每个表示都有一个特定的 URL。内容协商决定了在调用资源时如何选择特定的表示。客户端和服务器之间有几种协商方式。
最合适的表示通过以下两种机制之一来识别:
- 客户端的特定 HTTP 头(服务器驱动协商或主动协商),这是协商特定类型资源的标准方式。
- 服务器的
300
(多种选择)或406
(不可接受)、415
(不支持的媒体类型)HTTP 响应状态码(代理驱动协商或被动协商),用作回退机制。
多年来,其他内容协商方案,例如透明内容协商和 Alternates
头,已被提出。它们未能获得关注并被放弃。
服务器驱动的内容协商
在服务器驱动内容协商或主动内容协商中,浏览器(或任何其他类型的用户代理)与 URL 一起发送多个 HTTP 头。这些头描述了用户的首选。服务器将它们用作提示,内部算法选择最佳内容提供给客户端。如果无法提供合适的资源,它可能会响应 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 类型之间的相对偏好程度。
Accept
头由浏览器或任何其他用户代理定义,并可能根据上下文而异。例如,获取 HTML 页面或图像、视频或脚本。在地址栏中输入的文档或通过 <img>
、<video>
或 <audio>
元素链接的元素不同。浏览器可以自由使用他们认为最合适的头值;常见浏览器的默认值的详尽列表可用。
Accept-CH
头
注意: 这是名为客户端提示的实验性技术的一部分。Chrome 46 或更高版本提供了初步支持。Device-Memory 值在 Chrome 61 或更高版本中。
实验性的 Accept-CH
列出了服务器可用于选择适当响应的配置数据。有效值为:
值 | 含义 |
---|---|
Device-Memory |
表示设备的近似 RAM 量。此值是通过四舍五入到最接近的 2 的幂并将该数字除以 1024 得出的近似值。例如,512 兆字节将报告为 0.5 。 |
视口宽度 |
指示 CSS 像素中的布局视口宽度。 |
宽度 |
指示物理像素中的资源宽度(换句话说,图像的固有大小)。 |
Accept-Encoding
头
Accept-Encoding
头定义了可接受的内容编码(支持的压缩)。该值是一个 q 因子列表(例如,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 重定向结合使用:在检查协商标准后,脚本执行重定向。第二个问题是,需要再发一个请求才能获取真实资源,从而减慢了资源对用户的可用性。