nginx location匹配规则

location [=|~|~*|^~] /uri/ { … }
比如 http://localhost/a/b/c.htm 我们说的location 匹配的是/a/b/c.htm部分
规则语法
语法 匹配规则

普通匹配(遵循最大前缀匹配规则, 优先度比正则低)
=
精确(严格)匹配, 优先度最高,后续不再匹配正则
^~
非正则匹配(依然遵循最大前缀匹配规则)
开头表示 uri 以某个常规字符串开头,理解为匹配 url 路径即可。
nginx 不对 url 做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格),后续不再匹配正则
~ 表示区分大小写的正则匹配
~* 表示不区分大小写的正则匹配
/ 通用匹配,任何请求都会匹配到(本质上等同于语法 空)

!~ 和 !~* 分别为区分大小写不匹配及不区分大小写不匹配 的正则, 但是是用于条件判断的时候(即 if 语句)

if ($host !~* "^www\.") {
# ...
}

匹配简单来说:

优先匹配 = 精确匹配, 若未匹配到则转下一步骤
依照最大前缀匹配规则, 先匹配普通规则(空 , ^~)

若最终匹配到 ^~, 则使用

若匹配到 空 或 未匹配到, 则转下一步骤(当前匹配结果暂时保存)
按照物理存储顺序, 若匹配到任意一条正则, 马上使用(无视后面正则)

若未匹配到任意正则, 则使用步骤2中匹配到普通正则

location 分类

只有两类:正则location和普通location

~ 和 ~* 为正则location
=、^~、@和无任何前缀的都属于普通location,另外,@是用作服务端内部的一种转发行为,很少用,在此不做讨论。
匹配顺序:

先普通,再正则
普通location之间的匹配顺序:按最大前缀匹配
如location /a/{},location /a/b/ {},请求 http://hostname/a/b/c.html 匹配的是 location /a/b/ {}
正则location之间的匹配顺序:按配置文件中的物理顺序匹配,只要匹配到一条正则,就不再考虑后面的
若普通location匹配到 精确匹配= 或 非正则匹配 ^~, 则不再进行后续的正则匹配

普通location与正则location之间的匹配结果选择
普通location先匹配,匹配到了结果,只是一个临时结果;

会继续正则location的匹配,
如果匹配到正则,则用匹配到的正则结果;
如果没有匹配到正则,则继续用普通匹配的那个结果

综上,常规的顺序是匹配完普通location,还要继续匹配正则location,但是,也可以告诉nginx,匹配到了普通location,就不要再搜索匹配正则location了,通过在普通location前面加上^~符号,^表示非,~表示正则,^~就是表示不要继续匹配正则。

除了^~,=也可阻止nginx继续匹配正则,区别在于^~依然遵循最大前缀匹配规则,而=是严格匹配
扩展
location / {}和 location =/ {}的区别

/ {}作为普通匹配,是遵循最大前缀匹配原则的,所以,对于一个url,如果有更特殊合适的匹配,就选特殊合适的,如果没有更特殊合适的匹配,也有 / {}兜着,就像是默认配置一样

=/ {} 遵循的是严格匹配规则,只能匹配到 http://ip:port/,同时会停止搜索正则匹配。
测试

简单理解的方法:
多个 location 配置的情况下匹配顺序为

首先匹配 =
其次匹配 ^~
其次是按文件中顺序的正则匹配
最后是交给 / 通用匹配
当有匹配成功时候,停止匹配,按当前匹配规则处理请求

location = / {
#规则A
}
location = /login {
#规则B
}
location ^~ /static/ {
#规则C
}
location ~ \.(gif|jpg|png|js|css)$ {
#规则D
}
location ~* \.png$ {
#规则E
}
location / {
#规则F
}

那么产生的效果如下:
访问根目录 /, 比如 http://localhost/ 将匹配规则 A
访问 http://localhost/login 将匹配规则 B,http://localhost/register 则匹配规则 F
访问 http://localhost/static/a.html 将匹配规则 C
访问 http://localhost/a.gif, http://localhost/b.jpg 将匹配规则 D和规则 E,但是规则 D 顺序优先,规则 E不起作用,而 http://localhost/static/c.png则优先匹配到规则 C
访问 http://localhost/a.PNG 则匹配规则 E,而不会匹配规则 D,因为规则 E 不区分大小写
访问 http://localhost/category/id/1111 则最终匹配到规则 F,因为以上规则都不匹配,这个时候应该是 nginx 转发请求给后端应用服务器,比如 FastCGI(PHP),tomcat(jsp),nginx 作为反向代理服务器存在

