Nginx的location/if/return/rewrite/try_files指令及flag标志位的使用
Table of Contents
location指令的使用
location指令要放在server
模块里面,语法如下:
server {
location 修饰符 字符串或正则 {
[具体配置]
}
}
location指令是干嘛用的?
根据官方文档,location的作用是:Sets configuration depending on a request URI。即根据请求的URI来设置具体配置,比如:
location ^~ /images/ {
[ configuration ]
}
该配置表示当uri中包括/images/
时,就会被匹配,实际上不止要包括/images/
,并且还要以/images/
开头,因为它的前面有^~
修饰符,该修饰符表示要以它后面指定的字符串开头,事实上规则还挺复杂。
location指令修饰符及其匹配顺序
location匹配顺序,一句话概括:非正则最长匹配优先,如果最长匹配用^~
修饰,则不进行正则匹配。
修饰符有四种+无修饰符:
=
精确匹配^~
最长前缀匹配~
正则匹配(区分大小写),可加!
符号表示“非”:!~
~*
正则匹配(不区分大小写),可加~
符号表示“非”:!~*
无修饰符
前缀匹配
也就是说,=
、^~
、无修饰符
这三种属于非正则,而~
、~*
属于正则匹配。location匹配有一个特点:非正则匹配优先!也就是匹配优先级:非正则
> 正则
。
非正则匹配:
=
优先级最高^~
和无修饰符
优先级相同
非正则匹配中,=
是优先级最高的,因为它是精准匹配,一旦匹配上,马上停止匹配过程,并进入匹配的location执行该location里面的指令。
而^~
和无修饰符
都叫“前缀匹配”,它们之间没有优先级关系,nginx会按它们在配置文件中出现的顺序来匹配,假设有多个location都匹配上了(无论它们有^~
或者是无修饰符
),那么nginx会找到最长的那个,并看看它前面有没有^~
修饰符,如果有,则nginx会马上终止匹配过程,并选用这个“最长匹配”的location,进入它的内部,执行它内部的指令。注意,如果只有一个^~
匹配,那么它也算是最长的,因为没有人跟它比较(就像只有一个人考试,这个人肯定是第一名)。
如果最长的那个匹配前面没有^~
(其实没有^~
就一定是无修饰符
,因为不可能是=
,因为=
优先级高于“前缀匹配”),则nginx会先把这个最长的匹配“记住”,然后继续执行正则部分的location匹配,也就是那些用了~
(!~
)或~*
(!~*
)修饰符的location(如果有多个正则匹配,则按它们在配置文件中出现的顺序进行匹配)。当然,如果所有的“非前缀”location都不匹配,那一样会进入正则匹配。
如果正则匹配上了,那么它就会选中第一个正则匹配上的location,并进入该location里面,执行它里面的指令。而如果正则匹配都没匹配上,那么前面“记住”的那个location就会被选用。
总结:
- 同时有
无修饰符
和“正则”两种都匹配上,那么nginx会选正则匹配; - 同时有
^~
、无修饰符
都匹配上,则nginx会找最长的那个,看它前面有没有^~
,如果有,则马上终止匹配并选用该location; - 如果最长的那个匹配前面没有
^~
修饰,则暂时记住该“最长匹配”,然后进入正则匹配; - 如果有任意一个正则匹配成功,则立刻终止匹配,并选用该location;
- 如果没有一个正则匹配成功,则使用前面记住的“最长匹配”。
有人可能会说,那如果带^~
修饰符的字符串与无修饰符
的字符串长度一样,那怎么判断“最长”的是哪个呢?比如以下两个,如果都与URI能匹配上,那根本无法匹配哪个是最长匹配呀
location /user/login/ {
[规则A]
}
location ^~ /user/login/ {
[规则B]
}
答案是,这种情况根本不可能出现,因为这样的配置会报错,报错类似这样:
nginx: [emerg] duplicate location "/user/login/" in /usr/local/etc/nginx/vhost/personal/www.test.com.conf:25
location匹配过程
什么是URI: URI是指URL中除去协议、域名及参数后,剩下的部分,比如请求的URL为:http://www.test.com/test/index.php?page=1 ,则URI为:/test/index.php。
匹配前对URI做标准化处理: 匹配是针对一个标准的URI进行的,所以在开始匹配前,nginx会先把URI处理为正常的标准的URI,它会做以下处理:
- 把所有
%xx
格式的字符解码为正常字符,比如%26
是&
,%3D
是=
,%3F
是?
,%2F
是/
; - 把所有相对路径符号,即
.
和..
解析为绝对路径; - 把连续的两个或多个斜杠合并成一个,例如把
www.example.com//index.html
变成www.example.com/index.html
。
大致匹配过程:
- 把server模块下所有一级location分为两部分(内嵌的location属于二级location),一部分是非正则location(包括
=
、^~
及无修饰符
三种),一部分是正则location(包括~
和~*
两种); - 先对非正则部分进行匹配操作,非正则部分匹配完,再根据匹配的具体情况决定是否继续对正则部分进行匹配;
- 两部分都各自按在配置文件中出现的顺序自上往下匹配,非正则部分对顺序的要求没有正则部分严格,如果多个规则都能匹配上URI,那么位于最后的规则仍然有可能被选中。而正则部分,对顺序要求严格,如果正则部分有多个规则都能匹配上URI,则只有第一个会被选中,选中后马上终止匹配过程,所以正则部分对顺序很重要。
匹配步骤:
- 用所有的前缀字符串去匹配URI(这里说的前缀字符串,是除
~
和~*
修饰的字符串外的所有字符串,包括=
、^~
及无修饰符
三种都算是前缀字符串); - 如果找到
=
号修饰的字符串与URI匹配(无论它在哪个位置),则终止匹配过程,并进入该location内部执行具体操作; - 如果不找到
=
号修饰的字符串,则继续匹配普通前缀字符串(即用^~
修饰的或无修饰符
的字符串); - 在
^~
修饰的或无修饰符
的字符串中如果有多个规则能匹配上URI,则会查看最长的那一个是否用^~
修饰; - 如果最长的那个有用
^~
修饰,则终止匹配,这个用^~
修饰的“最长”的匹配就是最终被选用的location,但是如果这个“最长”的匹配没有用^~
修饰(说明它是无修饰符
的),则会暂时把它存储下来,但不会终止匹配,而是继续进行后面的正则匹配; - 开始正则匹配,匹配所有用
~
或~*
修饰的字符串 - 匹配到第一个正则表达式后终止匹配,使用对应的location;
- 如果没有匹配到正则表达式,则使用之前存储的前缀字符串对应的location,至此,整个匹配过程结束。
测试nginx配置:
server {
listen 80;
server_name www.test.com;
charset utf-8;
default_type text/html;
root /Users/bruce/www/personal/test;
access_log /usr/local/var/log/nginx/www.test.com.access.log;
error_log /usr/local/var/log/nginx/www.test.com.error.log;
autoindex on;
autoindex_exact_size off;
autoindex_format html;
autoindex_localtime on;
# ======= location部分 开始 ===========
# 由于location有很多种,所以后面具体说到的时候再说location是什么
# ======= location部分 结束 ===========
location ~ .php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
}
在hosts文件中添加域名解析:
127.0.0.1 www.test.com
另外,保证root、access_log、error_log三个指令指向的路径存在并且具有可访问权限,只要目录存在即可,目录里无需放任何文件。
鉴于一般人可能没装echo模块,所以我使用了return 502方式,如果你编译了echo模块,那么你可以把return 502
换成echo
即可,并且用echo的时候后面的\n
还可以去掉,而用return 502
如果后面没有\n
,用curl访问则会多一个%
,我加\n
就是为了去掉这个百分号。
测试时,最好使用curl测试,不要用浏览器。因为用浏览器会直接报错而不是显示502后面的那个值,当然这个错误可以通过添加一句配置来解决default_type text/html;
,但这样还是有个问题,就是在访问图片时,如果是浏览器,它自动显示图片,虽然图片并不存在,但浏览器还是会显示一个黑色界面表示这是显示图片,而用curl就没这个问题,curl不用参数,直接curl http://www.test.com
即可。
以下为测试url及结果说明(把以下各例中的location填到前面配置里进行测试):
本例用于测试=
号在location中的作用
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则C\n";
}
# ^~修饰符表示前缀匹配
location ^~ /user/login/ {
return 502 "规则B\n";
}
location /user/login/ {
return 502 "规则B\n";
}
# =修饰符表示精确匹配(也属于前缀字符串的一种)
location = /user/login/ {
return 502 "规则A\n";
}
测试结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则A
匹配过程:先找出所有前缀字符串(本例是规则A和规则B,而规则C是正则字符串),然后按顺序用这些前缀字符串去匹配URI,发现规则B匹配成功,再往下发现规则A也匹配成功,然后分别查看它们的修饰符,发现规则A是用“=”号修饰,“=”号修饰的规则优先级最高,所以选中规则C并进入该location内部执行里面的代码。
解释:“=”号的使用优先级最高,但它并没有查找优先级,也就是说,如果你把“=”号修饰的location放在所有location之后的话,它其实也是最后才被匹配的,所以如果你想加速这个匹配过程,加速nginx的响应时间,请把“=”号修饰的location放在所有location的前面。
我们把前面的=
号修饰符那个location删掉,只剩下正则修饰符~
和前缀修饰符^~
,用于测试正则与前缀字符串之间的优先级
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则C\n";
}
# ^~修饰符表示前缀匹配
location ^~ /user/login/ {
return 502 "规则B\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则B
匹配过程:先匹配前缀字符串(本例只有规则B是前缀字符串),发现规则B能匹配成功,并且它是最长的前缀字符串(因为只有它一个,所以它就是最长的),所以查看它是否用“^~”修饰,发现它确实使用了“^~”修饰,于是马上终止匹配过程,并进入规则B内部执行里面的操作。
解释:因为匹配到规则B之后就终止了匹配过程,所以规则C并没有参与匹配过程,即使它在配置文件中的顺序位于规则B之前,但由于它是正则字符串,而正则字符串需要等所有前缀字符串匹配完之后,对它进行匹配(如果匹配过程未被终止的话)。
再来修改一下,把^~
修饰符修饰的字符串去掉login/
,变成只剩/user/
,用于测试正则匹配与前缀匹配的优先级,是否受它们所修饰的字符串长短影响
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则C\n";
}
# ^~修饰符表示前缀匹配
location ^~ /user/ {
return 502 "规则B\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则B
匹配过程:先匹配前缀字符串(本例只有规则B是前缀字符串),发现规则B能匹配成功,并且它是最长的前缀字符串(因为只有它一个,所以它就是最长的),所以查看它是否用“^~”修饰,发现它确实使用了“^~”修饰,于是马上终止匹配过程,并进入规则B内部执行里面的操作。
解释:其实规则B和规则C都能与URI匹配,并且规则C更接近URI(实际上它刚好等于URI),但它却没有机会参与匹配过程,因为nginx在匹配到规则B时已经终止了匹配过程,所以规则C是不可能被选中的。
继续,把location换成这两个,用于测试^~
与无修饰符
字符串之间的优先级
location ^~ /user/ {
return 502 "规则B\n";
}
location /user/login/ {
return 502 "规则C\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则C
匹配过程:先找到所有前缀字符串(在本例中是规则B和规则C),然后开始从上往下匹配,最后两个都匹配成功,但因为规则C比较长,于是查看它是否有“^~”修饰,发现没有,于是存储下规则C,继续正则匹配,发现并没有正则匹配,所以使用前面存储的规则C作为最终选中的规则,进入该location内部。
解释:“^~”修饰符和“无修饰符”都属于前缀匹配,它们之间无优先级关系,所以匹配顺序是它们在配置文件中出现的顺序,自上往下匹配。“^~”修饰符的作用:在所有与URI都匹配的前缀字符串中,如果最长的那个前缀字符串使用了“^~”修饰,则终止匹配过程,并使用该最长前缀字符串,换句话说,如果最长的那个前缀字符串没有使用了“^~”修饰,则会继续执行正则匹配。
在前面的基础上多加一个规则A(结果会出乎你的意料),用于说明“最长前缀匹配”未使用^~
修饰时,正则字符串会参与匹配,如果匹配上,则会被选用
location ~ /user/ {
return 502 "规则A\n";
}
location ^~ /user/ {
return 502 "规则B\n";
}
location /user/login/ {
return 502 "规则C\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则A
疑问:你是不是以为还是规则C?就算不是C,那也应该是B呀,怎么会是规则A?
匹配过程:先找到所有前缀字符串(在本例是规则B和规则C),然后按它们在配置文件中出现的顺序,从上往下执行匹配,发现规则B和规则C都能匹配上,于是找它们中最长的那个,很明显规则C最长,然后看最长的那个有没有用“^~”修饰,发现并没有用“^~”修饰,于是继续执行正则字符串匹配(即规则A),发现这个正则字符串能匹配上URI,于是选用它,进入该location内部执行具体操作。
解释:这涉及到nginx的一个规则,nginx官方文档中有这句话“If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.”,意思就是,如果匹配到的前缀字符串中,最长的那个字符串被“^~”修饰,则终止匹配,不再检查正则匹配。那如果匹配到的字符串中最长的那个字符串没有被“^~”修饰符修饰呢?那就会继续执行正则匹配(本例恰好就是这样),发现规则A能匹配上,所以最终返回的是规则A。
注意:不管规则A放在最前还是最后,最终匹配上的都是规则A。因为正则匹配总是在前缀匹配之后,与它在配置文件中的位置无关。
继续,把location换成这两个,用于说明:第一,正则匹配修饰修饰符~
和~*
之间,没有优先级关系;第二,只要有一个正则匹配上了,则马上终止匹配过程并选用该location;第三,它们之间的区别是一个区分大小写~
,一个不区分~*
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则A\n";
}
# ~修饰符表示不区分大小写正则匹配
location ~* /user/login/ {
return 502 "规则B\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则A
匹配过程:先查找出前缀字符串的location,发现没有,于是执行正则匹配(按它们在配置文件中出现的顺序从上到下匹配),发现第一个(规则A)能匹配上,于是终止匹配,选中规则A并进入该location内部执行具体操作。
解释:知识点1,正则匹配修饰符“~”和“~*”优先级相同,如果同时出现在配置中,按它们在配置中出现的顺序执行匹配。知识点2,一旦有一个正则匹配成功,则马上会终止匹配过程,所以会返回规则A。
------------------------------------------------------------------------------------------------
访问:curl http://www.test.com/user/Login/
URI:/user/Login/
返回:规则B
解释:因为把login的L改成大写,所以只能匹配不区分大小写的规则B(该测试只能在Linux下进行,因为macOS和windows都不区分大小写,如果在macOS和Windows下,该答案会是规则A)。
另注:虽然macOS可以设置成区分大小写,但它默认是不区分大小写的,并且绝大部分人不会把它设置成区分大小写的(除非对这个不懂,不小心设置了),否则一些软件会无法运行,例如Adobe家族软件,比如Photoshop,就无法运行,当然还有其它的某些软件。
把location规则换成这样,用于说明“最长前缀匹配”未使用^~
修饰时,正则字符串会参与匹配
location /user/ {
return 502 "规则A\n";
}
location /user/login/ {
return 502 "规则B\n";
}
location ~ /user/ {
return 502 "规则C\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则C
解释:“无修饰符”的location也是前缀匹配,当有多个这种前缀匹配都符合匹配条件时(比如本例有规则A和规则B两个),它会匹配最长的那个,即本例会匹配规则B,但因为它是“无修饰符”前缀匹配,虽然在匹配过程中对它进行了匹配,但还会继续进行正则匹配,由于正则字符串匹配到了规则C,所以最后返回规则C。
注意,不同修饰符修饰的location并不是按它在配置文件中出现的顺序匹配的,比如本例我把规则C放在最前面,它依然会先匹配规则A,再到规则B,最后才是规则C,所以即使我把规则C放在最前面,它最终匹配上的,也是规则C。
把location规则换成这样
location /user/ {
return 502 "规则A\n";
}
location /user/login/ {
return 502 "规则B\n";
}
location ~ /test/ {
return 502 "规则C\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则B
解释:还是按匹配优先级,先匹配前缀字符串(没有修饰符也属于前缀字符串),也就是先匹配A,再匹配B,它有多个前缀字符串与URI匹配上时,它会记录下最长的那个匹配(规则B),然后查看该“最长匹配”是否有用“^~”修饰,如果有,则马上终止匹配过程并选用该“最长匹配”,如果最长匹配没有用“^~”修饰,则匹配过程不会被终止,而是会暂时记下该“最长前缀匹配”,然后继续执行正则匹配,发现没有符合的正则匹配,于是使用前面记录下的最长的那个匹配,也就是最终会匹配到规则B。
把location规则改成这样,用于说明浏览器或curl会自动给无URI的请求添加一个斜杠,这样URI就变成了斜杠/
location ~ / {
return 502 "规则B\n";
}
location ^~ / {
return 502 "规则C\n";
}
location = / {
return 502 "规则A\n";
}
再来看看结果
访问:curl http://www.test.com 或 curl http://www.test.com/
URI:见解释
返回:都返回规则A
解释:“=”号修饰的是精确匹配,在本例中,按道理URI必须是“/”时才会匹配上,然而`curl http://www.test.com`时,最后并没有“/”结尾,为什么也能匹配上?能匹配上说明它的URI确实就是“/”,所以看上去,似乎是curl自动帮我们在最后添加了一个“/”,怎么验证呢?加个“-v”即可看出详细过程
> curl -v http://www.test.com
* Rebuilt URL to: http://www.test.com/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to www.test.com (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: www.test.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 502 Bad Gateway
< Server: openresty/1.17.8.2
< Date: Sun, 18 Oct 2020 12:42:52 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 8
< Connection: keep-alive
<
规则A
* Connection #0 to host www.test.com left intact
我们能清楚的看到这句话“Rebuilt URL to: http://www.test.com/”,确实是curl在最后自动添加了斜杠“/”,事实上你用浏览器访问任何一个网站,如果你是手动输入的网址,并且最后没有输入斜杠“/”,浏览器也会自动给你加上,但是大多数浏览器虽然加上了这个斜杠,它却不显示,可能是为了让你看上去更好看的原因吧,但是如果你把浏览器地址栏的URL复制出来,粘贴到编辑器里,就能看到,它确实在最后有一个斜杠“/”。
但是这种情况只会出现在只有域名,没有具体URI的时候,如果你加个URI,比如http://www.test.com/user/login,它是不会自动添加一个斜杠变成http://www.test.com/user/login/的,它会直接识别/user/login中的login是一个文件名,而如果你在最后加了斜杠,即这样/user/login/,则它会把login识别为目录名
把location改成这样,用于说明内嵌在前缀字符串location里的正则字符串location,在进行正则匹配时,会优先于未内嵌在前缀字符串location里的location
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则A\n";
}
# ~修饰符表示不区分大小写正则匹配
location ~* /user/Login/ {
return 502 "规则B\n";
}
# ^~修饰符表示前缀匹配
location ^~ /user/ {
return 502 "规则C\n";
}
# 无修饰符也表示前缀匹配
location /user/login/ {
location ~ /user/ {
return 502 "规则E\n";
}
return 502 "规则D\n";
}
再来看看结果
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则E
匹配过程:先执行前缀字符串匹配,发现规则C和规则D都能匹配上,于是看哪个更长,发现规则D更长,于是检查它是否有“^~”修饰,发现没有,于是执行正则匹配,执行正则匹配时,它会先把内嵌在规则D中的二级正则字符串location拿出来变成一级,可是拿出来放哪里呢?放在所有正则字符串location的前面,然后开始匹配,发现规则E能匹配上,于是最终选中规则E。
解释:规则E虽然在匹配文件后面,但由于它内嵌到了前缀字符串内部,所以在匹配正则字符串时,它会被放到所有正则的最前面,优先进行匹配。
把location改成这样,用于说明@
开头的location是命名location,并且貌似命名location只能被try_files
使用
# ~修饰符表示区分大小写正则匹配
location ~ /user/login/ {
return 502 "规则A\n";
}
# ~修饰符表示不区分大小写正则匹配
location ~* /user/Login/ {
return 502 "规则B\n";
}
# ^~修饰符表示前缀匹配
location ^~ /user/ {
return 502 "规则C\n";
}
# 无修饰符也表示前缀匹配
location /user/login/ {
# 规则E
location ~ /user/ {
try_files $uri @custom;
}
return 502 "规则D\n";
}
# @开头的location是命名location,它本身不参与匹配,只能被其它location使用,不能被内嵌到其它location中,也不能在它里面内嵌其它location
location @custom {
return 502 "规则F\n";
}
再看看结果:
访问:curl http://www.test.com/user/login/
URI:/user/login/
返回:规则F
匹配过程:先执行前缀字符串匹配,发现规则C和规则D都能匹配上,于是看哪个更长,发现规则D更长,于是检查它是否有“^~”修饰,发现没有,于是执行正则匹配,执行正则匹配时,它会先把内嵌在规则D中的二级正则字符串location拿出来变成一级,可是拿出来放哪里呢?放在所有正则字符串location的前面,然后开始匹配,发现规则E能匹配上,于是最终选中规则E,但是在执行规则E中的try_files时,发现$uri不存在,于是使用后面的“@custom”,而“@custom”是一个命名location,所以最终会执行规则E那个命名location,返回规则E。
特别注意:在正则匹配时,规则D中的内嵌规则会被提取出来,是因为规则D也匹配上了URI,如果规则D完全不匹配URI,那么它里面的正则字符串location是不会被提取出来并放到位于所有正则的最前面的。
关于location的相关知识,我参考了这篇文章:Understanding Nginx Server and Location Block Selection Algorithms。
if指令的使用规则
if指令属于ngx_http_rewrite_module(即rewrite模块),if必须写在server里面或location里面。
- 变量名:如果变量值是空字符串或“0”则为 FALSE。注意,在 1.0.1 版本之前,任何以“0”开头的字符串都会被当做 FALSE。
- 使用
=
和!=
的变量跟字符串的比较(注意,它并不像编程语言那样用两个==
号来判断是否相等,而是用一个=
号,加个感叹号就是不等于,并且字符串不需要用引号引起来,当然引起来也可以)。 - 使用
~
(区分大小写匹配)和~*
(不区分大小写匹配)运算符将变量与正则表达式匹配。正则表达式可以包含捕获,之后可以通过$1
到$9
这几个变量名重复使用。!~
和!~*
用作不匹配运算符。如果正则表达式包含}
或;
字符,则整个表达式应该用单引号或双引号括起来。
if 的条件可能是以下任何一种情况:
-f
和!-f
用来判断是否存在文件(f:file)
-d
和!-d
用来判断是否存在目录(d:directory)
-e
和!-e
用来判断是否存在文件或目录(e:exists)
-x
和!-x
用来判断文件是否可执行(x:execute)
if指令测试配置:
注意if跟后面的括号一定要有一个空格,否则报错,但右括号(
与左花括号{
之间可以不用空格。
server {
listen 80;
server_name www.test.com;
charset utf-8;
default_type text/html;
root /Users/bruce/www/personal/test;
access_log /usr/local/var/log/nginx/www.test.com.access.log;
error_log /usr/local/var/log/nginx/www.test.com.error.log;
autoindex on;
autoindex_exact_size off;
autoindex_format html;
autoindex_localtime on;
location / {
# 如果用户代理 User-Agent 包含"chrome",rewrite 请求到/chrome/目录下。通过正则匹配的捕获可以用 $1/$2 等使用
if ($http_user_agent ~ Chrome) {
rewrite ^([^/]*)$ /chrome$1 break;
}
# 如果cookie匹配正则,设置变量$id等于匹配到的正则部分
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
#为了证明是否设置成功,这里打印一下
#return 502 $id;
}
# 如果请求方法为 POST,则返回状态 405(Method not allowed)
if ($request_method = POST) {
return 405;
}
# 如果请求的文件存在,则开启缓存,并通过 break 停止后面的检查
if (-f $request_filename) {
expires max;
break;
}
# 如果请求的文件、目录或符号链接都不存在,则用 rewrite 在 URI 头部添加 /index.php
if (!-e $request_filename) {
echo $request_filename;
rewrite ^/(.*)$ /index.php break;
}
}
location ~ .php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
}
以上配置不需要先注释其他if只留一个,全部一起测试就可以,注意如果你没有cookie,可以用Edit Thiscookie
Chrome扩展自己添加一个cookieid=10010
用于测试,测试cookie那一块可以把return 502那个注释打开,否则无法测试出是否进入。
test目录的结构如下:
test
├── chrome
│ └── index.html
└── index.php
return、rewrite 和 try_files 指令
NGINX rewrite的两个通用指令是return
和rewrite
,而try_files
指令可以将请求定向到应用程序服务器。
return指令
return
指令简单高效,建议尽量使用return
,而不是rewrite
。return
指令放在 server 或 location 上下文中。语法很简单:
return code [text];
return code URL;
return URL;
将客户重定向到一个新域名的示例:
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com;
return 301 $scheme://www.new-name.com$request_uri;
}
上面代码中,listen 指令表明server 块同时用于 HTTP 和 HTTPS 流量。server_name指令匹配包含域名www.old-name.com的请求。return 指令告诉 Nginx 停止处理请求,直接返回 301 (Moved Permanently) 代码和指定的重写过的 URL 到客户端。$scheme
是协议(HTTP 或 HTTPS),$request_uri
是包含参数的完整的URI。
对于 3xx 系列响应码,url 参数定义了新的(重写过的)URL:
return (301 | 302 | 303 | 307) url;
对于其他响应码,可以选择定义一个出现在响应正文中的文本字符串(HTTP 代码的标准文本,例如 404 的 Not Found,仍包含在标题中)。文本可以包含 NGINX 变量。
return (1xx | 2xx | 4xx | 5xx) ["text"];
例如,在拒绝没有有效身份验证令牌的请求时,此指令可能适用:
return 401 "Access denied because token is expired or invalid";
rewrite指令
rewrite 规则会改变部分或整个用户请求中的 URL,主要有两个用途:
– 通知客户端,请求的资源已经换地方了。例如网站改版后添加了 www 前缀,通过 rewrite 规则可以将所有请求导向新站点。
– 控制 Nginx 中的处理流程。例如当需要动态生成内容时,将请求转发到应用程序服务器。try_files 指令经常用于这个目的。
但是,如果需要测试 URL 之间更复杂的区别,或者要从原始 URL 中捕获的元素没有对应的 NGINX 变量,或者更改或添加路径中的元素(例如各大 PHP 框架常用的 index.php 入口文件),该怎么办? 可以使用 rewrite 指令。
rewrite 指令放在 server 或 location 上下文中。语法很简单:
rewrite regex URL [flag];
第一个参数 regex 是正则表达式。
flag 标志位
last
停止处理当前的ngx_http_rewrite_module指令集,并开始对匹配更改后的 URI 的新 location 进行搜索(再从 server 走一遍匹配流程)。此时对于当前 server 或 location 上下文,不再处理 ngx_http_rewrite_module 重写模块的指令。相当于Apache里的[L]标记。
break
终止匹配, 不再匹配后面的规则
redirect
返回包含 302 代码的临时重定向,地址栏会显示跳转后的地址,在替换字符串不以http://
、https://
或$scheme
开头时使用。
permanent
返回包含 301 代码的永久重定向。
last和break就相当于php里的continue和break。
rewrite 指令只能返回代码 301 或 302。要返回其他代码,需要在 rewrite 指令后面包含 return 指令。
rewrite 指令不一定会暂停 NGINX 对请求的处理,因为它不一定会发送重定向到客户端。除非明确指出(使用 flag 或 URL 的语法)你希望 NGINX 停止处理或发送重定向,否则它将在整个配置中运行,查找在重写模块中定义的指令(break、if、return、rewrite 和 set),并按顺序处理。如果重写的 URL 与 Rewrite 模块中的后续指令匹配,NGINX 会对重写的 URL 执行指定的操作(通常会重新写入)。
这是复杂的地方,必须仔细计划指令顺序以获得期望的结果。例如,如果原始 location 块和其中的 NGINX 重写规则与重写的 URL 匹配,NGINX 可以进入一个循环,Nginx 默认限制循序最大 10 次。
下面是使用 rewrite 指令的 NGINX 重写规则的示例。它匹配以字符串 /download 开头的 URL,然后用 /mp3/ 替换在路径稍后的某个位置包含的 /media/ 或 /audio/ 目录,并添加适当的文件扩展名.mp3
或.ra
。$1
和$2
变量捕获不变的路径元素。例如,/download/cdn-west/media/file1 变为 /download/cdn-west/mp3/file1.mp3。如果文件名上有扩展名(例如.flv),表达式会将其剥离并用.mp3替换。
server {
# ...
rewrite ^(/download/.*)/media/(w+).?.*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(w+).?.*$ $1/mp3/$2.ra last;
return 403;
# ...
}
可以将 flag 添加到重写指令来控制处理流程。示例中的last告诉NGINX跳过当前服务器或位置块中的任何后续ngx_http_rewrite_module重写模块的指令,并开始搜索与重写的URL匹配的新位置。
这个例子中的最后一个 return 指令意味着如果 URL 不匹配任何一个 rewrite 指令,将返回给客户端 403 代码。
try_files 指令
try_files 指令也放在 server 或 location 上下文中。语法很简单:
# file也可以目录
try_files file1 file2 … filen uri;
# code是http状态码,比如=404,则会在找不到文件时返回404
try_files file1 file2 … filen =code;
try_files 指令的参数是一个或多个文件或目录的列表,以及最后面的URI参数。
Nginx 会按顺序检查文件及目录是否存在(根据root和alias指令设置的参数构造完整的文件路径),并用找到的第一个文件提供服务(其实就是读取该文件的内容并返回给浏览器)。在参素名后面添加斜杠/
表示这个是目录。如果文件和目录都不存在,Nginx会执行内部重定向,跳转到命令的最后一个uri
参数定义的 URI 中。
要想try_files指令工作,必须定义一个location块捕捉内部重定向。最后一个参数可以是命名过的location,由初始符号@
指示。
try_files 指令通常使用$uri
变量,表示URL中域名之后的部分。
下面示例中,如果客户端请求的文件不存在,Nginx 会响应一个默认的 GIF 文件。假设客户请求http://www.domain.com/images/image1.gif
,Nginx 会首先通过用于这个 location 的 root 和 alias 指令,在本地目录中查找这个文件。如果“image1.gif”文件不存在,Nginx 会查找image1.gif/
目录,如果都不存在,会重定向到/images/default.gif
。这个值精确匹配后面的location指令,因此处理过程停止,Nginx返回这个文件,并标注其缓存30秒。
location /images/ {
try_files $uri $uri/ /images/default.gif;
}
location = /images/default.gif {
expires 30s;
}
try_files常用到的变量
$args
请求url中?
后面的参数(不包括问号本身)
$is_args
如果$args
不为空,则$is_args
就是一个?
号,如果$is_args
为空,则$is_args
也为空字符串,所以你会经常看见有这样的写法:
try_files $uri $uri/ /index.php$is_args$args;
而有些人会写成这样:
try_files $uri $uri/ /index.php?$args;
其实第二种写法肯定是不好的,因为万一$args
为空,那么就留下了一个无用的问号,而用$is_args
则能避免这个问题。
$query_string
与$args
相同。
$document_root
root指令指定的值。
$request_filename
当前连接请求的文件路径,由root或alias指令与URI请求生成(不包住参数)。
$request_uri
原始请求uri(即url中除去协议和域名的部分,比如:http://127.0.0.1/index.php的uri即为/index.php
)。
nginx官方文档中说明的内置变量
location:http://nginx.org/en/docs/http/ngx_http_core_module.html#location。
在nginx官网中点击右侧的documentation
然后搜索:Modules reference,找到ngx_http_core_module,点进去(因为我们配置的是http服务器,所以我们需要的所有指令都在这个模块里面)
然后搜索:Embedded Variables,点进去,然后你就可以看到所有nginx http模块的的内置变量了:
Embedded Variables
The ngx_http_core_module module supports embedded variables with names matching the Apache Server variables. First of all, these are variables representing client request header fields, such as $http_user_agent
, $http_cookie
, and so on. Also there are other variables:
$arg_name
argument name in the request line
$args
arguments in the request line
$binary_remote_addr
client address in a binary form, value’s length is always 4 bytes for IPv4 addresses or 16 bytes for IPv6 addresses
$body_bytes_sent
number of bytes sent to a client, not counting the response header; this variable is compatible with the “%B” parameter of the mod_log_config Apache module
$bytes_sent
number of bytes sent to a client (1.3.8, 1.2.5)
$connection
connection serial number (1.3.8, 1.2.5)
$connection_requests
current number of requests made through a connection (1.3.8, 1.2.5)
$content_length
“Content-Length” request header field
$content_type
“Content-Type” request header field
$cookie_name
the name cookie
$document_root
root or alias directive’s value for the current request
$document_uri
same as $uri
$host
in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
$hostname
host name
$http_name
arbitrary request header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$https
“on” if connection operates in SSL mode, or an empty string otherwise
$is_args
“?” if a request line has arguments, or an empty string otherwise
$limit_rate
setting this variable enables response rate limiting; see limit_rate
$msec
current time in seconds with the milliseconds resolution (1.3.9, 1.2.6)
$nginx_version
nginx version
$pid
PID of the worker process
$pipe
“p” if request was pipelined, “.” otherwise (1.3.12, 1.2.7)
$proxy_protocol_addr
client address from the PROXY protocol header, or an empty string otherwise (1.5.12) .The PROXY protocol must be previously enabled by setting the proxy_protocol parameter in the listen directive.
$proxy_protocol_port
client port from the PROXY protocol header, or an empty string otherwise (1.11.0)
The PROXY protocol must be previously enabled by setting the proxy_protocol parameter in the listen directive.
$query_string
same as $args
$realpath_root
an absolute pathname corresponding to the root or alias directive’s value for the current request, with all symbolic links resolved to real paths
$remote_addr
client address
$remote_port
client port
$remote_user
user name supplied with the Basic authentication
$request
full original request line
$request_body
request body
The variable’s value is made available in locations processed by the proxy_pass, fastcgi_pass, uwsgi_pass, and scgi_pass directives when the request body was read to a memory buffer.
$request_body_file
name of a temporary file with the request body
At the end of processing, the file needs to be removed. To always write the request body to a file, client_body_in_file_only needs to be enabled. When the name of a temporary file is passed in a proxied request or in a request to a FastCGI/uwsgi/SCGI server, passing the request body should be disabled by the proxy_pass_request_body off, fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off directives, respectively.
$request_completion
“OK” if a request has completed, or an empty string otherwise
$request_filename
file path for the current request, based on the root or alias directives, and the request URI
$request_id
unique request identifier generated from 16 random bytes, in hexadecimal (1.11.0)
$request_length
request length (including request line, header, and request body) (1.3.12, 1.2.7)
$request_method
request method, usually “GET” or “POST”
$request_time
request processing time in seconds with a milliseconds resolution (1.3.9, 1.2.6); time elapsed since the first bytes were read from the client
$request_uri
full original request URI (with arguments)
$scheme
request scheme, “http” or “https”
$sent_http_name
arbitrary response header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$sent_trailer_name
arbitrary field sent at the end of the response (1.13.2); the last part of a variable name is the field name converted to lower case with dashes replaced by underscores
$server_addr
an address of the server which accepted a request
Computing a value of this variable usually requires one system call. To avoid a system call, the listen directives must specify addresses and use the bind parameter.
$server_name
name of the server which accepted a request
$server_port
port of the server which accepted a request
$server_protocol
request protocol, usually “HTTP/1.0”, “HTTP/1.1”, or “HTTP/2.0”
$status
response status (1.3.2, 1.2.2)
$tcpinfo_rtt
,$tcpinfo_rttvar
, $tcpinfo_snd_cwnd
, $tcpinfo_rcv_space
information about the client TCP connection; available on systems that support the TCP_INFO socket option
$time_iso8601
local time in the ISO 8601 standard format (1.3.12, 1.2.7)
$time_local
local time in the Common Log Format (1.3.12, 1.2.7)
$uri
current URI in request, normalized
The value of $uri
may change during request processing, e.g. when doing internal redirects, or when using index files.
其他的可参考:Nginx 基本功能 – 将 Nginx 配置为 Web 服务器(HTTP Server)
相关文章:Nginx之Location匹配规则
你好。我想匹配类似http://ip:port/*.apk这样的请求,应该怎么写呢?
~* .(apk)$