讨论nginx location 顺序问题


Posted in Servers onMay 30, 2022

网上有很多讨论 nginx location 顺序的话题,得到的结论也基本一致,总结为:

  • 精准匹配 =
  • 前缀匹配 ^~
  • 正则匹配 ~~*
  • 不带修饰符的前缀匹配

在很长的一段时间里,我对上述的结论也一直深信不疑,甚至还将这个结论分享给其他小伙伴,直到在有一次配置时发现,请求 uri 明明是符合了前缀匹配 ^~ 规则,但 nginx 却没有使用,这让我对上述结论产生了疑惑。后续通过调研、实践后发现,上述结论可以说对,但也不对,是不是更疑惑了?没关系,看完这篇文章你就知道我为什么会这样说了。

本篇文章会从以下五个方面来介绍 location 顺序问题

  • location 是什么
  • location 的选项有哪些
  • location 的匹配规则是什么
  • location 的应用规则是什么
  • 总结

话不多说,我们直接进入正题。

一、location 是什么

location 翻译成中文就是定位,已经描述的比较清晰了,因为它的作用就是根据请求 uri 定位到某一个规则块,然后在由该规则块决定怎么处理用户的请求。

location 模块看起来挺简单的,但真要自己写,有时候就会觉得无从下手,我应该用 location /imageslocation ^~ /images 还是 location ~ /images 呢?我应该帮这个location 规则放在什么位置呢?将规则放在最前面会影响已有的配置吗?....上面说的问题,相信大部分同学都遇到过,想要彻底解决,就必须要了解 location 的处理逻辑,当然这也是本篇文章的目的所在。

二、location 的选项有哪些?

location [ = | ~ | ~* | ^~ ] uri { ... }

按照匹配模式进行区分,location 后可以放置两种类型的匹配规则,分别是前缀字符串(prefix string)和正则表达式(regular expression)。其中前缀字符串包括 =^~ 以及不设置(也就是空串),而正则表达式只有 ~~*。(这五种选项是经常看到的,还有一种不常用的 location @name { ... } ,并不在今天的讨论范围)

三、location 的匹配规则

只有请求 uri 满足 location 的规则,才有可能被应用,而对于不同的匹配模式,location 的匹配规则也是不同的

前缀字符串

顾名思义,它表示从请求 uri 的开头开始进行匹配,如果用 JavaScript 来描述的话,当 uri.indexOf(locationRule.uri) === 0 时表示满足匹配规则,其中 uri 表示请求路径,而 locationRule.uri 表示 location 设置的规则。

其中 = 选项比较特殊,它又叫做精准匹配,只有匹配规则与请求 uri 完全相等时才表示满足条件,即只有当 uri === locationRule.uri 时才表示匹配成功。

正则表达式

其实就是通过正则匹配来验证是否满足规则,nginx 使用的是 Perl 兼容正则表达式 (PCRE),网上可以找到很多验证正则表达式的站点,这里就不再展开了。但要注意,为了方便配置,nginx 进行了一些非标准的优化,例如,不必像在标准正则表达式中那样转义 URL 中的正斜杠(/)等等。

而对于 ~~* 唯一的区别就是:~ 区分大小写,而 ~* 不区分大小写。

总结

下面,我们对 location 的选项进行一个简单的总结

选项 匹配规则 示例
= 精准匹配 location = /test {...}
^~ 从请求 uri 的开头进行匹配 location ^~ /test {...}
[空串] 从请求 uri 的开头进行匹配 location /test {...}
~ 区分大小写的正则匹配 location ~ /test {...}
~* 不区分大小写的正则匹配 location ~* /test {...}

四、location 的应用规则

理论篇

在了解完匹配规则后,我们来看下 nginx 是如何应用这些规则的,这就要说到 location 的顺序问题了。

下面是根据实践以文档总结出来的 location 应用规则的逻辑:

  • server 块中的 location 按照匹配模式分成两个列表,分别为前缀字符串规则列表和正则匹配规则列表。
  • 首先遍历前缀字符串规则列表,当满足匹配的规则是精准匹配时(即匹配选项是 =),直接应用该规则,结束流程;否则找到匹配规则最长的那条记录(记作 maxLenthStringPrefixRule),继续执行逻辑3;
  • 如果 maxLenthStringPrefixRule 存在且匹配选项是 ^~ ,应用该规则,结束流程;否则,执行逻辑4;
  • 遍历正则匹配规则列表,如果满足匹配规则,则直接应用,流程结束;如果直到循环结束后依然没有满足规则的 location,则执行逻辑5;
  • 如果 maxLenthStringPrefixRule 存在,则应用该规则,流程结束;如果没有则返回 404,同样结束流程。

如果上述描述看着很难理解,可以尝试看下面的伪代码。

