幸福日记

Nginx loaction优先级探究

实验声明:
return指令
该指令可以直接返回一个状态码及文本信息,由于是实验环境,没有必要建立真实的目录来对比location的命中,也免去了查看日志等繁琐的步骤。
因此,此实验均使用return指令来返回结果,用到的指令格式为return code text

实验环境
本文中使用的server_name采用的域名test.local.lyz810.com为真实域名,绑定IP为127.0.0.1,所以读者可以完全复制到本机实验。另外,*.local.lyz810.com域名做了泛解析,全部解析到127.0.0.1,读者可以任意选用该域名的子域名进行各种实验。

location类型

  1. 精确匹配:=
  2. 正则大小写敏感:~
  3. 正则大小写不敏感:~*
  4. 前缀匹配(高优先级):^~
  5. 前缀匹配:/

location优先级
以下是官网中给出的优先级:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

翻译:
Nginx首先检查使用前缀定义的location,会选择并记住一个最长匹配的location作为候选。
检查正则表达式,根据出现在配置文件中的顺序。
遇到第一个匹配的正则表达式就停止搜索。
如果没有匹配的正则表达式,就使用最开始候选的location。

由此可以看出,正则表达式优先级最高,多个正则表达式按从上到下就先匹配到的为准,其次是前缀location。
注意,上面是普通前缀与正则之间的比较,所有location中精确匹配优先级最高。

实验一
目的:验证精确匹配优先级最高
原理:让所有类型的location出现在一个server中,并将精确匹配放到最后,消除顺序可能带来的影响,观察是否命中精确匹配location
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}$" {
    return 200 regular;
  }

  location =/test123 {
    return 200 exact;
  }
}

结果:访问/test123,返回exact.说明精确匹配优先级最高

实验二
目的:验证正则的优先级高于前缀
原理:访问一个同时满足前缀和正则的页面,观察返回结果。为了排除顺序的影响,提供两个不同顺序。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ~ "/welcome\d{3}$" {
    return 200 regular;
  }

  location /welcome123 {
    return 200 prefix;
  }
}

结果:访问/test123和/welcome123都返回regular,说明正则优先级高于前缀,并且与正则和前缀出现的顺序无关。

实验三
目的:验证前缀匹配只与匹配长度有关,与顺序无关。
原理:通过不同顺序的前缀匹配,观察输出结果。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 test123;
  }
  
  location /test1234 {
    return 200 test1234;
  }

  location /welcome1234 {
    return 200 welcome1234;
  }

  location /welcome123 {
    return 200 welcome123;
  }
}

结果:访问/welcome1234和/test1234分别返回welcome1234和test1234,与顺序无关,只与匹配长度有关。

实验四
目的:比较高优先级前缀(^~)与正则的优先级
原理:将正则location置于最前,高优先级前缀location置于后面,通过观察输出结果,比较优先级。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ^~ /test123 {
    return 200 prefix;
  }
}

结果:访问/test123返回prefix,证明^~优先级高于正则

实验五
目的:一个配置文件了解location优先级
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location =/test12345 {
    return 200 =/test12345;
  }

  location /test12 {
    return 200 /test12;
  }

  location /test1234 {
    return 200 /test1234;
  }

  location ^~ /test123 {
    return 200 "^~/test123";
  }

  location ~ "/test\d{3}$" {
    return 200 "~/test\d{3}";
  }

  location ~ "/test\d{4}$" {
    return 200 "~/test\d{4}";
  }

  location ~ "/test\d*" {
    return 200 "~/test\d*";
  }
}

结果:
1.访问/test12返回~/test\d*,匹配项/test12、/test\d*,正则优先级高,故命中正则。
2.访问/test123返回^~/test123,匹配项/test12、^~/test123、~/test\d{3}、~/test\d*,先对前缀进行比较,发现有2个前缀/test12和^~/test123,后者长,用后者,然后发现前缀匹配带^~,不再查找正则,故命中^~/test123。
3.访问/test1234返回~/test\d{4},匹配项/test12、/test1234、^~/test123、~/test\d{4}、~/test\d*,先对前缀进行比较,发现有3个前缀/test12、/test1234、^~/test123,根据匹配长度,选中/test1234,再对正则进行匹配,~/test\d{4}是第一个匹配的正则,而之前选出的前缀中不带^~,故命中第一个匹配的正则。
4.访问/test12345返回=/test12345,匹配项=/test12345、/test12、/test1234、^~/test123、~/test\d*,有精确匹配存在,其他的都靠边站。
5.访问/test123456返回~/test\d*,匹配项/test12、/test1234、^~/test123、~/test\d*,前缀选择/test1234,无^~,命中第一个(唯一一个)匹配的正则表达式。
6.访问/welcome/test1234返回~/test\d{4},匹配项~/test\d{4}、~/test\d*,正则可以匹配path的一部分,而前缀只能从头开始匹配。
7.访问/test12345?params=1返回=/test12345,location匹配的是path部分,不包括后面的queryString。

总结
location匹配的算法描述如下(实验的出的结论,非源码中的算法):
1.令pathname=url路径部分,findPrefix=””
2.令prefixList=所有前缀location,regularList=所有正则location,exactList=所有精确匹配location
3.遍历exactList,查找location =pathname,如果找到,则命中,算法结束
4.遍历prefixList,如果匹配pathname则与findPrefix的长度进行比较
4.1如果长度大于findPrefix,令findPrefix=当前location
4.2如果长度小于findPrefix,不做任何操作,继续遍历
4.3如果长度等于findPrefix,在配置载入的时候,会报location重复的错误(如同时存在/123和^~/123),没有该分支
5.判断findPrefix是否带^~前缀,如果带,命中findPrefix,算法结束
6.遍历regularList,如果匹配pathname,命中当前location,算法结束
7.如果findPrefix不为空,命中findPrefix,算法结束
8.输出404

退出移动版