使用 NGINX 进行条件重写或 try_files?

conditional rewrite or try_files with NGINX?

我在设置条件重写时遇到问题,我一直在尝试将 if 指令(尽管所有来源都表明它是 "evil")与 -f 一起使用切换以检查文件是否存在,但它不起作用。我相信 issue/case 最好用例子来解释,所以这里是:

目录结构

workspace/
  myapp/
    webroot/
      index.php
      assets/
        baz.js
        hello/
          foo.js
    modules/
      hello/
        assets/
          foo.js
          bar.js

预期结果

/                     =>  /workspace/myapp/webroot/index.php
/assets/hello/foo.js  =>  /workspace/myapp/webroot/assets/hello/foo.js
/assets/hello/bar.js  =>  /workspace/myapp/modules/hello/assets/foo.js
/assets/baz.js        =>  /workspace/myapp/webroot/assets/baz.js

总结:

现在不能正常工作的部分是:

location /assets/ {
    if (-f $uri) {
        break;
    }
    root     /workspace/myapp/modules;
    rewrite  ^/assets/([^/]+)/(.*)$ //assets/ break;
}

if 指令,似乎没有任何影响 - bar.js 文件是从 modules 而不是 webroot.

我是否应该使用 if

有什么办法可以用 try_files 来解决这个问题吗?我似乎无法理解这将如何与我似乎无法绕过的 rewrite 一起工作。

请不要建议使用部署脚本或其他方式重组资产 - 由于各种其他原因,这不是一个选项。

我以前在 Apache 上使用过这种模式,而且 NGINX 在大多数方面似乎都更强大,所以我确定这一定是可能的?

一个不绝对的要求是,我 没有 能够用 webroot/assets/hello/foo.js 覆盖 modules/hello/assets/foo.js - 从 webroot/assets/* 然而,这是一项要求。

答案分为两部分:第一部分解释了为什么您的配置不起作用,第二部分提供了如何解决您的问题的示例。如果您只对解决方案感兴趣,请直接进入第二部分。

问题

首先,请注意 root 指令在 location 块中的位置并不重要。不管放在location的最上面还是最下面,反正都会影响到整个location。另外,请记住 rewrite 行末尾的 break 告诉 Nginx 留在当前位置,即使 URI 已成功重写。

话虽如此,让我们看一下您的配置,看看来自 Expected results 的每个请求是如何处理的,以及为什么没有按预期工作。

我们假设在您的配置中没有其他合适的 location 具有更高的优先级。由于来自 Expected results 的每个请求都以 /assets 开头,所有请求都将根据您的 location 中提供的规则进行处理。所以:

  1. /assets/hello/foo.js

root 设置为 /workspace/myapp/modulesif 指令将被评估为 false,因为 /assets/hello/foo.js 不存在,因此 break 将不会被执行。最后,最后的 rewrite 会将请求的 URI 从 /assets/hello/foo.js 更改为 /hello/assets/foo.js,接下来的 break 将告诉 Nginx 留在当前的 location 内。因此 /workspace/myapp/modules/hello/assets/foo.js 将被送达。

  1. /assets/hello/bar.js

此请求的处理方式与上一个请求的处理方式完全相同,因此 /workspace/myapp/modules/hello/assets/bar.js 将得到处理。

  1. /assets/baz.js

再次将 root 设置为 /workspace/myapp/modules 并且 if 被评估为 false。但是这次最后的rewrite不会改变URI,因为请求不匹配正则表达式。结果 Nginx 将尝试服务 /workspace/myapp/modules/assets/baz.js 并且由于不存在这样的文件,将 return 404.

如您所见,由于以下几个原因,您的配置可能无法如您所愿地工作:

  • if 总是评估为 false,因为您尝试检查 URI 而不是文件;
  • 请求停留在该位置内,因为您在 rewrite 行中用 break 告诉它停留在那里;
  • root 在此位置始终设置为 /workspace/myapp/modules,因此无法从其他任何位置提供文件。

解决办法

  1. 最简单的解决方案是使用 try_files:

    root /workspace/myapp/webroot;
    
    location /assets/ {
        try_files $uri @modules;
    }
    
    location @modules {
        root     /workspace/myapp/modules;
        rewrite  ^/assets/([^/]+)/(.*)$ //assets/ break;
    }
    

    此配置告诉 Nginx 首先在 webroot 文件夹中查找文件,如果没有找到则转到另一个位置的 modules 文件夹。这种方法被认为是最可取的。

  2. 另一方面,使用 if 可以让您在一个位置解决问题:

    location /assets/ {
        root /workspace/myapp; # The parent folder
    
        if (-f $document_root/webroot/$uri) {
            rewrite ^(.*)$ /webroot/ break;
        }
    
        rewrite  ^/assets/([^/]+)/(.*)$ /modules//assets/ break;
    }
    

    但是,这种方法被认为是过时的,不推荐使用。