在基于PHP的应用程序的一种部署中,Apache的MultiViews
选项用于隐藏请求调度程序脚本的.php扩展名。例如。要求
/page/about
...将由
/page.php
...在URIt_code中提供请求URI的结尾部分。
在大多数情况下,这可以正常工作,但偶尔会导致诸如
[error] [client 86.x.x.x] no acceptable variant: /path/to/document/root/page
我的问题是:偶尔触发此错误的原因是什么,如何解决该问题?
最佳答案
简短答案
同时满足以下所有条件时,可能会发生此错误:
AddType
伪指令为任意文件分配任意类型来允许Multiviews提供PHP文件,最有可能的是这样的一行:AddType application/x-httpd-php .php
Accept
作为可接受的MIME类型的 */*
header (这是非常不寻常的,这就是为什么您很少看到该错误的原因)。 MultiviewsMatch
指令设置为其默认值NegotiatedOnly
。 您可以通过在Apache配置中添加以下内容来解决该错误:
<Files "*.php">
MultiviewsMatch Any
</Files>
解释
要了解这里发生的情况,至少需要对Apache的
mod_negotiation
和HTTP的Accept
和Accept-Foo
header 的工作至少有一个简要的概述。在遇到OP描述的错误之前,我对这两个都不了解。我不是通过故意选择启用mod_negotiation
的,而是因为apt-get
就是为我设置Apache的方式,并且我启用MultiViews
时对它的含义没有太多的了解,除了可以让.php
留在URL末尾之外。您的情况可能相似或相同。因此,这里有一些我不知道的重要基础知识:
诸如
Accept
和Accept-Language
之类的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
mod_negotiation
允许您将多个文件(例如myresource.html.en
,myresource.html.fr
,myresource.pdf.en
和myresource.pdf.fr
)存储在同一文件夹中,然后自动使用请求的Accept-*
header 来确定客户端向myresource
发送请求时要提供的服务。有两种方法可以做到这一点。第一种是在同一文件夹中创建Type Map文件,该文件显式声明每个可用文档的MIME类型和语言。另一个是多 View 。 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.
这里要注意的重要一点是,即使启用了Multiviews,Apache仍然会遵守
Accept
header 。与类型映射方法的唯一区别是Apache从文件扩展名推断文件的MIME类型,而不是通过您在类型映射中显式声明它。当存在已接收到URL的文件时,Apache会抛出(没有接受的)变体错误(并发送406响应),但是由于它们的MIME类型与MIME中提供的任何可能性都不匹配,因此不允许为其中任何一个提供服务请求的
Accept
header 。 (例如,如果在可接受的语言中没有任何变体,可能会发生同样的事情。)这符合HTTP规范,该规范指出: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.
您可以很容易地测试此行为。只需在启用了Multiviews的Apache服务器的webroot中创建一个名为
test.html
的文件,该文件包含字符串“Hello World”,然后尝试使用允许HTML响应而不允许HTML响应的Accept header 来请求它。我在这里用curl
在我的本地(Ubuntu)计算机上对此进行了演示:$ 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>
这给我们带来了一个尚 Unresolved 问题:
mod_negotiate
在确定它是否可以提供服务时如何确定PHP文件的MIME类型?由于该文件将要执行,并且可能会吐出它喜欢的任何Content-Type
header ,因此在执行之前不知道类型。好吧,默认情况下,答案是MultiViews根本不提供
.php
文件。但是您很有可能会遵循互联网上众多帖子中的一个的建议(如果我使用Google 'php apache multiviews',则我在首页上会显示4个帖子,而top one显然是此问题的OP所遵循的,因为他实际上在评论它)主张使用AddType header 解决此问题,可能看起来像这样:AddType application/x-httpd-php .php
??为什么这会神奇地使Apache乐于提供
.php
文件?当然,浏览器是否不将application/x-httpd-php
作为它们将在其Accept
header 中接受的类型之一?好吧,不完全是。但是所有主要的方法都包括
*/*
(因此允许任何MIME类型的响应-他们仅使用Accept
header 来表达偏好权重,而不是限制它们接受的类型。)这导致mod_negotiation
愿意选择并提供MIMEt类型的.php
文件-完全可以! -与他们相关联。例如,如果我只是在Chromium或Firefox的地址栏中输入一个URL,那么对于Chromium,浏览器发送的
Accept
header 就是...Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
...并且就Firefox而言:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
这两个 header 都包含
*/*
作为可接受的内容类型,因此允许服务器提供其喜欢的任何内容类型的文件。但是,一些不太流行的浏览器不接受*/*
-或仅将其包含在页面请求中,而不是在加载可能也通过PHP服务的<script>
或<img>
标记的内容时-这就是我们的问题所在。如果检查导致406错误的请求的用户代理,您可能会发现它们来自相对不寻常的用户代理。当我遇到此错误时,就是当我将
src
元素的<img>
指向动态提供图像的PHP脚本(URL中省略了.php
扩展名)时,我首先见证了它对于BlackBerry用户的失败:Mozilla/5.0 (BlackBerry; U; BlackBerry 9320; fr) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.714 Mobile Safari/534.11+
为了解决这个问题,我们需要让
mod_negotiate
通过某种方式为PHP脚本提供服务,而不是为它们提供任意类型,然后依靠浏览器发送Accept: */*
header 。为此,我们使用MultiviewsMatch
指令指定多 View 可以为PHP文件提供服务,无论它们是否与请求的Accept
header 匹配。默认选项是NegotiatedOnly
: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.
但是我们可以通过
Any
选项得到我们想要的:You may finally allow
Any
extensions to match, even ifmod_mime
doesn't recognize the extension.
为了仅将此规则更改限制为
.php
文件,我们使用 <Files>
指令,如下所示:<Files "*.php">
MultiviewsMatch Any
</Files>
有了这个微小的(但难以想象的)变化,我们就完成了!
关于php - Apache中MultiViews中的“no acceptable variant”,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16357933/