HTTP 中的内容协商机制(Content Negotitation)是指客户端和服务器端就响应的内容进行交涉,然后服务端提供给客户端最为合适的资源。内容协商会以响应资源的语言、字符集、编码方式等作为判断基准。

仅仅看内容协商机制的定义,可能还不是很清楚它到底是什么。举例来说,浏览器会根据自己默认语言(我的为中文),在访问某个 URI 的 Web 页面时,在 HTTP 请求的报文中添加如 Accept-Language: zh-CN,zh;q=0.9 报文头部信息,向服务器请求中文版的 Web 页面。服务器在接收到浏览器希望获得中文版内容的协商后,在响应的报文首部中添加 Content-Language: zh-CN 报文头部信息,并返回最为合适的中文版本(如果有中文版本)的 Web 页面。

内容协商原则

其实,前文的举例就基本说明了内容协商的基本原则了。特定的 Web 页面称为资源。当客户端想要获取(特定)资源时,客户端使用其 URL 请求它。服务器使用这个 URL 来选择它提供的一个变体(每个变体被称为一个表示),并向客户端返回一个特定的表示。整个资源以及每个表示都有一个特定的 URL。当资源被调用时如何选择特定的表示是由内容协商决定的。

通过以下两种机制中的一种确定最合适的表示:

  • 客户端的特定HTTP 标头(服务器驱动协商或主动协商),这是协商特定类型资源的标准方式;
  • 服务器的 300(多项选择)或 406(不可接受)、 415(不支持的媒体类型) HTTP 响应代码(代理驱动协商或反应协商),用作回退机制;

内容协商机制的种类

内容协商技术有3种类型:服务器驱动协商、客户端驱动协商和透明协商。

服务器驱动协商(Server-driven Negotitation)

服务器驱动协商是由服务器端进行内容协商。以请求的首部字段作为参考,在服务端自动处理。但对于用户来说,以浏览器发送的信息为判定的依据。并不一定能筛选出最优的内容。

在服务器驱动协商中,浏览器(或任何其他类型的用户代理)会随 URL 一起发送多个 HTTP 报文首部字段。这些首部字段描述了用户的首选。服务器将它们用作提示,内部算法会选择最佳内容提供给客户端。如果它无法提供合适的资源,作为回退,它可能会响应 406(不可接受)或 415(不支持的媒体类型)并为其支持的媒体类型设置首部字段(例如,分别使用 Accept-Post 或 Accept-Patch 用于 POST 和 PATCH 请求)。该算法(请参阅 Apache 协商算法)是特定于服务器的,标准中未定义。

用于服务器驱动协商的标准的 HTTP/1.1 报文首部字段列表:

  • Accept:它是用逗号分隔的 MIME 类型列表,每个列表都与一个质量因子相结合,该参数表示不同 MIME 类型之间的相对偏好程度。Accept 首部字段由浏览器,或任何其它用户代理限定,并且可以根据环境而变化;
  • Accept-CH:列出了服务器可以使用的配置数据来选择适当的响应。有效值为:Device-Memory、Viewport-Width 和 Width。这是一项名为 Client Hints 的实验性技术的一部分。最初支持 Chrome 46 或更高版本。Device-Memory 值在 Chrome 61 或更高版本中;
  • Accept-CH-Lifetime:用于与 Device-Memory 所述的值 Accept-CH 报头和表示时间的设备应该选择加入与服务器共享装置存储器的量的量。该值以毫秒为单位给出,它的使用是可选的。这是一项名为 Client Hints 的实验性技术的一部分,仅适用于 Chrome 61 或更高版本;
  • Accept-Encoding:定义内容可接受的(支持的压缩)编码。该值是一个 q 因子列表(例如:br, gzip;q=0.8,表示编码值的优先级)。默认值为 identity,最低优先级(除非另有声明)。
  • Accept-Language:用于指示所述用户的语言偏好。它是具有质量因素的值列表(例如:zh-CN,zh;q=0.9)。默认值通常根据用户代理的图形界面的语言设置,但大多数浏览器允许设置不同的语言首选项;
  • User-Agent:一个特性串,让服务器和网络对等体标识请求的应用程序,操作系统,供应商和/或版本的用户代理。尽管此标头用于选择内容是合法的,但依赖它来定义用户代理支持哪些功能被认为是不好的做法;
  • Vary:由 Web 服务器在其响应中发送。它指示在服务器驱动的内容协商阶段服务器使用的首部字段列表。需要它来通知缓存决策标准,以便它可以重现它,允许缓存发挥作用,同时防止向用户提供错误的内容;

服务器驱动协商有 3 个缺点:

  • 服务器并不完全了解浏览器。即使使用 Client Hints 扩展,它也不能完全了解浏览器的功能。与客户端做出选择的反应式内容协商不同,服务器的选择总是有些随意;
  • 客户端的信息非常冗长和隐私风险(HTTP 指纹识别);
  • 当发送给定资源的多个表示时,共享缓存效率较低,服务器实现更加复杂;

客户端驱动协商(Agent-driven Negotitation)

客户端驱动协商是由客户端进行的协商方式。在这个协商中,当面对一个不明确的请求时,服务器会发回一个包含可用替代资源链接的页面。用户将看到资源并选择要使用的资源。用户从浏览器显示的可选项列表中手动选择,或者利用 JavaScript 脚本实现自动进行上述选择。例如常见的按浏览器类型切换 PC 版本或者移动设备版本的页面。

客户端驱动协商有 2 个缺点:

  • 资源选择无法自动化。HTTP 标准没有指定用于在可用资源之间进行选择的页面格式,除了回退到服务器驱动的协商之外,这种方法几乎总是与脚本结合使用,在检查协商标准后,使用脚本执行重定向;
  • 需要重定向多次请求。为了获取真正的资源,还需要一个请求,从而降低了用户对资源的可用性;

透明协商(Transparent Negotitation)

透明协商是前两者的结合体。是由服务器端和客户端各自进行内容协商的一种方法。

注意:WHATWG 的维基页面中解释了 HTTP 内容协商的一些缺点。HTML5 提供了内容协商的替代方案,例如,<source>元素。