cri*_*hoj 10 php apache multiviews
在基于PHP的应用程序的一个部署中,Apache的MultiViews
选项用于隐藏请求调度程序脚本的.php扩展名.例如请求
/page/about
Run Code Online (Sandbox Code Playgroud)
......将由...处理
/page.php
Run Code Online (Sandbox Code Playgroud)
...有可用的请求URI的尾部PATH_INFO
.
大多数时候这种方法很好,但偶尔会导致错误
[error] [client 86.x.x.x] no acceptable variant: /path/to/document/root/page
Run Code Online (Sandbox Code Playgroud)
我的问题是:偶尔触发此错误的原因是什么,以及如何解决问题?
Mar*_*ery 10
当以下所有内容同时为真时,可能会发生此错误:
您允许Multiviews通过为AddType
指令指定任意类型来提供PHP文件,最有可能使用如下行:
AddType application/x-httpd-php .php
Run Code Online (Sandbox Code Playgroud)Accept
不包含*/*
在可接受的MIME类型中的标头(这是非常不寻常的,这就是您很少看到错误的原因).MultiviewsMatch
指令设置为其默认值NegotiatedOnly
.您可以通过将以下咒语添加到Apache配置来解决该错误:
<Files "*.php">
MultiviewsMatch Any
</Files>
Run Code Online (Sandbox Code Playgroud)
了解这里发生的事情需要至少对Apache mod_negotiation
和HTTP Accept
以及Accept-Foo
标题的工作方式进行表面的概述.在遇到OP描述的错误之前,我对这两者中的任何一个都一无所知; 我mod_negotiation
没有通过深思熟虑的选择启用,但因为这就是apt-get
为我设置Apache的方式,而且MultiViews
除了它之外我没有太多理解它的含义,它会让我不再.php
使用我的URL.您的情况可能相似或相同.
所以这里有一些我不知道的重要基础知识:
请求标题Accept
,Accept-Language
让客户端指定接收响应的MIME类型或语言,以及为可接受的类型或语言指定加权首选项.(当然,这些仅在服务器具有或能够根据这些标头生成不同响应时才有用.)例如,每当我加载页面时,Chromium都会为我发送以下标题:
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
Run Code Online (Sandbox Code Playgroud)Apache的mod_negotiation
,您可以存储多个文件一样myresource.html.en
,myresource.html.fr
,myresource.pdf.en
并myresource.pdf.fr
在同一个文件夹中,然后自动使用请求的Accept-*
标题来决定当客户端发送请求到服务myresource
.有两种方法可以做到这一点.第一种是在同一文件夹中创建一个Type Map文件,该文件显式声明每个可用文档的MIME类型和语言.另一个是Multiviews.
启用多视图时...
在Multiviews
...如果服务器收到请求
/some/dir/foo
和/some/dir/foo
不存在,则服务器会寻找一个名为所有文件的目录foo.*
,并有效假货一个类型的地图,所有的名字这些文件,赋予它们不同的介质类型和内容编码如果客户要求其中一个名字,它会有.然后,它会根据客户的要求选择最佳匹配,并返回该文档.
这里要注意的重要一点是,Accept
即使启用了Multiview,Apache仍然会尊重标题; 与类型映射方法的唯一区别是Apache正在从文件扩展名中推断文件的MIME类型,而不是通过在类型映射中明确声明它.
在没有可接受的变异引发错误(和406响应发送)由阿帕奇当存在文件已收到的网址,但它不能成为任何人,因为他们的MIME类型不匹配,任何在规定的可能性请求的Accept
标题.(例如,如果没有可接受语言的变体,则会发生同样的情况.)这符合HTTP规范,其中规定:
如果存在Accept头字段,并且如果服务器无法根据组合的Accept字段值发送可接受的响应,则服务器应该发送406(不可接受)响应.
您可以轻松地测试此行为.只需test.html
在启用了Multiviews的Apache服务器的webroot中创建一个名为包含字符串"Hello World" 的文件,然后尝试使用允许HTML响应的Accept标头与不响应的标头请求它.我在我的本地(Ubuntu)机器上演示了这个curl
:
$ 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>
Run Code Online (Sandbox Code Playgroud)
这给我们带来了一个我们尚未解决的问题:mod_negotiate
在决定是否可以为PHP文件提供服务时,如何确定PHP文件的MIME类型?由于文件将被执行,并且可能会吐出Content-Type
它喜欢的任何标头,因此在执行之前不知道该类型.
嗯,默认情况下,答案是MultiViews根本不会提供.php
文件.但是很可能你是按照互联网上很多很多帖子之一的建议(如果我谷歌的'php apache multiviews',我在第一页上得到4 ,最明显的是这个问题的OP跟随的那个,因为他实际上评论过它)主张使用AddType标头绕过它,可能看起来像这样:
AddType application/x-httpd-php .php
Run Code Online (Sandbox Code Playgroud)
咦?为什么这会让Apache很乐意为.php
文件提供服务呢?当然浏览器不包括application/x-httpd-php
他们在Accept
标题中接受的类型之一?
好吧,不完全是.但所有主要的都包括*/*
(因此允许任何MIME类型的响应 - 他们Accept
只使用标题表示偏好权重,而不是限制他们将接受的类型.)这导致mod_negotiation
愿意选择和提供.php
文件只要一些MIME类型 - 任何一个! - 与他们相关联.
例如,如果我只是在Chromium或Firefox的地址栏中键入一个URL,Accept
那么浏览器发送的标题就是Chromium ...
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Run Code Online (Sandbox Code Playgroud)
......以及Firefox的情况:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Run Code Online (Sandbox Code Playgroud)
这两个标头都包含*/*
可接受的内容类型,因此允许服务器提供其喜欢的任何内容类型的文件.但是一些不太受欢迎的浏览器不接受*/*
- 或者可能只包含它用于页面请求,而不是在加载您可能也通过PHP提供的标记<script>
或<img>
标记的内容时- 这就是我们的问题所在.
如果检查导致406错误的请求的用户代理,您可能会发现它们来自相对不寻常的用户代理.当我遇到这个错误时,就是当我有src
一个<img>
指向动态提供图像的PHP脚本的元素时(.php
URL中省略了扩展名),我首先目睹了BlackBerry用户的失败:
Mozilla/5.0 (BlackBerry; U; BlackBerry 9320; fr) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.714 Mobile Safari/534.11+
Run Code Online (Sandbox Code Playgroud)
为了解决这个问题,我们需要mod_negotiate
通过一些方法来提供PHP脚本,而不是给它们任意类型,然后依靠浏览器发送Accept: */*
标题.为此,我们使用该MultiviewsMatch
指令指定多视图可以为PHP文件提供服务,无论它们是否与请求的Accept
标头匹配.默认选项是NegotiatedOnly
:
该
NegotiatedOnly
选项规定基本名称后面的每个扩展名必须与mod_mime
内容协商的已识别扩展名相关联,例如Charset,Content-Type,Language或Encoding.这是最严格的实现,具有最少的意外副作用,并且是默认行为.
但是我们可以通过Any
选项得到我们想要的东西:
Any
即使mod_mime
无法识别扩展名,您也可以最终允许扩展名匹配.
要将此规则限制为仅更改为.php
文件,我们使用<Files>
指令,如下所示:
<Files "*.php">
MultiviewsMatch Any
</Files>
Run Code Online (Sandbox Code Playgroud)
随着这一微小(但难以弄清楚)的变化,我们已经完成了!
小智 5
马克·阿默里给出的答案几乎是完整的,但是它缺少最佳点,并且没有解决“请求中没有给出延期,因此与替代方案的谈判失败”的问题。
您可以通过添加以下配置片段来解决此错误:
你的 PHP 配置应该是这样的:
<FilesMatch "\.ph(p3?|tml)$">
SetHandler application/x-httpd-php
</FilesMatch>
Run Code Online (Sandbox Code Playgroud)
不要使用AddType application/x-httpd-php .php
或任何其他 AddType
你的附加配置应该是这样的:
RemoveType .php
<Files "*.php">
MultiviewsMatch Any
</Files>
Run Code Online (Sandbox Code Playgroud)
如果您确实使用 AddType,您将收到如下错误:
GET /index/123/434 HTTP/1.1
Host: test.net
Accept: image/*
HTTP/1.1 406 Not Acceptable
Date: Tue, 15 Jul 2014 13:08:27 GMT
Server: Apache
Alternates: {"index.php" 1 {type application/x-httpd-php}}
Vary: Accept-Encoding
Content-Length: 427
Connection: close
Content-Type: text/html; charset=iso-8859-1
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,它确实找到了index.php,但是它没有使用此替代方案,因为它无法匹配Accept: image/*
to application/x-httpd-php
。如果你要求/index.php/1/2/3/4
它工作正常。
我在mod_negotiation模块的源代码中找到了这个原因。我试图找出为什么 Apache 在 .php 类型是“cgi”的情况下可以工作,但在其他情况下就不行(提示:application/x-httpd-cgi
是硬编码的..)。在源代码中,我注意到,如果该文件的 Content-Type 与 Accept 标头匹配,或者该文件的 Content-Type 为空,则 apache 只会将该文件视为匹配项。
如果您使用 SetHandler,那么 apache 不会将 .php 文件视为application/x-httpd-php
,但不幸的是,许多发行版也在 /etc/mime.types 文件中定义了这一点。因此,可以肯定的是,RemoveType .php
如果此错误困扰您,只需将其添加到您的配置中即可。