摘要

  • RFC 2295 让同一 HTTP URI 所关联的多种表示通过机器可读的变体清单显露出来,但清单响应并不包含任何变体数据。
  • 选择、取回和呈现是分开的步骤;在一定条件下服务器可以返回已选表示,客户端也可以自行选择并请求清单中的某个变体。

一个资源不一定只有一种答案

到 20 世纪 90 年代末,Web 已经遇到一个实际问题:同一资源可能有 HTML 和 PostScript 版本,可能有英语和法语版本,也可能要适配能力不同的用户代理。发布者可以为各个版本设置不同 URL,也可以让一个 URI 在多个版本之间进行协商。难点不只是选出一个版本;代理和缓存也必须知道不同响应分别对应什么请求。

1998 年 3 月发布的 RFC 2295《HTTP 中的透明内容协商》提出了一套实验性机制,让这些候选版本变得可见。规范把每个版本称为一个“变体”,并以机器可读的描述清单将其绑定到同一个可协商资源。Alternates 响应头可以列出候选变体的 URI,并描述媒体类型、语言、源质量或功能特性等属性。这里的“透明”是指源服务器内部的变体能被外部参与方看到;它并不意味着所有浏览器都会自动协商,更不意味着选择过程不再可见。

规范定义了四个协商维度:媒体类型、字符集、语言和功能特性。第四个维度用于表达前三者无法涵盖的属性,例如 HTML 扩展或其他媒体格式的能力。内容编码(如压缩)则与此正交,并非该机制中的第五种变体维度。这个边界很重要:规范要描述的是哪一种表示可能合适,而不是服务器可以对字节执行的所有转换。

三张凭据,而非一个事件

列表响应提供的是目录。RFC 2295 将其定义为返回可协商资源的变体清单,但不返回变体数据。支持透明协商的用户代理可以比较各项,然后用普通 HTTP 请求访问某个变体 URI。规范的示例把两个动作清楚地分开:服务器先返回清单,客户端随后请求 paper.1;只有第二个响应带有论文内容。300 Multiple Choices 响应还可以附带一个供人选择的页面,让不支持协商的代理或读者手动挑选。即便如此,清单也不是最终选定的表示。

服务器并不只是被动目录。一种叫作“选择响应”(choice response)的响应会返回最佳变体的表示,也可以同时带上清单。不过,服务器必须有足够信息代表用户代理作出选择,而且所选变体必须满足规范定义的 URI 邻接条件。配套的实验性 RFC 2296 规定了远程变体选择算法,并让服务器选择成为有条件的结果:如果证据不能确定一个质量为正且明确最佳的变体,或者不满足邻接条件,算法就返回清单。客户端也可以应用自己的算法;如果响应仍带有清单,它还可以取回另一项。

因此,把历史概括成“客户端做了选择”并不准确。有时由客户端选,有时服务器可以代选。协议将候选清单、决策权、承载内容字节的响应,以及之后的呈现分别处理。从服务器的角度看,用户代理通过 Negotiate 请求头声明自己支持透明协商。但能力声明并不能证明某次请求实际使用了这套机制。

缓存是设计的一部分

协商机制可能让同一个 URI 对应不同表示。如果缓存把这些响应混为一谈,再好的选择算法也会给出错误结果。RFC 2295 因而使用 HTTP 的 Vary 和实体标签,并为变体清单增加验证器。它还说明了缓存如何从选择响应中提取普通 HTTP 响应,以及选中资源的位置如何关联到可协商资源。缓存是否正确不是外围实现细节,而是复用同一 URI 能否成立的一部分协议契约。

这一设计也带来代价。每次请求都发送完整偏好可能使请求头过长,所以用户代理往往需要在本地查看清单。但 Accept 偏好可能暴露用户所用软件或环境的特征。规范明确讨论了隐私信息泄露、变体资源响应遭到伪造,以及协商可能暴露的安全漏洞。这些是规范识别的设计风险,并不是某起已发生事故的证据。

RFC 2295 明确属于 Experimental(实验性),并声明它不构成任何 Internet 标准。其透明协商适用于 GET 和 HEAD,并不覆盖所有 HTTP 事务。我们可以从规范中还原它的目标:让候选版本可被检查,同时把选择分布在客户端、服务器和缓存之间。但本文使用的资料并未证明浏览器支持率、实际部署规模或用户端成效。被列出的变体未必真的被取回;收到响应,也不能证明读者最终看到了什么。

来源