php - Apache中MultiViews中的“no acceptable variant”

标签 php apache multiviews

在基于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

我的问题是:偶尔触发此错误的原因是什么,如何解决该问题?

最佳答案

简短答案

同时满足以下所有条件时,可能会发生此错误:

  • 您的网络服务器已启用Multiviews
  • 您可以通过使用AddType伪指令为任意文件分配任意类型来允许Multiviews提供PHP文件,最有可能的是这样的一行:

    AddType application/x-httpd-php .php
    
  • 您的客户端浏览器向请求发送不包含Accept作为可接受的MIME类型的 */* header (这是非常不寻常的,这就是为什么您很少看到该错误的原因)。
  • 您已将 MultiviewsMatch 指令设置为其默认值NegotiatedOnly

  • 您可以通过在Apache配置中添加以下内容来解决该错误:

    <Files "*.php">
        MultiviewsMatch Any
    </Files>
    

    解释

    要了解这里发生的情况,至少需要对Apache的 mod_negotiation 和HTTP的AcceptAccept-Foo header 的工作至少有一个简要的概述。在遇到OP描述的错误之前,我对这两个都不了解。我不是通过故意选择启用mod_negotiation的,而是因为apt-get就是为我设置Apache的方式,并且我启用MultiViews时对它的含义没有太多的了解,除了可以让.php留在URL末尾之外。您的情况可能相似或相同。

    因此,这里有一些我不知道的重要基础知识:

    诸如AcceptAccept-Language之类的
  • 请求 header 可让客户端指定可以接收其响应的MIME类型或语言,并为可接受的类型或语言指定加权首选项。 (自然,仅当服务器具有或能够基于这些 header 生成不同的响应时,这些才有用。)例如,每次加载页面时,Chromium都会为我发送以下 header :

    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的mod_negotiation允许您将多个文件(例如myresource.html.enmyresource.html.frmyresource.pdf.enmyresource.pdf.fr)存储在同一文件夹中,然后自动使用请求的Accept-* header 来确定客户端向myresource发送请求时要提供的服务。有两种方法可以做到这一点。第一种是在同一文件夹中创建Type Map文件,该文件显式声明每个可用文档的MIME类型和语言。另一个是多 View 。
  • 启用多 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 named foo.*, 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 recognized mod_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 if mod_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/

    相关文章:

    php - 如何使用按钮选择列表的最新或下一个元素

    php - 当元素是表单的一部分时,document.getElementById 返回 null

    apache - Solr TermVector 结果未返回

    matlab - 根据两个垂直 View 的图像生成图像

    apache - Tomcat 上的内容协商

    PHP 自动完成功能不起作用

    php - 在 php 中, "echo ` 命令` "做什么?(带单引号)

    apache - 在带有自定义域的 OpenShift 中从 HTTPS 重定向到 HTTP

    java - windows 7中如何启动apache-tomcat-5.0.28运行java中的servlet程序

    c# - 多个 View 共享相同的数据与多个线程之间的双向数据绑定(bind)