nginx 路由杂谈 - rewrite 指令与重定向
在书写 nginx 路由规则的时候, 得保证规则配置的规范, 否则维护起来成本很高。
本文简要地讨论了在 rewrite 模块的基础实现的简单路由规则,并解释了常用指令的使用细节。
rewrite 与 location
首先,这是一个不完整 nginx 虚拟主机配置:
1 | service { |
上面的例子中, 指定的类型可以分为两种。
server
,root
,index
,location
是ngx_http_core_module
提供的指令- 而
rewrite
则是ngx_http_rewrite_module
提供的指令之一, 同类的还有常用的if
简单地概括, nginx 的处理配置流程如下
- 进行 rewrite 规则匹配,根据命中规则改写 location。
- 如果没有被其他指令中断并退出, 则进入 location 匹配。
- 执行匹配成功的 location 区块中的指令。
而对于 rewrite 系列指令和 location 指令的安排, 在书写配置的时候, 应该先定义好 location , 因为 location 是和 nginx 和后端服务沟通的桥梁。
再根据 location 的需要, 使用 rewrite 将各种各样用户输入的 url 正确地映射到 对应 locaton , 由 location 中的 proxy 指令转发给后端。
比如一个常规的 php 站点,有以下类似的 location 配置。
1 | # 静态资源 |
注意, 这份配置省略了不必要的指令,能够满足大部分的 php 应用的需要。
有时候,为了优化 url 的展示,或者兼容旧应用的 url 规则, 只是这么简单的两条 location 配置自然是不够用的。这时候我们可以通过 rewrite
规则进行弥补。
1 | rewrite /index.html$ /app/page-default; //兼容旧 url |
请注意这两条 rewrite
规则区别的在最后一个 flag
参数 break
可用的 flag 参数还有 `permanent`, `redirect`, `last` 等, 后面分析它们之间的差别。
多出来的 break
参数作用会使得 rewirte
指令的跳转方式发生变化:
- 没有 flag 的 rewrite 指令完成 location 改写之后 ,继续往下寻找其他
rewrite
规则, 看看有没有符合要求的。如果没有, 那么进入location
匹配。 - 带
break
, 跳过其他所有rewrite
规则, 进入location
匹配
这里简单总结一下:
- 如果 某一条
rewrite
规则命中直接转发给后端应用,那么应该加上一个break
标记。 - 如果
rewrite
规则起补充作用,还需要其他规则配合完成, 那么不带第三个的flag
参数。
以上说的 rewrite
规则属于 nginx 的内部重定向规则, 也就是说, 用户外部看到的 url 依然是他输入的 url , 而转给后端应用的 $uri
, 则已经是 nginx 改写之后的结果。
如果需要进行显式地外部重定向, 需要借助 redirect
, permanent
这两个 flag 进行 302 和 301 重定向.
它的行为和 break
类似, 区别在于 nginx 会中断流程, 通过 http 请求告诉用户端进行重定向,
也就是这次请求不需要进过后端服务, 由 nginx 全职负责。
rewrite /error.html$ /error2.html redirect;
break 和 last 区别
这两个 flag 都会中断当前 rewrite 流程, 不再继续匹配后续的 rewrite 指令。
wiki 上是这么写的
stops processing the current set of
ngx_http_rewrite_module
directives and starts a search for a new location matching the changed URI
如果是在 server
的顶级部分, 那么它们的作用是相同, 跳过剩下的 rewrite 指定, 进入 location 匹配。
如果 rewrite 是在 server
区块顶级 if 内部, 和直接放在 server 下级的 rewrite 行为是一致的。
1 | server { |
以上的 rewrite 的跳转行为是相同的, 进入 location 匹配流程。
而两者的区别, 在于当 rewrite 指令存在于 location
区块中时, 见例子
1 | location = index.php { |
第一个 location , 如果 q
参数为空, 那么将展示错误页面。
第二个 location , 如果 test
参数不为空, 通过 rewrite 规则,使用另外一个 error-test.html,
但 location 不变
注意, 这里并没有一个 `location = error2.html` 的匹配规则
从这个 case 的结果, 可以明显得区分两个指令之间的细微差别
- last 跳出 location 块, 重新进行 location 匹配
- break 跳过 location 下的后续 rewrite 规则, 执行其他指令。
所以 last 的特殊在于重新进行 location 匹配, 这也就是为什么会从
locaton = index.php
转向
locaton = error.html
所以一般情况下, 使用 break
指令会相对安全, 不会造成循环重定向。
比如:
1 | location = error.html { |
1 | location = error.html { |
第一个例子,完成了 rewrite 指令之后,break 指令使得后续的其他 rewrite 规则失效, 接着进行 proxy.
第二个例子会导致不断地进行 location 匹配, 最终导致 nginx 返回 500.
返回结果是这样的:
1 | HTTP/1.1 500 Internal Server Error |
注:nginx 会记录同一条 rewrite 规则的执行次数,如果超过10次,将自动触发 500 进行自我保护。
小结
这里构造了两组例子用来说明 last
和 break
两个 flag 参数行为上的不一致。
并不是说 rewrite 规则在 server 和 location 上下文中的行为不一致, 而是他们的行为特征很容易造成误解。
两者的本质区别在于是否重新进行 location
匹配,所以当在 location 上下文 进行 last rewrite时。
对于不熟悉 rewrite 指定的其他人容易造成误解。
所以还是前文所提到的观点, 尽可能地将 rewrite 和 location 离开来。 在 location 中进行 rewrite, 容易造成重定向问题。
由于 rewrite 模块的 `rewrite` 和 `if` 指令会使得 nginx
的路由规则出现较多的逻辑和分支跳转, 在维护性上是比较糟糕的,
并不推荐过多地进行使用, 本文只是从行为和特性上分析了这些指令,
并不代表支持过度使用 rewrite 指令。
return 指令的应用
在 rewrite
模块中, 有一条非常有用的指令 return
, 用于直接返回客户端指定的状态码。
甚至支持指定文本内容和url, 相比起使用 rewrite
指令302进行曲线救国,要简便地多。
1 | location = /index.php { |
对于常规的基于 url 提供服务的应用, 基础的 rewrite 指令配合已经足够完成大部分任务。
下一篇文章再聊聊基于条件,变量和更加复杂的上下文, 完成进行路由规则匹配.