Nginx配置文件详解请参考另一篇文章 Nginx(三) 配置文件详解
本篇文章主要是探讨Nginx location的匹配顺序,依照惯例,我们还是先贴结论再看测试结果。
配置顺序
配置location时,需要注意正则表达式location的配置顺序。因为正则表达式匹配没有最长匹配策略,而是只要匹配成功就立刻终止匹配,所以配置顺序会影响匹配结果。而其它匹配规则不受配置顺序影响。
当我们配置某个/uri的location时,尽量不要同时使用通用匹配和“指定字符串开头”的匹配,不然会搞乱自己的配置思路,容易分不清匹配优先级。
# 注意,这两个location是冲突的,二者只能存其一。
location /test {
···
}
location ^~ /test {
···
}
优先级
结合所有测试结果,我们先对各种匹配规则的作用优先级做个排序。
优先级排序 | 匹配规则 | 修饰符 |
1 | 精确匹配 | = |
2 | 指定字符串开头 | ^~ |
3 | 正则匹配 | ~、~* 、!~、!~*、~* \.(gif|jpg|jpeg)$、~* /js/.*/\.js |
4 | 通用匹配 | / |
注意:1.默认情况下,“指定字符串开头”匹配规则的优先级确实比正则表达式高。但是,当请求URI前缀与某个通用匹配location完全匹配时,Nginx是永远也不会匹配到"^~"的(即使"^~"的匹配长度是最长的),而是先匹配正则表达式,再进行通用匹配,所以“指定字符串开头”匹配规则的优先级比正则表达式高并不是绝对的。(参考测试2 - 测试8)
2.“指定字符串开头”匹配规则和通用匹配是有最长匹配策略的,并不是匹配到就终止匹配;
匹配顺序
匹配location的过程,其实可以理解成一个在众多选项中寻找最佳答案的过程。当然,“=”代表的就是正确答案,而其它情况只是相对于正确答案的最佳选项,如果有正确答案就选正确答案,如果没有就只能选择最佳答案,所以,严格意义上来说,我们只需要关注“以指定字符串开头”、正则表达式和通用匹配这三者之间的匹配顺序。下面我们来看具体的匹配顺序:
stp1.按序匹配完所有location。Nginx按照location配置顺序依次匹配完所有location,每次匹配完都会进行记录比较(仅记录通用匹配),如果匹配的长度比记录中的长,就覆盖记录。
stp2.精确匹配。如果在第一步的匹配过程中,与"="location匹配成功,则立刻终止匹配;参考测试1
stp3.检查请求URI前缀是否与某个通用匹配location完全匹配。如果没有这种情况,那么“指定字符串开头”匹配规则的优先级要比正则表达式高,Nginx此时会优先匹配"^~",再匹配正则表达式。如果有这种情况,Nginx不再匹配"^~"(即使"^~"的匹配长度是最长的),而是直接去匹配正则表达式。所以stp3是一个确认“指定字符串开头”和正则表达式匹配规则优先级的过程。参考测试2-测试9
stp4.当“指定字符串开头”匹配规则的优先级比正则表达式高时,Nginx才开始匹配"^~"。如果跟某个"^~"location匹配成功,并不会立刻停止匹配,而是要继续匹配完所有"^~"(最长匹配策略),直到找到与"^~"匹配长度最长的那个location后才停止匹配。所以,既然有最长匹配策略,那么在配置文件中,"^~"location的配置顺序并不会影响最终匹配结果。参考测试3、4、5、7
stp5.当"指定字符串开头"匹配失败或正则表达式匹配规则的优先级比“指定字符串开头”高时,Nginx才开始匹配正则表达式。此时,Nginx会按照配置文件中正则表达式location的配置顺序依次完成匹配,只要匹配到就立刻停止匹配。参考测试2、6、8、11、12
stp6.当"指定字符串开头"和正则表达式都匹配失败时,则在通用匹配中选择匹配长度最长的那个location(最长匹配策略)。参考测试9、10
stp7.当通用匹配都匹配失败时,只能选择"/"location了。
下面,我们开始测试。基本配置如下
http {
log_subrequest on; # 开启将子请求日志记录到access.log中
log_format format2 escape=json '{'
'"SN":"$sn",' #自定义变量sn
'"http_host":"$http_host",'
'"remote_addr":"$remote_addr",'
'"time_iso8601":"$time_iso8601",'
'"request":"$request",'
'"http_referer":"$http_referer",'
'"request_time":"$request_time",'
'"request_length":"$request_length",'
'"status":"$status",'
'"bytes_sent":"$bytes_sent",'
#'"body_bytes_sent":"$body_bytes_sent",'
'"user_agent":"$http_user_agent",'
'}';
absolute_redirect on;
server_name_in_redirect off;
port_in_redirect on;
server {
listen 8688;
server_name www.read********.cn;
access_log logs/access.log format2;
error_log logs/error.log notice; # 将error_log日志级别修改为notice,否则rewrite log无法记录。
rewrite_log on; # 开启记录请求重写日志,默认是关闭
root pages; # 根目录设置为psges,该目录下有index.html、test.html、one.html、two.html、three.html
# 下面配置本次测试的指令
···
···
}
}
测试1:精确匹配 "="
server {
···
set $sn 8;
location /tes {
set $sn 101;
rewrite ^(.*)$ /t101;
}
location /testla { # location102,通用匹配
set $sn 102;
rewrite ^(.*)$ /t102;
}
location ~ /test { # location201,正则表达式20x
set $sn 201;
rewrite ^(.*)$ /t201;
}
location ^~ /test { # location301,指定字符串开头
set $sn 301;
rewrite ^(.*)$ /t301;
}
location ^~ /testl { # location302,指定字符串开头
set $sn 302;
rewrite ^(.*)$ /t302;
}
location ~* /testl { # location202,正则表达式20x
set $sn 202;
rewrite ^(.*)$ /t202;
}
location ~* /testla { # location203,正则表达式20x
set $sn 203;
rewrite ^(.*)$ /t203;
}
# 下面这个location会跟第二个location冲突
#location ^~ /testla {
# set $sn 99;
# rewrite ^(.*)$ /t99;
#}
location = /testla {
set $sn 401;
rewrite ^(.*)$ /t401;
}
location / {
index index.html index.htm;
}
}