// 字符串匹配的规则集合,按照在 .conf 文件中的顺序放置到该集合中
const stringPrefixRuleList = [...]
// 正则匹配的规则集合,按照在 .conf 文件中的顺序放置到该集合中
const regularExpressionRuleList = [...]
// 用于存放最长字符串匹配的规则
let maxLenthStringPrefixRule;
// 遍历字符串匹配的规则集合
for (let stringPrefixRule of stringPrefixRuleList) {
    // 符合匹配规则
    if (stringPrefixRule.isMatched()) {
        // 匹配选项是精准匹配
        if (stringPrefixRule.option === '=') {
            // 应用该规则,结束流程
            applyRule(stringPrefixRule);
            return;
        }
        // 将最长匹配规则记录下来,留到后面使用
        if (!maxLenthStringPrefixRule 
            || stringPrefixRule.uri.length > maxLenthStringPrefixRule.uri.length) {
            maxLenthStringPrefixRule = stringPrefixRule
        }
    }
}
// 如果最长匹配规则的选项是 ^~, 则应用该规则,流程结束
if (maxLenthStringPrefixRule && maxLenthStringPrefixRule.option === '^~') {
    applyRule(maxLenthStringPrefixRule);
    return;
}
// 遍历正则匹配规则集合
for (let regularExpressionRule of regularExpressionRuleList) {
    // 如果有规则匹配上,则直接应用,流程结束
    if (regularExpressionRule.isMatched()) {
        applyRule(regularExpressionRule);
        return;
    }
}
// 如果最长字符串匹配的规则存在,则应用该规则
if (maxLenthStringPrefixRule) {
    applyRule(maxLenthStringPrefixRule);
    return;
}
// 404,规则未找到
throw new Error(404)

由此,我们可以得到几个结论:

  • 命中 = 匹配规则后会终止搜索,并直接使用该规则。所以,如果某个请求 url 频繁发生,例如 /,我们可以在 nginx.conf 中添加 location = / 规则,这会加速这些请求的处理速度,因为在命中规则后会终止搜索。
  • ^~ 和空串的前缀匹配,区别在于,如果命中 ^~ 的规则,并且是最长前缀匹配,就会终止搜索正则匹配规则列表。
  • 除了命中精准匹配外,前缀字符串匹配列表都会被遍历一遍,并且找到最长匹配的那条 location 规则,所以前缀字符串匹配和在文件中的位置无关,但是和匹配长度有关
  • 由于正则匹配的时间、资源消耗较多,所以 nginx 在对 location 规则进行正则匹配时,命中一个就直接使用了,所以正则匹配和 location 规则在文件中的位置有关

实践篇

从上面的理论篇中,大家应该能大致了解到 nginx 是如何命中 location 规则的,为了加深大家记忆,同时也为了能佐证理论是对的,我们来一些实践吧。

我们的模板是这样的,后面所有的实践内容,都是放到两个 rule section 之间。

server {
    listen	9001;
    location / {
        default_type text/html;
        return 200 'hello world';
    }
    # ===== rule section ======
    # ===== rule section ======

精准匹配优先级最高

location /test {
    default_type text/html;
    return 200 '/test';
}

location ~ /test {
   default_type text/html;
   return 200 '~ /test';
}

location = /test {
   default_type text/html;
   return 200 '= /test';
}

我们在 nginx.conf 放置了 /test~ /test= /test 三个 location 规则,随后在浏览器输入 localhost:9001/test,发现输出内容是 = /test,符合我们的预期,= 选项的优先级最高。

正则匹配和顺序有关

location ~ /test {
   default_type text/html;
   return 200 '~ /test';
}

location ~ /test/*/demo {
   default_type text/html;
   return 200 '~ /test/*/demo';
}

随后我们将 rule section 区块替换成 ~ /test~ /test/*/demo 规则,在浏览器输入 localhost:9001/test/xyz/demo,从正则角度来说,/test/xyz/demo 既满足 /test规则,又满足 /test/*/demo 规则,但是由于 ~ /test 在文件中位置靠前,所以优先被命中,理论上应该会输出 ~ /test,校验后发现,确实是这样。

正则、^~前缀匹配、空前缀匹配混搭

location ^~ /test {
   default_type text/html;
   return 200 '^~ /test';
}
location /test/more/andmore {
   default_type text/html;
   return 200 '/test/more/andmore';
}
location ~ /test {
   default_type text/html;
   return 200 '~ /test';
}

将 section rule 区域替换成上述内容,然后在浏览器中输入 localhost:9001/test/more/andmore,猜猜会发生什么?

我们来一起分析下:

  • 没有精准匹配的规则
  • 找到最长的前缀字符串匹配规则是 /test/more/andmore,它不是 ^~ 选项,所以会遍历正则匹配规则
  • 发现 ~ /test 满足匹配规则,直接应用该规则,所以会输出 location ~ /test 规则块的内容,也就是 ~ /test

我们再来试一下,在浏览器输入 localhost:9001/test/more,会显示什么呢?

  • 没有精准匹配规则
  • 最长前缀字符串匹配规则是 ^~ /test,是 ^~ 选项,所以不用遍历正则匹配规则列表,所以页面会显示 ^~ /test

通过校验后发现,上述分析的结果和实际显示结果是一样的。

上面几个实践都比较简单,大家也可以尝试各种组合,然后按照上面的分析步骤来检验下自己是不是真的理解了 location

