有这个简单的 nginx 配置:
location / {
return 200 "Location 1\n";
}
location ~ \.php$ {
return 200 "Location 2\n";
}
location /tmp {
return 200 "Location 3\n";
location ~ \.php$ {
return 200 "Location 3a\n";
}
}
为什么/tmp/foo.php
请求会给出Location 3a
响应,尽管根据文档,除非前缀位置有修饰符,否则正则表达式位置Location 2
应该超过请求?Location 3
^~
发生这种情况的原因是 nginx 中实际选择位置的算法与文档中描述的不同。或者更具体地说,官方文档没有解释嵌套位置的位置选择过程,而这要复杂得多。到目前为止,我还没有遇到任何英文文章解释它是如何真正工作的,所以我在这里尝试澄清一下。
让我们从原始问题中提供的配置开始:
人们可以认为请求
/tmp/foo.php
将继续进行location ~ \.php$ { ... }
(标记为“位置 2”),因为文档明确指出:我们来检查一下:
出乎意料,不是吗?文档没有说明的是,在确定最长匹配前缀位置后,nginx 将开始对其嵌套位置(如果有)进行新的搜索迭代。此过程以递归方式继续:在每个步骤中,都应用相同的匹配规则,并且如果找到匹配的嵌套正则表达式位置,而没有匹配的嵌套最长前缀位置,则 nginx 停止进一步搜索并使用该位置来处理请求。
在我们的示例中,对于
/tmp/foo.php
请求:location /tmp { ... }
(标记为“位置 3”)。location ~ \.php$ { ... }
(标记为“位置 3a”)匹配并用于处理请求。现在,让我们通过添加第四个位置来扩展配置:
测试相同请求:
这里发生了什么?
location /tmp/foo { ... }
。location ~ \.php$ { ... }
匹配并用于处理请求。其他请求
/tmp/bar.php
继续按以前的方式处理:接下来,让我们再次修改配置,将
location /tmp/foo { ... }
里面的移动到location /tmp { ... }
:运行一些测试:
到目前为止还没有什么意外。
现在,让我们检查一下
^~
location 指令修饰符。根据文档:让我们使用以下配置来检查一下:
运行一些测试:
似乎一切都按预期进行。
^~
修饰符 onlocation ^~ /tmp/foo { ... }
确保同一级别的正则表达式位置(例如location ~ \.php$ { ... }
)不会超过 之类的匹配请求/tmp/foo.php
。现在,有件事可能会让你大吃一惊。让我们修改一下配置,再次移动
location ^~ /tmp/foo { ... }
内部location /tmp { ... }
:让我们测试相同的
/tmp/foo.php
请求。您期望响应是Location 3b
,对吗?哎呀……看起来很奇怪,不是吗?好吧,这可能是需要解释的算法的最后一个方面。在确定最嵌套级别(在我们的例子中是,
location ^~ /tmp/foo { ... }
标记为“位置 3b”)的最长匹配前缀位置后,如果在其中找不到匹配的正则表达式位置,则 nginx 会记住该位置并开始沿配置树向上追溯到最外层。在上升过程中经过的每个级别,都会采取以下操作:^~
,nginx 会将请求与该级别定义的所有正则表达式位置进行匹配。第一个匹配的正则表达式位置(如果有)用于处理请求;否则,nginx 继续升序。^~
,nginx 将跳过与该层级上定义的正则表达式位置匹配请求,并立即上升到下一层级。如果已经到达最外层,则使用记住的最嵌套层级中的最长匹配前缀位置来处理请求。就是这样。
^~
如果最外层最长匹配前缀位置(位置 3)没有修饰符,则在嵌套前缀位置(位置 3b)上使用修饰符并不能保证请求不会被外部正则表达式位置(位置 2)超越^~
。PS 当然,需要指出的是,如果
=
在下降过程中的任何级别找到完全匹配的位置(带有修饰符的位置),nginx 将立即停止搜索,并使用找到的位置来处理请求。考虑到这一点,典型的 nginx 配置如下index.php
如果文件是请求的唯一处理程序,则可以进行显著优化,这在许多用例中很常见: