"没有可接受的变体QUOT;从多视图中的Apache [英] "no acceptable variant" from MultiViews in Apache
问题描述
在一个基于PHP的应用程序的一个部署,Apache的多视图
选项被用来隐藏一个请求分发脚本的PHP扩展。例如。于请求
/页/约
...将由
办理 /page.php
...与请求的URI提供尾随部分 PATH_INFO
。
这其中大部分工作正常,但偶尔也会导致这样的错误
时间 [错误] [客户86.x.x.x]没有可接受的变体:/路径/到/文件/根/页
我的问题是:什么偶尔会触发这个错误,我怎么能解决这个问题。
简答
在以下所有可能会出现此错误是同时成立:
- 您的Web服务器有在Multiviews 启用
-
您是允许在Multiviews通过分配它们与
将AddType
指令的任意类型,最有可能与这样的产品线以满足PHP文件:将AddType应用/ X的httpd - PHP的.php
- 您的客户端的浏览器请求的
接受$ C发送$ C>
头不包括* / *
作为一个可以接受的MIME类型(这是极不寻常的,这就是为什么你看到的错误很少)。 - 您有您的
MultiviewsMatch
指令设置为NegotiatedOnly
。
您可以通过添加下面的咒语,Apache配置解析错误:
<文件* .PHP>
MultiviewsMatch任何
< /文件>
说明
了解正在发生的事情在这里至少需要获得Apache的的 mod_negotiation模块
和HTTP的接受
和接受-美孚
头。此前创下的OP描述的错误,我也不知道其中任一;我有 mod_negotiation模块
启用不是刻意的选择,而是因为这是如何 apt-get的
设置Apache对我来说,我不得不启用多视图
无,除了影响太多了解,这将让我离开的.php
离年底我网址。你的情况可能是相似或相同的。
因此,这里是我不知道的一些重要基本面:
-
请求头像
接受
和接受语言
让客户端指定哪些MIME类型或语言,它是可以接受的他们接受了可接受的类型或语言的响应,以及指定加权preferences。 (当然,这些都是如果服务器具有或能够产生的唯一有用的,基于这些头不同的反应。)例如,铬发送折以下标题对我来说,每当我加载一个页面:<$c$c>Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
接受编码:gzip,紧缩,SDCH
接受语言:EN-GB,EN-US; Q = 0.8,连接; Q = 0.6
-
Apache的
mod_negotiation模块
可以让您存储多个文件,如myresource.html.en
,myresource.html.fr
,myresource.pdf.en
和myresource.pdf.fr
在同一个文件夹中,然后自动使用请求的接受 - *
标题来决定当客户端发送到myresource 请求该服务code>。这样做有两种方式。首先是建立在同一个文件夹中的类型地图文件明确声明MIME类型和语言每个可用的文件。另一种是在Multiviews。
-
当在Multiviews启用...
在Multiviews
...如果服务器收到
请求/一些/目录/ foo的
和/一些/目录/ foo的
不存在,则服务器会寻找一个名为foo的所有文件的目录。*
,并有效假货一个类型的地图,名称所有这些文件,赋予它们的不同的介质类型和内容编码它会如果客户曾要求按名称其中之一。然后,选择客户要求的最佳匹配,并返回该文档。
块引用>
这里要注意的重要一点是,接受
头仍被即使在Multiviews Apache的尊重启用;从类型映射的方法,唯一的区别是,Apache是从他们的文件扩展名,而不是通过你在一个类型映射明确声明它推断MIME类型的文件。
的没有可接受的变种的错误被Apache抛出(和406响应发送)当存在文件已收到的URL,但它不能提供任何他们,因为他们的MIME类型不符合任何请求的规定的可能性接受
头。 (同样的事情可以发生,如果有,例如,在一个可以接受的语言无变体。)这是一个符合HTTP规范,其中指出:
如果一个Accept头域present,如果服务器不能发送根据联合接受字段值这是可以接受的响应,那么服务器应该发送一个406(不接受)响应。
块引用>您可以测试这种行为很轻松了。只要创建
的test.html
含有与在Multiviews Apache服务器启用的根目录的字符串Hello World被称为一个文件,然后尝试用Accept头的许可证要求它HTML响应与一个不。我在这里展示这个我的地方(Ubuntu的)机上的卷曲
:$卷曲--header&ldquo;接受:text / html的&QUOT;本地主机/测试的
你好,世界
$卷曲--header&ldquo;接受:图像/ PNG&QUOT;本地主机/测试的
&LT; DOCTYPE HTML PUBLIC&QUOT;! - // IETF // DTD HTML 2.0 // EN&QUOT;&GT;
&LT; HTML和GT;&LT; HEAD&GT;
&LT;标题&GT; 406不可接受&LT; /标题&GT;
&LT; /头&GT;&LT;身体GT;
&LT; H1&GT;不接受&LT; / H1&GT;
&LT; P&gt;该请求的资源/测试的一个适当的再presentation不能在此服务器上找到&LT; / P&GT;
可用变量:
&LT; UL&GT;
&LT;立GT;&LT; A HREF =&QUOT; test.html的&QUOT;&GT;的test.html&LT; / A&GT; ,类型text / html&LT; /李&GT;
&LT; / UL&GT;
&LT;小时&GT;
&LT;地址&gt;的Apache / 2.4.6(Ubuntu的)在本地主机端口80℃的服务器; /地址&gt;
&LT; /身体GT;&LT; / HTML&GT;这给我们带来了一个问题,我们现在还没有解决:如何
mod_negotiate
确定MIME类型决定是否可以成为它的时候一个PHP文件?由于文件将被执行,并能吐出任何内容类型
头它喜欢的类型不是在执行之前已知的。那么,在默认情况下,答案是多视图根本不会成为
的.php
文件。但有机会,你接下来的互联网上很多很多的职位之一的意见(我在第一页上获得4,如果我谷歌的PHP阿帕奇多视图,显然是href=\"http://stackoverflow.com/a/14306831/1709587\">顶个的将AddType应用/ X的httpd - PHP的.php
咦?为什么这个神奇让Apache竭诚为
的.php
文件?当然,浏览器不包括应用程序/ X的httpd - PHP
因为他们会在他们的接受
头?好吧,不完全是。但是,所有的主要的做包括
* / *
(从而允许任何MIME类型的响应 - 他们正在使用的接受
只为前pressing preference加权头,的不的为限制类,他们会接受的。)这将导致mod_negotiation模块
要愿意选择和服务的.php
只要文件作为一些MIME类型 - 任何在所有! - 与它们相关联例如,如果我只是输入一个URL到铬或Firefox浏览器,在地址栏中的
接受
头的浏览器发送的,在Chromium的情况下...<$c$c>Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
...并在Firefox的情况:
接受:text / html的,是application / xhtml + xml的,应用/ XML; Q = 0.9 * / *;问= 0.8
这两种头包含
* / *
作为一个可以接受的内容类型,从而使服务器提供的任何内容的文件类型它喜欢。但一些不太流行的浏览器的不的接受* / *
- 或者只是将其包含在页面请求,不加载的内容时,&LT;脚本&GT;
或&LT; IMG&GT;
标签,你也许还可以通过PHP服务 - 而这也正是我们的问题就来了从。如果您检查导致406错误的请求的用户代理,你可能会看到,他们从相对不寻常的用户代理是。当我经历了这个错误,那是当我有
的src
的&LT; IMG&GT;
元素指向一个PHP脚本动态提供图像(从URL省略了的.php
扩展名),我第一次亲眼目睹了失败的黑莓用户:的Mozilla / 5.0(黑莓手机; U;黑莓9320; FR)为AppleWebKit / 534.11 +(KHTML,例如Gecko)版本/ 7.1.0.714移动版Safari / 534.11 +
要解决这个问题,我们需要让
mod_negotiate
通过比给他们一个任意类型,然后依赖于浏览器等发送<$一些手段服务于PHP脚本C $ C>接受:* / * 头。要做到这一点,我们使用MultiviewsMatch
指令指定多视图可以为PHP文件,无论它们是否匹配请求的接受
头。默认选项是NegotiatedOnly
:
的
NegotiatedOnly
选项提供了基本名称下面每一个扩展名必须关联到一个公认的mod_mime
扩展内容协商,如:字符集,内容类型,语言或编码。这是最严格落实以最少意外的副作用,而且是默认的行为。
块引用>但是,我们可以得到我们想要的
任何
选项:
您可能会最终允许
任何
扩展匹配,即使的mod_mime
无法识别的扩展名。
块引用>要限制这种规则的改变只是
的.php
的文件中,我们使用的&LT; FilesMatch&GT;
指令,如:&LT;文件* .PHP&GT;
MultiviewsMatch任何
&LT; /文件&GT;和与微小(但难以数字输出)的变化,我们就大功告成了!
In one deployment of a PHP-based application, Apache's
MultiViews
option is being used to hide the .php extension of a request dispatcher script. E.g. a request to/page/about
...would be handled by
/page.php
...with the trailing part of the request URI available in
PATH_INFO
.Most of the time this works fine, but occasionally results in errors like
[error] [client 86.x.x.x] no acceptable variant: /path/to/document/root/page
My question is: What triggers this error occasionally, and how can I fix the problem?
解决方案Short Answer
This error can occur when all the following are simultaneously true:
- Your webserver has Multiviews enabled
You are allowing Multiviews to serve PHP files by assigning them an arbitrary type with the
AddType
directive, most likely with a line like this:AddType application/x-httpd-php .php
- Your client's browser sends with requests an
Accept
header that does not include*/*
as an acceptable MIME type (this is highly unusual, which is why you see the error only rarely).- You have your
MultiviewsMatch
directive set to its default ofNegotiatedOnly
.You can resolve the error by adding the following incantation to your Apache config:
<Files "*.php"> MultiviewsMatch Any </Files>
Explanation
Understanding what is going on here requires getting at least a superficial overview of the workings of Apache's
mod_negotiation
and HTTP'sAccept
andAccept-Foo
headers. Prior to hitting the bug described by the OP, I knew nothing about either of these; I hadmod_negotiation
enabled not by deliberate choice but because that's howapt-get
set up Apache for me, and I had enabledMultiViews
without much understanding of the implications of that besides that it would let me leave.php
off the end of my URLs. Your circumstances may be similar or identical.So here are some important fundamentals that I didn't know:
request headers like
Accept
andAccept-Language
let the client specify what MIME types or languages it is acceptable for them to receive the response in, as well as specifying weighted preferences for the acceptable types or languages. (Naturally, these are only useful if the server has, or is capable of generating, different responses based upon these headers.) For example, Chromium sends off the following headers for me whenever I load a page:Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding:gzip,deflate,sdch Accept-Language:en-GB,en-US;q=0.8,en;q=0.6
Apache's
mod_negotiation
lets you store multiple files likemyresource.html.en
,myresource.html.fr
,myresource.pdf.en
andmyresource.pdf.fr
in the same folder and then automatically use the request'sAccept-*
headers to decide which to serve when the client sends a request tomyresource
. There are two ways of doing this. The first is to create a Type Map file in the same folder that explicitly declares the MIME Type and language for each of the available documents. The other is Multiviews.When Multiviews are enabled...
Multiviews
... If the server receives a request for
/some/dir/foo
and/some/dir/foo
does not exist, then the server reads the directory looking for all files namedfoo.*
, and effectively fakes up a type map which names all those files, assigning them the same media types and content-encodings it would have if the client had asked for one of them by name. It then chooses the best match to the client's requirements, and returns that document.The important thing to note here is that the
Accept
header is still being respected by Apache even with Multiviews enabled; the only difference from the type map approach is that Apache is inferring the MIME types of files from their file extensions rather than through you explicitly declaring it in a type map.The no acceptable variant error is thrown (and a 406 response sent) by Apache when there exist files for the URL it has received, but it's not allowed to serve any of them because their MIME types don't match any of the possibilities provided in the request's
Accept
header. (The same thing can happen if there is, for example, no variant in an acceptable language.) This is compliant with the HTTP spec, which states:If an Accept header field is present, and if the server cannot send a response which is acceptable according to the combined Accept field value, then the server SHOULD send a 406 (not acceptable) response.
You can test this behaviour easily enough. Just create a file called
test.html
containing the string "Hello World" in the webroot of an Apache server with Multiviews enabled and then try to request it with an Accept header that permits HTML responses versus one that doesn't. I demonstrate this here on my local (Ubuntu) machine withcurl
:$ curl --header "Accept: text/html" localhost/test Hello World $ curl --header "Accept: image/png" localhost/test <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>406 Not Acceptable</title> </head><body> <h1>Not Acceptable</h1> <p>An appropriate representation of the requested resource /test could not be found on this server.</p> Available variants: <ul> <li><a href="test.html">test.html</a> , type text/html</li> </ul> <hr> <address>Apache/2.4.6 (Ubuntu) Server at localhost Port 80</address> </body></html>
This brings us to a question that we haven't yet addressed: how does
mod_negotiate
determine the MIME type of a PHP file when deciding whether it can serve it? Since the file is going to be executed, and could spit out anyContent-Type
header it likes, the type isn't known prior to execution.Well, by default, the answer is that MultiViews simply won't serve
.php
files. But chances are that you followed the advice of one of the many, many posts on the internet (I get 4 on the first page if I Google 'php apache multiviews', the top one clearly being the one the OP of this question followed, since he actually commented upon it) advocating getting around this using an AddType header, probably looking something like this:AddType application/x-httpd-php .php
Huh? Why does this magically cause Apache to be happy to serve
.php
files? Surely browsers aren't includingapplication/x-httpd-php
as one of the types they'll accept in theirAccept
headers?Well, not exactly. But all the major ones do include
*/*
(thus permitting a response of any MIME type - they're using theAccept
header only for expressing preference weighting, not for restricting the types they'll accept.) This causesmod_negotiation
to be willing to select and serve.php
files as long as some MIME type - any at all! - is associated with them.For example, if I just type a URL into the address bar in Chromium or Firefox, the
Accept
header the browser sends is, in the case of Chromium...Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
... and in the case of Firefox:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Both of these headers contain
*/*
as an acceptable content type, and thus permit the server to serve a file of any content type it likes. But some less popular browsers don't accept*/*
- or perhaps only include it for page requests, not when loading the content of a<script>
or<img>
tag that you might also be serving through PHP - and that's where our problem comes from.If you check the user agents of the requests that result in 406 errors, you'll likely see that they're from relatively unusual user agents. When I experienced this error, it was when I had the
src
of an<img>
element pointing to a PHP script that dynamically served images (with the.php
extension omitted from the URL), and I first witnessed it failing for BlackBerry users:Mozilla/5.0 (BlackBerry; U; BlackBerry 9320; fr) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.714 Mobile Safari/534.11+
To get around this, we need to let
mod_negotiate
serve PHP scripts via some means other than giving them an arbitrary type and then relying upon the browser to send anAccept: */*
header. To do this, we use theMultiviewsMatch
directive to specify that multiviews can serve PHP files regardless of whether they match the request'sAccept
header. The default option isNegotiatedOnly
:The
NegotiatedOnly
option provides that every extension following the base name must correlate to a recognizedmod_mime
extension for content negotiation, e.g. Charset, Content-Type, Language, or Encoding. This is the strictest implementation with the fewest unexpected side effects, and is the default behavior.But we can get what we want with the
Any
option:You may finally allow
Any
extensions to match, even ifmod_mime
doesn't recognize the extension.To restrict this rule change only to
.php
files, we use a<FilesMatch>
directive, like this:<Files "*.php"> MultiviewsMatch Any </Files>
And with that tiny (but difficult-to-figure-out) change, we're done!
这篇关于&QUOT;没有可接受的变体QUOT;从多视图中的Apache的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!