接下来测试验证。

1.先验证第二条:普通location之间的匹配顺序:按最大前缀匹配

nginx.conf配置:

#普通location
location /a/b {
  return 666;
}
#普通location
location /a/b/c {
  return 777;
}

测试链接:http://192.168.88.38/a/b,http状态码为666,符合预期。如图(后面的测试可自行F12打开浏览器控制台查看http状态码,不再截图):

测试链接:http://192.168.88.38/a/b/c,http状态码为777,匹配的是location /a/b/c {return 777;},符合预期。

2.验证第三条:正则location之间的匹配顺序:按配置文件中的物理顺序匹配,只要匹配到一条正则,就不再考虑后面的

nginx.conf配置:

location ~* /a {
  return 999;
}
#匹配a-z的任意一个字母
location ~* ^/[a-z]$ {
  return 666;
}

测试链接:http://192.168.88.38/a,http状态码999,匹配的是location ~* /a {renturn 999;},符合预期。
将nginx.conf中的两个正则匹配顺序调换下:

location ~* ^/[a-z]$ {
  return 666;
}
location ~* /a {
  return 999;
} 

测试链接:http://192.168.88.38/a,http状态码666,匹配的是location ~* ^/[a-z]$,符合预期。

3.验证第4条,其实第4条就相当于是总结性的匹配顺序了:

nginx.conf配置:

#普通location
location /a {
  return 666;
}
#普通location
location /a/b {
  return 777;
}
#正则location
location ~* /a/b {
  return 888;
}

测试链接:http://192.168.88.38/a,http状态码666,匹配到普通location,location /a {return 666;},符合预期。
测试链接:http://192.168.88.38/a/b,http状态码777,先进行普通location匹配,遵循最大前缀原则,匹配到location /a/b {return 777; },但是,这只是一个临时结果,因为接下来还要继续往下进行正则location匹配,匹配到 location ~* /a/b { return 888; },最终返回结果为888。符合预期。
将正则location的规则改下:

#普通location
location /a {
  return 666;
}
#普通location
location /a/b {
  return 777;
}
#正则location
location ~* /a/c {
  return 888;
}

测试链接:http://192.168.88.38/a/b,http状态码777,匹配到location /a/b {return 777; },并且因为接下来没有符合的正则location,所以最终返回为777,符合预期。

综上,location的匹配顺序及结果取值都符合2,3,4点结论。
接下来再测试验证普通location中的^~及=符号对于匹配搜索过程的阻断效果,当然,别忘了这俩符号的真实作用。^~为普通字符匹配,=为精确匹配。
^~测试验证nginx.conf配置:

location /a {
  return 666;
}
#普通匹配
location ^~ /a/b {
  return 777;
}
#正则location
location ~* /a/b {
  return 888;
}

测试链接:http://192.168.88.38/a/b,匹配到 location ^~ /a/b {return 777;}后,因为使用了^~符号,不再继续搜索正则location匹配,所以,虽然下面有符合条件的正则location,但是最终还是返回了777,符合预期。
=测试验证nginx.conf配置:

location /a {
  return 666;
}
#普通匹配
location = /a/b {
  return 777;
}
#正则location
location ~* /a/b {
  return 888;
}

测试链接:http://192.168.88.38/a/b,匹配到 location = /a/b {return 777;}后,因为使用了=符号,不再继续搜索正则location匹配,最终返回777,符合预期。

另附上常用正则表达式:

. : 匹配除换行符外的任意字符
? : 重复0次或1次
: 重复1次或更多次
: 重复0次或更多次
d :匹配数字
^ : 匹配字符串的开始
$ : 匹配字符串的结束
{n} : 重复n次
{n,} : 重复n次或更多次
[c] : 匹配单个字符,如此处的字符 c
[a-z] : 匹配a-z小写字母的任意一个
(a|b|c):匹配a或b或c

点赞