记录一个nginx rewrite的坑

栏目: 服务器 · Nginx · 发布时间: 7年前

内容简介:记录一个nginx rewrite的坑

nginx很重要也很常用的一个功能就是rewrite,经常被用于实现伪静态SEO、升级迁移等各类运维需求。

今天专门学习了一下相关的配置方法,着实被坑了一顿,因此记录在这里。

基础知识

关于nginx rewrite配置的原理和方法,可以参考 这篇博客

配置执行顺序

概括的来说,nginx收到请求后会找到对应vhost的server配置开始执行解析,其执行顺序如下:

  • 执行server块的rewrite指令:当然也包括其他指令,在这个过程中会跳过location指令的解析。
  • 执行location匹配:当server块内的指令解析完后,会开始解析所有的location指令,也就是获取它们的匹配规则(注意不是执行location内的代码),得到全部location的匹配规则后,会按照一个优先级开始对URI进行规则匹配:
    • =前缀的指令严格匹配这个查询。如果找到,停止搜索。
    • 所有剩下的常规字符串,字符串长的优先匹配。如果这个匹配使用^〜前缀,搜索停止。
    • 正则表达式,在配置文件中定义的顺序进行逐一匹配。
    • 如果第3条规则产生匹配的话,结果被使用。否则,使用第2条规则的结果。
  • 执行选定的location中的rewrite指令:经过上面的步骤就选定location了,接下来做的就是执行这个location内的代码即可(与其他location再无瓜葛)。当然,location内的全部指令都会被执行,不仅仅是rewrite。

关于rewrite的last和break

概括的说,它们具有如下的相同点:

break和last并不会阻止代码继续向下执行,而是跳过后面其他的rewrite指令,相当于视而不见。

它们具有如下的区别:

last不仅会继续执行后面的代码,而且当执行完毕后会重置nginx的解析状态,以重写后的URI为准重新进行Nginx匹配,就像一个新的请求一样。而break与last相比,只是缺少了这个行为而已。

当然,rewrite可以不跟随break、last,这种情况下后面的代码也将继续执行,并且rewrite也一样会被执行。

实例演示

事实上,nginx rewrite的break和last行为还有它的”阴暗面”,网上没有资料说明这一块,下面一起看个例子。

log_format myformat 'is_rewrite=' $is_rewrite;
 
server {
 listen 9999;
 
 rewrite_log on;
 
 access_log /Users/webroot/access.log myformat;
 error_log /Users/webroot/error.log notice;
 
 root /Users/webroot/;
 
 location /break/ {
 # to_test直接访问test目录
 if ($uri ~ ^/break/to_test/(.*)) {
 rewrite ^/break/to_test/(.*) /$1 break;
 root /Users/webroot/test;
 }
 # to_last改写到last,重走匹配
 rewrite ^/break/to_last/(.*) /last/$1 last;
 # uri被重写, 则设置一个变量
 if ($uri !~ ^/break/(.*)) {
 set $is_rewrite 1;
 }
 }
 
 location /last/ {
 rewrite ^/last/(.*) /test/$1 last;
 }
 
 location /test/ {
 
 }
}

这是我设计的一个场景。

磁盘上有2个目录,它们里面的x文件内容不同:

liangdong:Documents webroot$ cat break/x
break
liangdong:Documents webroot$ cat test/x
test

然后,我们逐条看一下nginx配置的目的:

  • log_format:配置了一个access log的格式,命名为myformat。
  • server:一个vhost,监听在9999端口。
  • rewrite_log:on,将rewrite模块的日志输出打开,便于调试。
  • access_log:这个vhost的access log使用myformat格式。
  • error_log:错误日志和rewrite日志都会写到这个文件。
  • root:这个vhost的文档目录。
  • location /break:如果访问/break/x将匹配该location指令。
  • location /last:如果访问/last/x将匹配该location指令。
  • location /test:如果访问/test/x将匹配该location指令。

这里回顾一下”配置执行顺序”部分讲解的流程,在URI匹配得到对应的location后,将执行location内的指令,并结束请求。

接下来,我们逐个location来讲解,分别看看匹配时它们会做什么。

  • 请求:/test/x
  • 匹配:location /test
  • 执行:内部指令是空,那么location立即执行完成。因为在server层指定了root,所以nginx会去/Users/webroot/下查找test/x文件,返回内容为test。
  • 请求:/last/x
  • 匹配:location /last
  • 执行:rewrite将/last/x重写到了/test/x,最后的last会导致nginx重新解析请求,新的URI是/test/x,这次解析将会匹配location /test,也就是执行上一条规则内的指令,请求最终结束。
  • 请求:/break/x
  • 匹配:location /break
  • 执行:首先演示了if正则的用法,显然/break/x不匹配^/break/to_test/(.*)规则,因此不进入If。同样,接下来的rewrite指令^/break/to_last/(.*)也未能匹配,因此跳过继续执行。最后执行if语句,/break/x可以匹配^/break/(.*)因此不会进入if(这个if判断的是不等于!~)。至此,该location结束执行,也就是什么事情也没做,会使用server中的root作为查找路径,最终在/Users/webroot/的break/x路径下找到文件,输出为”break”。
  • 请求:/break/to_test/x
  • 匹配:location /break
  • 执行:这次第一个If语句成功匹配,if内的rewrite可以成功执行,将/break/to_test/x重写至了/x,break将导致该rewrite之后的所有rewrite将被忽略,但代码将继续向下执行(这很重要),因此接下来的root指令会得到执行,root被直接指向了/Users/webroot/test目录,其覆盖了server中定义的默认root。接下来遇到第二个rewrite指令时将直接跳过(因为之前的break)。 按照规则,接下来的if语句应该可以得到执行,并且也应满足条件,is_rewrite变量将会被设置为1,然而现实却是if没有执行,”坑”来了!

