Apache内容协商
Apache HTTPD支持HTTP/1.1
规范中描述的内容协商。它可以根据浏览器提供的媒体类型,语言,字符集和编码首选项,选择资源的最佳表示形式。它还实现了一些功能,可以更智能地处理来自发送不完整协商信息的浏览器的请求。
内容协商由mod_negotiation
模块提供,该模块默认编译。
关于内容协商
资源可以以多种不同的表示形式提供。例如,它可能以不同语言或不同媒体类型或组合形式提供。选择最合适选择的一种方法是为用户提供索引页面,然后让他们选择。但是,服务器通常可以自动选择。这是可以做到的,因为浏览器可以在每个请求中发送有关他们喜欢的表示形式的信息。例如,如果可能的话,浏览器可以表明它希望看到法语信息,否则会发送英语信息。浏览器通过请求中的标题指示其首选项。仅请求法语的表示,那么浏览器将发送法语内容信息。
Accept-Language: fr
请注意,只有在可以选择表示时才会应用此首选项,并且这些首选项因语言而异。
作为更复杂请求的示例,此浏览器已配置为接受法语和英语,但更喜欢法语,并接受各种媒体类型,更喜欢HTML而不是纯文本或其他文本类型,并且优先选择GIF或JPEG而不是其他媒体类型 ,但也允许任何其他媒体类型作为最后的手段:
Accept-Language: fr; q=1.0, en; q=0.5
Accept: text/html; q=1.0, text/*; q=0.8, image/gif; q=0.6, image/jpeg; q=0.6, image/*; q=0.5, */*; q=0.1
httpd支持“服务器驱动的”内容协商,如HTTP/1.1
规范中所定义。它完全支持Accept
,Accept-Language
,Accept-Charset
和Accept-Encoding
请求标头。httpd还支持“透明”内容协商,这是RFC 2295
和RFC 2296
中定义的实验性协商协议。它不支持这些RFC中定义的“功能协商”。
资源是由URI(RFC 2396)标识的概念实体。像Apache HTTP Server这样的HTTP服务器提供对其命名空间内资源表示的访问,每个表示形式为具有已定义媒体类型,字符集,编码等的字节序列。每个资源可以关联在任何给定时间使用零个,一个或多个表示。如果有多个表示可用,则该资源被称为可协商的,并且其每个表示被称为变体。可转让资源的变体的变化方式称为协商的维度。
httpd的协商
为了协商资源,需要向服务器提供有关每个变体的信息。这可以通过以下两种方式之一完成:
- 使用类型映射(即
*.var
文件)明确命名包含变体的文件,或 - 使用“MultiViews”搜索,服务器执行隐式文件名模式匹配,并从结果中进行选择。
使用类型映射文件
类型映射是与名为type-map
的处理程序相关联的文档(或者,对于与旧的httpd配置的向后兼容性,MIME类型的应用程序/x-type-map
)。请注意,要使用此功能,必须在配置中设置一个处理程序,将文件后缀定义为type-map
是最好的。
AddHandler type-map .var
在服务器配置文件中。
类型映射文件应与其描述的资源具有相同的名称,后跟扩展名.var
。在下面显示的示例中,资源名为foo
,因此类型映射文件名为foo.var
。
此文件应包含每个可用变体的条目; 这些条目由连续的HTTP格式标题行组成。不同变体的条目由空行分隔。空白行在条目中是非法的。通常开始一个映射文件,其中包含整个组合实体的条目(尽管这不是必需的,如果存在则将被忽略)。示例映射文件如下所示。
此文件中的URI相对于类型映射文件的位置。通常,这些文件将与类型映射文件位于同一目录中,但这不是必需的。可以为与映射文件位于同一服务器上的任何文件提供绝对或相对URI。
URI: foo
URI: foo.en.html
Content-type: text/html
Content-language: en
URI: foo.fr.de.html
Content-type: text/html;charset=iso-8859-2
Content-language: fr, de
请注意,即使启用了Multiviews,类型映射文件也将优先于文件名的扩展名。如果变体具有不同的源质量,则可以通过媒体类型的“qs”参数指示,如此图片(可用作JPEG,GIF或ASCII艺术):
URI: foo
URI: foo.jpeg
Content-type: image/jpeg; qs=0.8
URI: foo.gif
Content-type: image/gif; qs=0.5
URI: foo.txt
Content-type: text/plain; qs=0.01
qs
值可以在0.000
到1.000
的范围内变化。请注意,永远不会选择qs
值为0.000
的任何变体。没有’qs’参数值的变量的因子为1.0
。qs
参数表示此变体与其他可用变体相比的相对“qs”,与客户端的能力无关。例如,如果JPEG文件试图表示照片,则该文件通常具有比ASCII文件更高的源质量。但是,如果表示的资源是原始ASCII艺术,那么ASCII表示将具有比JPEG表示更高的源质量。因此,qs
值特定于给定变体,具体取决于它所代表的资源的性质。
MultiviewsMultiViews
是一个每个目录选项,它可以在httpd.conf
中的<Directory>
,<Location>
或<Files>
部分中使用Options
指令设置,或者在.htaccess
文件中设置(如果正确设置了AllowOverride
)。请注意,选项全部不设置MultiViews
; 必须通过名字指定它。
MultiViews的效果如下:如果服务器收到/some/dir/foo
的请求,如果/some/dir
启用了MultiViews,并且/some/dir/foo
不存在,那么服务器会读取寻找的目录名为foo.*
的文件,有效地伪造了一个类型映射,该映射命名所有这些文件,为它们分配相同的媒体类型和内容编码,如果客户端通过名称请求其中一个文件。然后,它会根据客户的要求选择最佳匹配。
如果服务器尝试索引目录,MultiViews也可能适用于搜索DirectoryIndex
指令指定的文件。如果配置文件指定:
DirectoryIndex index
然后,如果两者都存在,服务器将在index.html
和index.html3
之间进行选择。如果两者都不存在,并且存在index.cgi
服务器将运行index.cgi
。
如果在读取目录时找到的其中一个文件没有mod_mime
识别的扩展名来指定其Charset
,Content-Type
,Language
或Encoding
,则结果取决于MultiViewsMatch
指令的设置。此指令确定处理程序,过滤器和其他扩展类型是否可以参与MultiViews
协商。
协商方法
在httpd从类型映射文件或目录中的文件名获得给定资源的变体列表之后,它会调用两种方法之一来决定要返回的“最佳”变体(如果有)。为了使用httpd的内容协商功能,没有必要知道协商实际发生的任何细节。
有两种协商方法:
服务器驱动协商 - 在正常情况下使用与httpd算法的服务器驱动的协商。httpd算法在下面更详细地解释。当使用该算法时,httpd有时可以“摆弄”特定维度的品质因数以获得更好的结果。httpd可以改变质量因素的方式在下面更详细地解释。
透明内容协商 - 当浏览器通过RFC 2295中定义的机制专门请求时,使用透明内容协商。此协商方法使浏览器完全控制决定“最佳”变体,因此结果取决于浏览器使用的特定算法。作为透明协商过程的一部分,浏览器可以要求httpd运行RFC 2296中定义的“远程变量选择算法”。
httpd协商算法
httpd可以使用以下算法选择“最佳”变体(如果有)返回浏览器。该算法不是可配置的。它的运作方式如下:
第1步 - 首先,对于协商的每个维度,检查相应的Accept *
标头字段并为每个变体分配质量。如果任何维度的Accept *
标头暗示此变体不可接受,则将其删除。如果没有变体,请转到第4步。
第2步 - 通过消除过程选择“最佳”变体。按顺序应用以下每个测试。在每次测试中未选择的任何变体都被消除。每次测试后,如果只剩下一个变量,请将其选为最佳匹配,然后继续执行第3步。如果仍有多个变量,请继续进行下一个测试。
- 将Accept标头中的质量因子与此变体媒体类型的源质量因子相乘,并选择具有最高值的变体。
- 选择具有最高语言质量因子的变体。
- 使用
Accept-Language
标头中的语言顺序(如果存在)或LanguagePriority
指令中的语言顺序(如果存在),选择具有最佳语言匹配的变体。 - 选择具有最高“级别”媒体参数的变体(用于提供
text/html
媒体类型的版本)。 - 选择具有最佳字符集媒体参数的变体,如
Accept-Charset
标题行所示。除非明确排除,否则使用Charset ISO-8859-1
。假定具有text/*
媒体类型但未与特定字符集明确关联的变体在ISO-8859-1
中。 - 选择那些具有非
ISO-8859-1
关联字符集媒体参数的变体。如果没有此类变体,请选择所有变体。 - 选择具有最佳编码的变体。如果存在具有用户代理可接受的编码的变体,请仅选择这些变体。否则,如果存在编码和非编码变体的混合,请仅选择未编码的变体。如果编码所有变体或未编码所有变体,请选择所有变体。
- 选择内容长度最小的变体。
- 选择剩余的第一个变体。这将是类型映射文件中列出的第一个,或者从目录中读取变体时,使用ASCII代码顺序排序时文件名首先出现的变量。
- 该算法现在选择了一个“最佳”变体,因此将其作为响应返回。HTTP响应头Vary设置为指示协商的维度(浏览器和缓存可以在缓存资源时使用此信息)。
- 到达此处意味着未选择任何变体(因为浏览器不接受任何变体)。返回406状态(表示“无法接受的表示”),其响应正文由列出可用变体的HTML文档组成。还要设置HTTP Vary标头以指示方差的维度。
摆弄质量值
httpd有时会改变上面对httpd协商算法的严格解释所期望的质量值。这是为了从不发送完整或准确信息的浏览器的算法中获得更好的结果。一些最流行的浏览器发送Accept头信息,否则会导致在许多情况下选择错误的变体。如果浏览器发送完整且正确的信息,则不会应用这些小提琴。
媒体类型和通配符Accept:request
标头指示媒体类型的首选项。它还可以包括“通配符”媒体类型,例如image/*
或*/*
,其中*
匹配任何字符串。所以请求包括:
Accept: image/*, */*
表示任何以image/
开头的类型都是可以接受的,就像任何其他类型一样。除了可以处理的显式类型之外,一些浏览器还会定期发送通配符。例如:
Accept: text/html, text/plain, image/gif, image/jpeg, */*
这样做的目的是表明明确列出哪些类型是首选的,但如果有不同的表示,那也没关系。使用显式质量值,浏览器真正想要的是:
Accept: text/html, text/plain, image/gif, image/jpeg, */*; q=0.01
显式类型没有品质因子,因此它们默认为1.0(最高)的偏好。通配符*/*
的优先级为0.01
,因此只有在没有变量与明确列出的类型匹配时才会返回其他类型。
如果Accept:标头根本不包含q
因子,httpd将*/*
”的q
值设置为0.01
以模拟所需的行为。它还将格式为type/*
的通配符的q
值设置为0.02
(因此这些优先于匹配*/*
的匹配。如果Accept:标头上的任何媒体类型包含aq
因子,则这些特殊值不是应用,所以来自浏览器的请求发送显式信息以按预期开始工作。
语言协商异常
在httpd 2.0中新增了一些异常,在协商算法中添加了一些异常,以便在语言协商找不到匹配时允许优雅的回退。
当客户端请求服务器上的页面,但服务器找不到与浏览器发送的Accept语言匹配的单个页面时,服务器将向客户端返回“No Acceptable Variant”或“Multiple Choices”响应。为了避免这些错误消息,可以将httpd配置为在这些情况下忽略Accept语言,并提供一个未明确匹配客户端请求的文档。ForceLanguagePriority
指令可用于覆盖这些错误消息中的一个或两个,并以LanguagePriority
指令的形式替换服务器判断。
当没有找到其他匹配时,服务器还将尝试匹配语言子集。例如,如果客户端请求英语为en-GB语言的文档,则HTTP/1.1标准通常不允许服务器将其与标记为en的文档进行匹配。(请注意,在Accept-Language
标题中包含en-GB
肯定是一个配置错误,因为读者不太可能理解英国英语,但一般不懂英语。不幸的是,很多当前客户端具有与此类似的默认配置。)但是,如果没有其他语言匹配且服务器即将返回“No Acceptable Variants”错误或回退到LanguagePriority,则服务器将忽略子集规范并匹配en-GB相应文件。隐式地,httpd会将父语言添加到客户端可接受的语言列表中,并且质量非常低。但请注意,如果客户端请求en-GB; q=0.9,fr; q=0.8
,并且服务器具有指定为“en”和“fr”的文档,则将返回“fr”文档。这对于保持对HTTP/1.1
规范的遵从性以及与正确配置的客户端有效地工作是必要的。
为了支持高级技术(例如cookie或特殊URL路径)来确定用户的首选语言,因为httpd 2.0.47 mod_negotiation
识别环境变量prefer-language
。如果它存在且包含适当的语言标记,则mod_negotiation
将尝试选择匹配的变体。如果没有这样的变体,则适用正常的协商过程。
示例 -
SetEnvIf Cookie "language=(.+)" prefer-language=$1
Header append Vary cookie
透明内容协商的扩展
httpd扩展了透明内容协商协议(RFC 2295),如下所示。在变体列表中使用新的{encoding...}
元素来标记仅具有特定内容编码的变体。RVSA/1.0算法(RFC 2296)的实现被扩展为识别列表中的编码变体,并且根据Accept-Encoding
请求头,只要它们的编码是可接受的,就将它们用作候选变体。在选择最佳变体之前,RVSA/1.0实现不会将计算的质量因子舍入到5位小数。
关于超链接和命名约定的注释
如果您正在使用语言协商,则可以在不同的命名约定之间进行选择,因为文件可以具有多个扩展名,并且扩展的顺序通常无关紧要。
当具有该文件的不同语言变体时,典型文件具有MIME类型扩展(例如,html
),可能是编码扩展(例如,gz
),当然还有语言扩展(例如,en
)。
例子:
foo.en.html
foo.html.en
foo.en.html.gz
这里有一些文件名的例子以及有效和无效的超链接:
文件名 | 有效的超链接 | 无效的超链接 |
---|---|---|
foo.html.en |
foo ,foo.html |
— |
foo.en.html |
foo |
foo.html |
foo.html.en.gz |
foo ,foo.html |
foo.gz ,foo.html.gz |
foo.en.html.gz |
foo |
foo.html ,foo.html.gz ,foo.gz |
foo.gz.html.en |
foo ,foo.gz ,foo.gz.html |
foo.html |
foo.html.gz.en |
foo ,foo.html ,foo.html.gz |
foo.gz |
查看上表,应该注意到在超链接(例如,foo)中始终可以使用没有任何扩展名的名称。优点是可以隐藏文档rsp
的实际类型。文件并可以在以后更改它,例如,从html更改为shtml或cgi而不更改任何超链接引用。
如果您想继续在超链接中使用MIME类型(例如foo.html
),则语言扩展(包括编码扩展,如果有的话)必须位于MIME类型扩展的右侧(例如,foo.html.en
)。
关于缓存的注意事项
当缓存存储表示时,它将其与请求URL相关联。下次请求该URL时,缓存可以使用存储的表示。但是,如果资源在服务器上可协商,则可能导致仅缓存第一个请求的变体,并且后续缓存命中可能返回错误的响应。为了防止这种情况,httpd通常将内容协商后返回的所有响应标记为HTTP/1.0客户端不可缓存。httpd还支持HTTP/1.1协议功能,以允许缓存协商的响应。
对于来自HTTP/1.0
兼容客户端(浏览器或缓存)的请求,可以使用指令CacheNegotiatedDocs
来缓存需要协商的响应。该指令可以在服务器配置或虚拟主机中给出,并且不带参数。它对来自HTTP/1.1
客户端的请求没有影响。
对于HTTP/1.1
客户端,httpd发送Vary HTTP响应头以指示响应的协商维度。缓存可以使用此信息来确定是否可以从本地副本提供后续请求。要鼓励缓存使用本地副本而不管协商维度,请设置force-no-vary
环境变量。