记录一个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的坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

数字乌托邦

数字乌托邦

[美]弗雷德·特纳 / 张行舟、王芳、叶富华、余倩 / 译言·东西文库/电子工业出版社 / 2013-5-1 / 49.80元

20世纪60年代早期,在美国大众眼中,计算机只是冷战中冰冷的机器,然而到了90年代互联网到来之时,计算机却呈现出一个截然不同的世界——它们模拟出了一个数字乌托邦般的协同体,而这正是曾经最反对冷战的嬉皮士们的共同愿景。 本书正是探索这次非同寻常,且颇具讽刺意味的变革的第一本书。作者挖掘出那些在旧金山湾区的先驱者——斯图尔特·布兰德和他的“全球网络”鲜为人知的故事。1968年到1998年期间,通......一起来看看 《数字乌托邦》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

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

正则表达式在线测试