网上的资料都会说break/last之后的rewrite指令不会被执行,但是为什么导致了if不执行呢?其实,这种说法是”以讹传讹”的错误结论,按照官方的说法break和last终止的是 ngx_http_rewrite_module模块的执行,这个模块负责了像if、set、return、rewrite这些语法的解析和执行,因此不仅仅是rewrite失效,后续出现的这些语法都会失效!

官方文档地址: http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#break ,break的相关说明:

Stops processing the current set of ngx_http_rewrite_module directives.

If a directive is specified inside the location , further processing of the request continues in this location.

注意,停止的是the current set of ngx_http_rewrite_module 的执行,而不是Stops processing。因此,指令将继续下来执行,而属于ngx_http_rewrite_module的指令将被跳过。

这里贴一下rewrite日志,看一下究竟。

首先是请求/break/x:

2017/06/12 17:55:14 [notice] 29778#0: *790 "^/break/to_test/(.*)" does not match "/break/x", client: 127.0.0.1, server: , request: "GET /break/x HTTP/1.1", host: "localhost:9999"
2017/06/12 17:55:14 [notice] 29778#0: *790 "^/break/to_last/(.*)" does not match "/break/x", client: 127.0.0.1, server: , request: "GET /break/x HTTP/1.1", host: "localhost:9999"
2017/06/12 17:55:14 [notice] 29778#0: *790 "^/break/(.*)" matches "/break/x", client: 127.0.0.1, server: , request: "GET /break/x HTTP/1.1", host: "localhost:9999"

第1行日志代表了第一个If和/break/to_test/(.*)匹配失败,第2行日志代表了第二个rewrite /break/to_last/(.*)匹配失败,第3行日志判断!~ ^/break/(.*)得到执行但条件并没有成立,这是因为前面的rewrite都没有匹配成功,last和break并没有生效,因此if指令可以被ngx_http_rewrite_module模块正常执行。

接下来请求/break/to_test/x:

2017/06/12 17:57:49 [notice] 29778#0: *792 "^/break/to_test/(.*)" matches "/break/to_test/x", client: 127.0.0.1, server: , request: "GET /break/to_test/x HTTP/1.1", host: "localhost:9999"
2017/06/12 17:57:49 [notice] 29778#0: *792 "^/break/to_test/(.*)" matches "/break/to_test/x", client: 127.0.0.1, server: , request: "GET /break/to_test/x HTTP/1.1", host: "localhost:9999"
2017/06/12 17:57:49 [notice] 29778#0: *792 rewritten data: "/x", args: "", client: 127.0.0.1, server: , request: "GET /break/to_test/x HTTP/1.1", host: "localhost:9999"

第1行日志代表了第一个If匹配成功,因此进入了if。第2行日志代表了if内的rewrite匹配成功(指定了break),因此第3行日志打印URI被改写为/x。接下来还有1个rewrite /break/to_last/(.*)并没有打印match日志,后续的if也没有打印,说明rewrite的break生效了,符合预期。

接下来请求/break/to_last/x:

2017/06/12 18:03:57 [notice] 29778#0: *794 "^/break/to_test/(.*)" does not match "/break/to_last/x", client: 127.0.0.1, server: , request: "GET /break/to_last/x HTTP/1.1", host: "localhost:9999"
2017/06/12 18:03:57 [notice] 29778#0: *794 "^/break/to_last/(.*)" matches "/break/to_last/x", client: 127.0.0.1, server: , request: "GET /break/to_last/x HTTP/1.1", host: "localhost:9999"
2017/06/12 18:03:57 [notice] 29778#0: *794 rewritten data: "/last/x", args: "", client: 127.0.0.1, server: , request: "GET /break/to_last/x HTTP/1.1", host: "localhost:9999"
2017/06/12 18:03:57 [notice] 29778#0: *794 "^/last/(.*)" matches "/last/x", client: 127.0.0.1, server: , request: "GET /break/to_last/x HTTP/1.1", host: "localhost:9999"
2017/06/12 18:03:57 [notice] 29778#0: *794 rewritten data: "/test/x", args: "", client: 127.0.0.1, server: , request: "GET /break/to_last/x HTTP/1.1", host: "localhost:9999"

第1行日志说明If /break/to_test/(.*)匹配失败,因此没有进入If。第2行日志说明rewrite /break/to_last/(.*)匹配成功(指定了last),因此第3行日志打印URI被改写为/last/x。代码继续向下执行到最后一个If,因为之前rewrite last的原因If将不会被执行,因此没有相关日志打印。location执行完成,因为last原因URI被Nginx重新执行解析,这次匹配了location /last/,其内部的rewrite直接将/last/x重写为/test/x,因为其last的原因又再次被nginx重新执行解析。最终,与location /test匹配,直接读取server中的root路径/Users/webroot下的/test/x文件,请求结束。

掌握了rewrite可以做很多事情,玩的愉快。


以上所述就是小编给大家介绍的《记录一个nginx rewrite的坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Data Structures and Algorithm Analysis in Java

Data Structures and Algorithm Analysis in Java

Mark A. Weiss / Pearson / 2011-11-18 / GBP 129.99

Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course wa......一起来看看 《Data Structures and Algorithm Analysis in Java》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具