17.3 服务器驱动的协商

在前一节中,我们了解了客户端驱动的协商存在的若干缺点。大部分缺点都涉及客户端和服务器之间通信量的增长,这些通信量用来决定什么页面才是对请求的最佳响应。减少额外通信量的一种方法是让服务器来决定发送哪个页面回去,但为了做到这一点,客户端必须发送有关客户偏好的足够信息,以便服务器能够作出准确的决策。服务器通过客户端请求的首部集来获得这方面的信息。

有以下两种机制可供 HTTP 服务器评估发送什么响应给客户端比较合适。

  • 检查内容协商首部集。服务器察看客户端发送的 Accept 首部集,设法用相应的响应首部与之匹配。

  • 根据其他(非内容协商)首部进行变通。例如,服务器可以根据客户端发送的 User-Agent 首部来发送响应。

后面的小节将详细介绍这两种机制。

17.3.1 内容协商首部集

客户端可以用表 17-2 中列出的 HTTP 首部集发送用户的偏好信息。

表17-2 Accept首部集

首  部 描  述
Accept 告知服务器发送何种媒体类型
Accept-Language 告知服务器发送何种语言
Accept-Charset 告知服务器发送何种字符集
Accept-Encoding 告知服务器采用何种编码

注意,这些首部与第 15 章讨论的那些实体首部非常类似。不过,这两种首部的用途截然不同。正如第 15 章中所述,实体首部集像运输标签,它们描述了把报文从服务器传输给客户端的过程中必须的各种报文主体属性。而内容协商首部集是由客户端发送给服务器用于交换偏好信息的,以便服务器可以从文档的不同版本中选择出最符合客户端偏好的那个来提供服务。

服务器用表 17-3 中列出的实体首部集来匹配客户端的 Accept 首部集。

表17-3 Accept首部集和匹配的文档首部集

Accept首部 实体首部
Accept Content-Type
Accept-Language Content-Language
Accept-Charset Content-Type
Accept-Encoding Content-Encoding

注意,由于 HTTP 是无状态的协议(表示服务器不会在不同的请求之间追踪客户端的偏好),所以客户端必须在每个请求中都发送其偏好信息。

如果两个客户端都发送了 Accept-Language 首部,描述它们感兴趣的语言信息,服务器就能够决定发送 www.joes-hardware.com 的何种版本给哪个客户端了。让服务器自动选择发送回去的文档,减少了往返通信的时延,这种时延是客户端驱动模型中无法避免的。

然而,假设某个客户端偏好西班牙文,那服务器应当回送哪个版本的页面呢?英语还是法语?服务器只有两种选择:猜测或回退到客户端驱动模型,问客户端选择哪个。假如这个西班牙人碰巧懂一点英语,他可能会选择英文页面,这不是最理想的,但它能解决问题。在这种情况下,这个西班牙人需要有办法传达更多与其偏好有关的信息,也就是他的确对英语略知一二,在没有西班牙语的时候,英语也行。

幸运的是,HTTP 提供了一种机制,可以让与这个西班牙人情况类似的客户端更详细地描述其偏好。这种机制就是质量值(简称 q 值 )。

17.3.2 内容协商首部中的质量值

HTTP 协议中定义了质量值,允许客户端为每种偏好类别列出多种选项,并为每种偏好选项关联一个优先次序。例如,客户端可以发送下列形式的 Accept-Language 首部:

  1. Accept-Language: en;q=0.5, fr;q=0.0, nl;q=1.0, tr;q=0.0

其中 q 值的范围从 0.0 ~ 1.0(0.0 是优先级最低的,而 1.0 是优先级最高的)。上面列出的那个首部,说明该客户端最愿意接收荷兰语(缩写为 nl)文档,但英语(缩写为 en)文档也行;无论如何,这个客户端都不愿意收到法语(缩写为 fr)或土耳其语(缩写为 tr)的版本。注意,偏好的排列顺序并不重要,只有与偏好相关的 q 值才是重要的。

服务器偶尔也会碰到找不到文档可以匹配客户端的任何偏好的情况。对于这种情况,服务器可以修改文档,也就是对文档进行转码,以匹配客户端的偏好。我们将在本章后面讨论这种机制。

17.3.3 随其他首部集而变化

服务器也可以根据其他客户端请求首部集来匹配响应,比如 User-Agent 首部。例如,服务器知道老版本的浏览器不支持 JavaScript 语言,这样就可以向其发送不含有 JavaScript 的页面版本。

在这种情况下,没有 q 值机制可供查找“最近似”的匹配。服务器或者去找完全匹配,或者简单地有什么就给什么,这取决于服务器的实现。

由于缓存需要尽力提供所缓存文档中正确的“最佳”版本,HTTP 协议定义了服务器在响应中发送的 Vary 首部。这个首部告知缓存(还有客户端和所有下游的代理)服务器根据哪些首部来决定发送响应的最佳版本。本章后面会更详细地讨论 Vary 首部。

17.3.4 Apache中的内容协商

这里概括了著名的 Web 服务器 Apache 是如何支持内容协商的。网站的内容提供者,比如说 Joe,要负责为 Joe 的索引页面提供不同的版本。Joe 还必须把这些索引页面文件放在和站点相关的 Apache 服务器的适当目录下。用以下两种方式可以启用内容协商。

  • 在网站目录中,为网站中每个有变体的 URI 创建一个 type-map(类型映射)文件。这个 type-map 文件列出了每个变体和其相关的内容协商首部集。

  • 启用 MultiViews 指令,这样会使 Apache 自动为目录创建 type-map 文件。

  1. 使用type-map文件

Apache 服务器需要知道 type-map 文件的命名规则。可以在服务器的配置文件中设置 handler 来说明 type-map 文件的后缀名。例如:

  1. AddHandler type-map .var

这行就说明了后缀是 .var 的文件就是 type-map 文件。

这里给出一个 type-map 文件示例:

  1. URI: joes-hardware.html
  2.  
  3. URI: joes-hardware.en.html
  4. Content-type: text/html
  5. Content-language: en
  6. URI: joes-hardware.fr.de.html
  7. Content-type: text/html;charset=iso-8859-2
  8. Content-language: fr, de

根据这个 type-map 文件,Apache 服务器就知道要发送 joes-hardware.en.html 给请求英语版的客户端,发送 joes-hardware.fr.de.html 给请求法语版的客户端。Apache 服务器也支持质量值,具体信息请参阅它的文档。

  1. 使用MultiView

为了使用 MultiView,必须在网站目录下的 access.conf 文件中的适当小节(<Directory>、<Location>,或 <Files>)使用 OPTION 指令来启用它。

如果启用了 MultiView,而浏览器又请求了名为 joes-hardware 的资源,服务器就会查找所有名字中含有 joes-hardware 的文件,并为它们创建 type-map 文件。服务器会根据名字猜测其对应的内容协商首部集。例如,法语版的 joes-hardware 应当含有 .fr

17.3.5 服务器端扩展

另一种在服务器端实现内容协商的方法是使用服务器端扩展,比如微软的动态服务器页面(Microsoft's Active Server Pages,ASP)。参见第 8 章中关于服务器端扩展的综述。