Nginx的location/if/return/rewrite/try_files指令及flag标志位的使用

Nginx的location/if/return/rewrite/try_files指令及flag标志位的使用

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里面的指令。

^~无修饰符都叫“前缀匹配”,它们之间没有优先级关系,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的规则,并且该规则内部嵌套有正则匹配,则该嵌套的正则匹配会位于所有正则匹配的前面(即使该非正则location在配置文件中的位置在最后)。

匹配步骤:

  1. 用所有的前缀字符串去匹配URI(这里说的前缀字符串,是除~~*修饰的字符串外的所有字符串,包括=^~无修饰符三种都算是前缀字符串);
  2. 如果找到=号修饰的字符串与URI匹配(无论它在哪个位置),则终止匹配过程,并进入该location内部执行具体操作;
  3. 如果不找到=号修饰的字符串,则继续匹配普通前缀字符串(即用^~修饰的或无修饰符的字符串);
  4. ^~修饰的或无修饰符的字符串中如果有多个规则能匹配上URI,则会查看最长的那一个是否用^~修饰;
  5. 如果最长的那个有用^~修饰,则终止匹配,这个用^~修饰的“最长”的匹配就是最终被选用的location,但是如果这个“最长”的匹配没有用^~修饰(说明它是无修饰符的),则会暂时把它存储下来,但不会终止匹配,而是继续进行后面的正则匹配;
  6. 开始正则匹配,匹配所有用~~*修饰的字符串
  7. 匹配到第一个正则表达式后终止匹配,使用对应的location;
  8. 如果没有匹配到正则表达式,则使用之前存储的前缀字符串对应的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 ThiscookieChrome扩展自己添加一个cookieid=10010用于测试,测试cookie那一块可以把return 502那个注释打开,否则无法测试出是否进入。

test目录的结构如下:

test
├── chrome
│   └── index.html
└── index.php

return、rewrite 和 try_files 指令

NGINX rewrite的两个通用指令是returnrewrite,而try_files指令可以将请求定向到应用程序服务器。

return指令

return指令简单高效,建议尽量使用return,而不是rewritereturn指令放在 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

Xnip2019-01-02_01-35-08.jpg

然后搜索:Modules reference,找到ngx_http_core_module,点进去(因为我们配置的是http服务器,所以我们需要的所有指令都在这个模块里面)

Xnip2019-01-02_01-47-07.jpg

然后搜索:Embedded Variables,点进去,然后你就可以看到所有nginx http模块的的内置变量了:

Xnip2019-01-02_01-46-28.jpg

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匹配规则

打赏

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
max
max
4 months ago

你好。我想匹配类似http://ip:port/*.apk这样的请求,应该怎么写呢?

borisz
borisz
4 months ago
Reply to  max

~* .(apk)$

0
Would love your thoughts, please comment.x
()
x

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

Nginx的location/if/return/rewrite/try_files指令及flag标志位的使用