Nginx中location 匹配规则
location 匹配的变量
Nginx 的 location 规则匹配的变量是 $uri, 所以不用管后面的参数 $query_string (或者 $args)
location 匹配的种类
匹配格式:
1 | location [空格 | = | ~ | ~* | ^~ | @ ] /uri/ { |
上面匹配格式分为三部分:
- 修饰符匹配规则
[空格 | = | ~ | ~* | ^~ | @ ]
- uri匹配规则
/uri/
- 大括号内的路由转发
1
2
3{
...
}
修饰符匹配规则
格式:[空格 | ^~ | = | ~ | ~* |@ ]
接下来解释一下,这些都表示啥意思:
字符 | 解释 |
---|---|
空格 | 无修饰符的前缀匹配,匹配前缀是 你配置的(比如说你配的是 /aaa) 的url |
^~ | 常规字符串匹配(跟空格类似,但是它优先级比空格高)。^表示“非”,即不查询正则表达式。如果匹配成功,并且所匹配的字符串是最长的, 则不再匹配其他location。 |
= | 表示精确匹配,如果找到,立即停止搜索并立即处理此请求。 |
~ | 表示执行一个正则匹配,区分大小写 |
~* |
表示执行一个正则匹配,不区分大小写。注意,如果是运行 Nginx server 的系统本身对大小写不敏感,比如 Windows ,那么 ~* 和 ~ 这两个表现是一样的 |
@ | 用于定义一个 Location块,且该块不能被外部Client 所访问,只能被Nginx内部配置指令所访问,比如try_files 或 error_page |
修饰符的匹配顺序
- 优先查找精确匹配,精确匹配 (=) 的 location 如果匹配请求 URI 的话,此 location 被马上使用,匹配过程结束。
- 接下来进行常规字符串匹配(空格 和
^), 如果发现匹配最长的那个是 ^前缀, 那么也停止搜索并且马上使用,匹配过程结束。 否则继续往下走。 - 如果常规字符串匹配失败,或者匹配的最长字符串不是 ^~ 前缀 (比如是空格匹配),那么继续搜索正则表达式匹配, 这时候就根据在配置文件定义的顺序,取最上面的配置(正则匹配跟匹配长度没关系,只跟位置有关系,只取顺序最上面的匹配)
- 如果第三步找到了,那么就用第三步的匹配,否则就用第二步的匹配(字符匹配最长的空格匹配)
简单的来说就是顺序如下:精确匹配 > 字符串匹配( 长 > 短 [ 注: ^~ 匹配则停止匹配 ]) > 正则匹配( 上 > 下 )
换成符号的优先级就是:[=] > [^~] > [~/~*] > [空格]
注意几个细节:
- 常规字符串匹配类型。是按前缀匹配(从根开始)。 而正则表达式匹配是包含匹配,只要包含就可以匹配
和*的优先级一样,取决于在配置文件中的位置,最上面的为主,跟匹配的字符串长度没关系,所以在写的时候,应该越精准的要放在越前面- 空格匹配和^~都是字符串匹配,所以如果两个后面的匹配字符串一样,是会报错的,因为 nginx 会认为两者的匹配规则一致,所以会有冲突
- ^~, =, ~, ~* 这些修饰符和后面的 URI 字符串中间可以不使用空格隔开(大部分都是用空格隔开)。但是 @ 修饰符必须和 URI 字符串直接连接。
例子
1 | location / { |
这个是测试结果
1 | [root@VM_156_200_centos ~]# curl 127.0.0.1/hello #精确匹配,直接结束 |
配置文件配置:
1 | location /images/test.png { |
这个是测试结果
1 | [root@VM_156_200_centos ~]# curl http://127.0.0.1/images/test.png |
第一个返回 3 是因为本例是 普通匹配和正则匹配都存在, 并且因为 ^~ 匹配不是最长的话,那么就取 正则匹配, 正则匹配满足条件的有两个, 取最上面那个, 所以是 3, (本例的字符串最长匹配是空格匹配)。(对于本例来说,如果去掉后面的两个正则匹配,那么返回的就是 1, 因为空格匹配的字符串是最长的)
第二个返回 2 是因为普通匹配和正则匹配都存在,但是这个^~ 匹配是最长的,所以就是 2。
普通字符串的匹配冲突
如果我这样子写:
1 | location /images/test.png { |
这时候我 reload 是会报错的:
1 | root@VM_156_200_centos sbin]# ./nginx -s reload |
辟谣 ~ 比 ~* 优先级高
之前也有在网上看到这种说法,就是匹配顺序的时候,如果都匹配了,那么 ~ 比 ~* 优先级高,其实这个说法是错误的,这哥俩并没有谁比谁高贵,根据上面的匹配顺序,如果都是正则匹配,那么就是谁排在前面,就采用谁。 做个实践, 我的执行顺序是这样子的:
1 | location ~* \.jpG$ { |
我的测试结果如下:
1 | [root@VM_156_200_centos ~]# curl 127.0.0.1/1.jpg |
只要有匹配,肯定是最上面的那个 1。
辟谣之 修饰符 包含 !~ 和 !~*
我看到有些网上的文章说 nginx 的 location modifier 还包含 !~ 和 !~* 这两个,其实是不对的(也有可能是旧版本的,至少我的最新版本的 nginx 不支持), 如果你这样子:
1 | location !~ \.(gif|jpg)$ { |
那么在 reload 的时候, nginx 会报这个错误:
1 | nginx: [emerg] invalid location modifier "!~" in /usr/local/nginx/conf/nginx.conf:25 |
不过因为 Nginx 的正则是使用PCRE(Perl Compatible Regular Expressions), 所以我们可以这样子写来达到我们想要的目的:
1 | location ~ \.*(?<!(gif|jpg))$ { |
做个实践,假设我的路由是这样子: 如果后缀含有 gif 或者是 jpg, 那么就会返回 200, 否则就会返回 404
1 | location / { |
测试结果如下:
1 | [root@VM_156_200_centos ~]# curl 127.0.0.1/1.gif |
这个结果是对的。 那么就换成,如果后缀不是 gif 或者是 jpg,那么才返回 200, 否则就返回 404 (相当于上述的 !~):
1 | location / { |
可以看到,同样的结果,结果相反了:
1 | [root@VM_156_200_centos ~]# curl 127.0.0.1/1.gif |
所以是可以实现这种效果的。
@前缀的命名匹配
这个主要是内部定义一个 location 块,举个例子,因为 location 只验证 uri,参数是没有验证的,如果我们要验证参数,并且要根据不同的参数来进行不同的操作的话,就可以用这个内部定义块,举个例子:
1 | location / { |
测试结果如下:
1 | [root@VM_156_200_centos ~]# curl http://127.0.0.1/?service=one |
location uri匹配规则
根据前面的符号,这里可以填写精确到 path 路径,也可以填正则表达式,Nginx 用的是 PCRE正则表达式语法,下表是在PCRE中元字符及其在正则表达式上下文中的行为的一个完整列表:
字符 | 描述 |
---|---|
|将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,\n 匹配 \n。\n 匹配换行符。序列 \ 匹配 \ , 而 ( 则匹配 (。即相当于多种编程语言中都有的转义字符的概念。 | |
^ | 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配\n 或 \r 之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了 RegExp 对象的 Multiline 属性,$ 也匹配\n 或 \r 之前的位置。 |
* |
匹配前面的子表达式零次或多次。例如,zo* 能匹配 z 以及zoo。 * 等价于 {0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,zo+ 能匹配 zo 以及 zoo,但不能匹配 z。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 does 或 do。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的n次。例如,o{2} 不能匹配 Bob 中的 o,但是能匹配 food 中的两个o。 |
{n,} | n 是一个非负整数。至少匹配n次。例如,o{2,} 不能匹配 Bob 中的 o,但能匹配 foooood 中的所有o。o{1,} 等价于 o+ 。o{0,} 则等价于 o*。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,o{1,3} 将匹配 fooooood 中的前三个o。o{0,1} 等价于o?。 请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串oooo,o+? 将匹配单个 o,而 o+ 将匹配所有 o。 |
. | 匹配除\n和\r之外的任何单个字符。要匹配包括\n和\r在内的任何字符,请使用像[\s\S]的模式。 |
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在 VBScript 中使用 SMatches 集合,在JScript中则使用 0…0…0…9 属性。要匹配圆括号字符,请使用( 或 )。 |
(?:pattern) | 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符 “( |
(?=pattern) | 非获取匹配,正向肯定预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,”Windows(?=95 |
(?!pattern) | 非获取匹配,正向否定预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如”Windows(?!95 |
(?<=pattern) | 非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,”(?<=95 |
(?<!pattern) | 非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如 “(?<!95 |
x | y |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,[abc] 可以匹配 plain 中的 a。 |
[^xyz] | 否定字符集合。匹配未包含的任意字符。例如,[^abc] 可以匹配 plain 中的 p。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,[a-z] 可以匹配 a 到 z 范围内的任意小写字母字符。 |
[^a-z] | 否定字符范围。匹配任何不在指定范围内的任意字符。例如,[^a-z] 可以匹配任何不在 a 到 z 范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b 可以匹配 never 中的 er ,但不能匹配 verb 中的 er。\b1_ 可以匹配1_23中的1_,但不能匹配21_3中的1_。 |
\B | 匹配非单词边界。er\B 能匹配 verb 中的 er ,但不能匹配never 中的 er。 |
\cx | 匹配由x指明的控制字符。例如,\cM 匹配一个Control-M 或 回车符。x 的值必须为A-Z或a-z之一。否则,将c视为一个原义的c字符。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^\f\n\r\t\v]。 |
\w | 匹配包括下划线的任何单词字符。类似但不等价于[A-Za-z0-9_],这里的单词字符使用Unicode字符集。 |
\W | 匹配任何非单词字符。等价于[^A-Za-z0-9_]。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,\x41匹配A。\x041则等价于\x04&1。正則表达式中可以使用ASCII编码。 |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,(.)\1匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果\n之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字(0-7),则 n 为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果\nm之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果\nm 之前至少有 n 个获取,则 n 为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。 |
\nml | 如果n为八进制数字(0-7),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9 匹配版权符号(©)。 |
例子
判断是不是 IP 白名单:
1
2
3
4
5
6
7
8
9
10
11
12#定义初始值
set $my_ip 0;
#判断是否为指定的白名单
if ( $http_x_forwarded_for ~* "10.0.0.1|172.16.0.1" ){
set $my_ip 1;
}
#不是白名单的IP进行重定向跳转
if ( $my_ip = 0 ){
rewrite ^/$ /40x.html;
}如果页面有多语言,但是这个多语言所在的文件找不到,那么就将多语言路径去掉,重新跳转
1
2
3
4
5location / {
if (!-e $request_filename) {
rewrite ^/([A-Za-z0-9_-]+)/(.*) /$2 permanent;
}
}比如你请求是这样子的 https://foo.com/zh-cn/a.html 这时候服务端找不到这个文件,那么就会重定向到 https://foo.com/a.html。 这个很适合那种有多语言静态页面的站点
如果是一些特殊的静态文件,那么额外配置
1
2
3location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
root /webroot/res/;
}针对国内的搜索爬虫,返回中文的页面
1
2
3
4
5
6
7set $chinaspider "0";
if ($http_user_agent ~* "Baiduspider|Sogou spider|Sogou web spider") {
set $chinaspider "1";
}
if ($chinaspider = 1) {
return 301 https://$server_name/zh-cn;
}禁止以/data开头的文件
1
2
3location ~ ^/data {
deny all;
}文件反盗链并设置过期时间
1
2
3
4
5
6
7
8
9
10
11
12location ~* ^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ {
valid_referers none blocked *.domain.com *.domain.net localhost 208.97.167.194;
if ($invalid_referer) {
rewrite ^/ http://error.domain.com/error.gif;
return 412;
break;
}
access_log off;
root /opt/lampp/htdocs/web;
expires 3d;
break;
}这里的 return 412 为自定义的 http 状态码,默认为403,方便找出正确的盗链的请求
- rewrite ^/ http://error.domain.com/error.gif;显示一张防盗链图片
- access_log off;不记录访问日志,减轻压力
- expires 3d所有文件3天的浏览器缓存
最后附上可以用作判断的全局变量
全局变量 | 内容 |
---|---|
$remote_addr | 获取客户端ip |
$binary_remote_addr | 客户端ip(二进制) |
$remote_port | 客户端port,如:50472 |
$remote_user | 已经经过Auth Basic Module验证的用户名 |
$host | 请求主机头字段,否则为服务器名称,如:blog.sakmon.com$request用户请求信息,如:GET ?a=1&b=2 HTTP/1.1 |
$request_filename | 当前请求的文件的路径名,由root或alias和URI request组合而成,如:/2013/81.html |
$status | 请求的响应状态码,如:200 |
$body_bytes_sent | 响应时送出的body字节数数量。即使连接中断,这个数据也是精确的,如:40 |
$content_length | 等于请求行的Content_Length的值 |
$content_type | 等于请求行的Content_Type的值 |
$http_referer | 引用地址 |
$http_user_agent | 客户端agent信息,如:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36 |
$args | 与 $query_string 相同 等于当中URL的参数(GET),如a=1&b=2 |
$document_uri | 与$uri相同, 这个变量指当前的请求URI,不包括任何参数(见$args) 如:/2013/81.html |
$document_root | 针对当前请求的根路径设置值 |
$hostname | 如:centos53.localdomain |
$http_cookie | 客户端cookie信息 |
$cookie_COOKIEcookie | COOKIE变量的值 |
$is_args | 如果有$args参数,这个变量等于 ”?”,否则等于”” |
$limit_rate | 这个变量可以限制连接速率,0 表示不限速 |
$query_string | 与$args相同,等于当中URL的参数(GET),如a=1&b=2 |
$request_body | 记录POST过来的数据信息 |
$request_body_file | 客户端请求主体信息的临时文件名 |
$request_method | 客户端请求的动作,通常为GET或POST,如:GET |
$request_uri | 包含请求参数的原始URI,不包含主机名,如:/2013/81.html?a=1&b=2 |
$scheme | HTTP方法(如http,https),如:http |
$uri | 这个变量指当前的请求URI,不包括任何参数(见$args) 如:/2013/81.html |
$request_completion | 如果请求结束,设置为OK。当请求未结束或如果该请求不是请求链串的最后一个时,为空(Empty),如:OK |
$server_protocol | 请求使用的协议,通常是HTTP/1.0或HTTP/1.1,如:HTTP/1.1 |
$server_addr | 服务器IP地址,在完成一次系统调用后可以确定这个值 |
$server_name | 服务器名称,如:blog.sakmon.com |
$server_port | 请求到达服务器的端口号,如:80 |
路由转发
Rewrite语法规则
rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。
如果想修改域名或参数字符串,可以使用全局变量匹配,也可以使用proxy_pass反向代理。
rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。
rewrite语法:rewrite 正则表达式 要替换的内容 [flag];
其中flag有如下几个值:
- last – 本条规则匹配完成后,立即发起新一轮的location 匹配规则
- break – 本条规则匹配完成即终止,不再匹配后面的任何规则
- redirect – 返回302临时重定向,浏览器地址会显示跳转新的URL地址
- permanent – 返回301永久重定向。浏览器地址会显示跳转新的URL地址
1 | rewrite 后面没有任何 flag 时就顺序执行 |
很多情况下rewrite也会写在location里,它们的执行顺序是:
- 顺序执行server块中的rewrite模块指令,得到rewrite后的请求URI
- 执行location匹配
- 执行选定的location中的rewrite指令
- 如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;
- 循环超过10次,则返回500 Internal Server Error错误。
last 和 break的区别
- last 和 break一样 它们都会终止此 location 中其他它rewrite模块指令的执行
- 但是 last 立即发起新一轮的 location 匹配 而 break 则不会
1 | location / { |
redirect 和 permanent
- 临时重定向:对旧网址没有影响,但新网址不会有排名
- 永久重定向:新网址完全继承旧网址,旧网址的排名等完全清零
修改nginx.conf文件:
1 | server { |
rewrite 后的请求参数
如果替换字符串replacement包含新的请求参数,则在它们之后附加先前的请求参数。如果你不想要之前的参数,则在替换字符串 replacement 的末尾放置一个问号,避免附加它们。
1 | 由于最后加了个 ?,原来的请求参数将不会被追加到rewrite之后的url后面 |
rewrite_log
开启或者关闭 rewrite模块指令执行的日志,如果开启,则重写将记录下notice 等级的日志到nginx 的 error_log 中,默认为关闭 off
Syntax: rewrite_log on | off;
return
格式:
1 | return code [text]; |
停止处理并将指定的code码返回给客户端。 非标准code码 444 关闭连接而不发送响应报头。
从0.8.42版本开始, return 语句可以指定重定向 url (状态码可以为如下几种 301,302,303,307),也可以为其他状态码指定响应的文本内容,并且重定向的url和响应的文本可以包含变量。
有一种特殊情况,就是重定向的url可以指定为此服务器本地的uri,这样的话,nginx会依据请求的协议$scheme, server_name_in_redirect 和 port_in_redirect自动生成完整的 url
此处要说明的是server_name_in_redirect和port_in_redirect 指令是表示是否将server块中的 server_name 和 listen 的端口 作为redirect用 )
1 | return code [text]; 返回 ok 给客户端 |
break
停止执行 ngx_http_rewrite_module 的指令集,但是其他模块指令是不受影响的
1 | server { |