简答
当以下所有条件同时为真时,可能会发生此错误:
您的网络服务器启用了多视图
AddType
您允许 Multiviews 通过使用指令为 PHP 文件分配任意类型来服务 PHP 文件,很可能是这样的一行:
AddType application/x-httpd-php .php
您的客户端浏览器随请求发送一个Accept
不包含*/*
作为可接受 MIME 类型的标头(这是非常不寻常的,这就是您很少看到错误的原因)。
您将MultiviewsMatch
指令设置为其默认值NegotiatedOnly
.
您可以通过将以下咒语添加到您的 Apache 配置来解决该错误:
<Files "*.php">
MultiviewsMatch Any
</Files>
解释
了解这里发生的事情至少需要对 Apachemod_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
Apachemod_negotiation
允许您将多个文件(如myresource.html.en
、myresource.html.fr
和)存储在同一个文件夹中,然后自动使用请求的myresource.pdf.en
标头来决定当客户端向. 有两种方法可以做到这一点。首先是在同一文件夹中创建一个类型映射文件,该文件明确声明每个可用文档的 MIME 类型和语言。另一个是多视图。myresource.pdf.fr
Accept-*
myresource
启用多视图时...
多视图
...如果服务器收到请求/some/dir/foo
但/some/dir/foo
不存在,则服务器读取目录以查找所有名为 的文件foo.*
,并有效地伪造一个类型映射来命名所有这些文件,为它们分配相同的媒体类型和内容编码如果客户按名字要求其中一个,它就会有。然后它选择最符合客户要求的匹配项,并返回该文档。
这里要注意的重要一点是,Accept
即使启用了 Multiviews,Apache 仍然尊重标头;与类型映射方法的唯一区别是 Apache 从文件扩展名推断文件的 MIME 类型,而不是通过您在类型映射中显式声明它。
当它收到的 URL 存在文件时,Apache 会抛出不可接受的变体错误(并发送 406 响应),但不允许提供任何文件,因为它们的 MIME 类型与提供的任何可能性都不匹配请求的Accept
标头。(例如,如果在可接受的语言中没有变体,也会发生同样的事情。)这符合 HTTP 规范,该规范指出:
如果存在 Accept 头字段,并且如果服务器无法发送根据组合 Accept 字段值可接受的响应,则服务器应该发送 406(不可接受)响应。
您可以很容易地测试此行为。只需在启用了 Multiviews 的 Apache 服务器的 webroot 中创建一个test.html
包含字符串“Hello World”的文件,然后尝试使用允许 HTML 响应与不允许 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>
这给我们带来了一个我们尚未解决的问题:mod_negotiate
在决定是否可以提供 PHP 文件时,如何确定它的 MIME 类型?由于该文件将被执行,并且可以吐出Content-Type
它喜欢的任何标题,因此在执行之前该类型是未知的。
好吧,默认情况下,答案是 MultiViews 根本不会提供.php
文件。但是很可能您遵循了互联网上众多帖子中的一个的建议(如果我 Google 'php apache multiviews',我在第一页上得到 4 个,最重要的显然是这个问题的 OP 紧随其后,因为他实际上对此发表了评论)主张使用 AddType 标头来解决这个问题,可能看起来像这样:
AddType application/x-httpd-php .php
嗯?为什么这会神奇地使 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
...对于 Firefox:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
这两个标头都包含*/*
作为可接受的内容类型,因此允许服务器提供它喜欢的任何内容类型的文件。但是一些不太流行的浏览器不接受*/*
- 或者可能只在页面请求中包含它,而不是在加载您也可能通过 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+
为了解决这个问题,我们需要让mod_negotiate
PHP 脚本通过某种方式提供服务,而不是给它们一个任意类型,然后依靠浏览器发送一个Accept: */*
标头。为此,我们使用该MultiviewsMatch
指令指定多视图可以服务 PHP 文件,无论它们是否匹配请求的Accept
标头。默认选项是NegotiatedOnly
:
该NegotiatedOnly
选项规定,基本名称后面的每个扩展都必须与mod_mime
内容协商的可识别扩展相关,例如字符集、内容类型、语言或编码。这是最严格的实现,具有最少的意外副作用,并且是默认行为。
但是我们可以通过以下选项得到我们想要的Any
:
您最终可能会允许Any
扩展名匹配,即使mod_mime
不识别扩展名。
为了将此规则更改仅限于.php
文件,我们使用<Files>
指令,如下所示:
<Files "*.php">
MultiviewsMatch Any
</Files>
有了这个微小(但难以弄清楚)的变化,我们就完成了!