五、总结

看完上面的介绍,相信大家对 location 规则的处理逻辑都有一定的了解,也应该明白为什么在文章开头说曾经看到的结论对、也不对了。

如果要用对 location 顺序进行总结的话,可以在原有的基础上适当的进行一些扩展:

  • 精准匹配 =
  • 前缀匹配 ^~如果该前缀匹配是最长前缀匹配规则,则应用
  • 正则匹配 ~~*和该规则在文件中的顺序有关,执行顺序从上到下
  • 不带修饰符的前缀匹配,和匹配规则的长度有关,只会应用最长的匹配规则,与在文件中的顺序无关

参考链接:nginx.org/en/docs/htt…

到此这篇关于nginx location 顺序问题的文章就介绍到这了!


Tags in this post...

Servers 相关文章推荐
Nginx工作原理和优化总结。
Apr 02 Servers
windows下快速安装nginx并配置开机自启动的方法
May 11 Servers
nginx配置文件使用环境变量的操作方法
Jun 02 Servers
Nginx使用Lua模块实现WAF的原理解析
Sep 04 Servers
Nginx缓存设置案例详解
Sep 15 Servers
Windows Server 2012 修改远程默认端口3389的方法
Apr 28 Servers
使用Nginx的访问日志统计PV与UV
May 06 Servers
服务器SVN搭建图文安装过程
Jun 21 Servers
win10+RTX3050ti+TensorFlow+cudn+cudnn配置深度学习环境的方法
Jun 25 Servers
zabbix 代理服务器的部署与 zabbix-snmp 监控问题
Jul 15 Servers
Nginx如何获取自定义请求header头和URL参数详解
Jul 23 Servers
Shell中的单中括号和双中括号的用法详解
Dec 24 Servers
项目中Nginx多级代理是如何获取客户端的真实IP地址
May 30 #Servers
nginx rewrite功能使用场景分析
May 30 #Servers
Nginx静态压缩和代码压缩提高访问速度详解
May 30 #Servers
Nginx 配置 HTTPS的详细过程
May 30 #Servers
关于windows server 2012 DC 环境 重启后蓝屏代码:0xc00002e2的问题
May 25 #Servers
聊聊配置 Nginx 访问与错误日志的问题
May 25 #Servers
利用nginx搭建RTMP视频点播、直播、HLS服务器
You might like
php数组删除元素示例
2014/03/21 PHP
如何在旧的PHP系统中使用PHP 5.3之后的库
2015/12/02 PHP
三个思路解决laravel上传文件报错:413 Request Entity Too Large问题
2017/11/13 PHP
php正确输出json数据的实例讲解
2018/08/21 PHP
通过MSXML2自动获取QQ个人头像及在线情况(给初学者)
2007/01/22 Javascript
JavaScript 获取事件对象的注意点
2009/07/29 Javascript
Javascript在IE下设置innerHTML时出现未知的运行时错误的解决方法
2011/01/12 Javascript
JQuery切换显示的效果实例代码
2013/02/27 Javascript
JavaScript实现简单图片翻转的方法
2015/04/17 Javascript
jquery实现兼容IE8的异步上传文件
2015/06/15 Javascript
详解Javascript中的原型OOP
2016/10/12 Javascript
JS简单实现移动端日历功能示例
2016/12/28 Javascript
使用JavaScript为一张图片设置备选路径的方法
2017/01/04 Javascript
jquery插件ContextMenu设置右键菜单
2017/03/13 Javascript
vue-cli 为项目设置别名的方法
2019/10/15 Javascript
纯js实现无缝滚动功能代码实例
2020/02/21 Javascript
Vue项目中使用flow做类型检测的方法
2020/03/18 Javascript
python检测服务器是否正常
2014/02/16 Python
Python中用pycurl监控http响应时间脚本分享
2015/02/02 Python
pandas系列之DataFrame 行列数据筛选实例
2018/04/12 Python
python 运用Django 开发后台接口的实例
2018/12/11 Python
python调用虹软2.0第三版的具体使用
2019/02/22 Python
Python基于机器学习方法实现的电影推荐系统实例详解
2019/06/25 Python
pandas DataFrame 警告(SettingWithCopyWarning)的解决
2019/07/23 Python
python实现的发邮件功能示例
2019/09/11 Python
python return逻辑判断表达式实现解析
2019/12/02 Python
python中的时区问题
2021/01/14 Python
中国高端鲜花第一品牌:roseonly(一生只送一人)
2017/02/12 全球购物
大学生村官工作感言
2014/01/10 职场文书
优秀学生干部个人事迹材料
2014/06/02 职场文书
我的中国梦演讲稿800字
2014/08/19 职场文书
学习优秀党务工作者先进事迹材料思想报告
2014/09/17 职场文书
教师先进事迹材料
2014/12/16 职场文书
提升Nginx性能的一些建议
2021/03/31 Servers
Java 语言中Object 类和System 类详解
2021/07/07 Java/Android
vue配置型表格基于el-table拓展之table-plus组件
2022/04/12 Vue.js