nginx 重写规则简单实践

什么是 nginx 的重写(rewrite)规则

nginx的重写模块是一个简单的正则表达式匹配与一个虚拟堆叠机结合。依赖于PCRE库,因此需要安装pcre。根据相关变量重定向和选择不同的配置,从一个location跳转到另一个location,不过这样的循环最多可以执行10次,超过后nginx将返回500错误。同时,重写模块包含set指令,来创建新的变量并设其值,这在有些情景下非常有用的,如记录条件标识、传递参数到其他location、记录做了什么等等。

nginx重写规则的语法主要包括这几个关键字

  • set

set主要是用来设置变量

语法:set variable value

  • if

if主要用来判断一些在rewrite语句中无法直接匹配的条件,比如检测文件存在与否,http header,cookie等,

可以和系统默认变量联合判断条件

语法: if(条件) {…}, 当if表达式中的条件为true,则执行if块中的语句

判断的条件可以有以下值:
1. 一个变量的名称:空字符传”“或者一些“0”开始的字符串为false。
2. 字符串比较:使用=或!=运算符
3. 正则表达式匹配:使用~(区分大小写)和~*(不区分大小写),取反运算!~和!~*。
4. 文件是否存在:使用-f和!-f操作符
5. 目录是否存在:使用-d和!-d操作符
7. 文件、目录、符号链接是否存在:使用-e和!-e操作符
8. 文件是否可执行:使用-x和!-x操作符

if 中可以使用的变量参见:nginx rewrite 参数和例子

举个很经常被举的例子:

 //如果UA包含”MSIE”,rewrite 请求到/msie目录

  • return

直接返回 HTTP 状态码,可以返回204,400,402-406,408,410, 411, 413, 416与500-504但是不可用 return 返回301和302

  • break

功能同标志位的 break

  • rewrite

rewrite 命令是重写规则的执行部分,语法:rewrite  用于匹配的正则表达式  替换后的内容  标志位

匹配表达式:简单地说,就是用半角括号()包含的部分中将被正则匹配并替换,在后面的替换段中使用$1/$2···表示被正则匹配并代替后的字符串。

有几个注意点:

1,匹配只对应请求字段,即不包括 http://头和域名部分,也就是说如果写正则的时候是包括了域名甚至是把 http://也加进去是永远不会生效的。

2,注意rewrite 所处的字段,是 server 还是 location,sever区块中如果有包含rewrite规则,则会最先执行,而且只会执行一次, 然后再判断命中哪个location的配置,再去执行该location中的rewrite, 当该location中的rewrite执行完毕时,rewrite并不会停止,而是根据rewrite过的URL再次判断location并执行其中的配置。

3,有个 last 和 break 的区别值得注意,break是终止当前location的rewrite检测,而且不再进行location匹配,而 last是终止当前location的rewrite检测,但会继续重试location匹配并处理区块中的rewrite规则。

关于重写规则,核心是正则表达式,推荐一篇很好的正则表达式教程 正则表达式30分钟入门 。

关于location 的判断、标志位last 和 break 的选择、如何停止rewrite,参见 nginx rewrite 研究笔记 简单的说就是在一个 location 大括号里,break 将停止此请求的所有location匹配,而 last 只表示停止此次重写。引用上面那位作者的话说:

如果是全局性质的rewrite,最好放在server区块中并减少不必要的location区块.location区块中的rewrite要想清楚是用last还是break.

实例1,使用HTTP 301 强制将域名直接访问跳转到 https:

这是我的一个自用在线代理的配置里截的一段,由于代理的主要目的就是在不方便使用 VPN 和 shadowsocks 的地方访问一些比较和谐的网站,不加 https 简直作死分分钟被关键字拦截的节奏 。这个实例中因为 rewrite 实际不匹配协议的前缀和域名,实际上^匹配的开头是从域名后的请求开始的,所以在重写结果里需加上 server_name 。

实例2,不带 www域名自动跳转www 及自动跳转到网站子目录下

情况简述:现有域名 (用XXXXX代替),www 和@同时指向一个 IP 下,需要将非 www 的访问跳转到 www 域名,由于某些更新,主目录更改到原目录的子目录下

暂且不考虑 www 的问题,对于跳转子目录我尝试过这样的错误配置:

这样会非常蛋疼低造成重定向循环,因为重写后生成的请求地址依然是符合重写规则的,此处 last 和 break 都无效,因为对 nginx 而言重写结束后的请求又是一个新的请求,他会重新检查是否符合配置文件下的规则。此时浏览器会报错过多的重定向而不会跳 HTTP 500,nginx 本身的限制10次重写上限此处没有生效(大概是因为 nginx 本身的检测仅限一个 rewrite 语法执行中的次数),在地址栏里也会看到蛋疼的一大串子目录·······

可以使用的正确的姿势应该是:

使用 permanent 标记返回301,将所有访问转到 www 域名下,然后利用 nginx 本身的变量判断访问的是不是根目录(实际上就起到了判断有没有被重定向过的作用),然后只将对根目录的访问进行重写,这里的 break 实际上可以不写,因为不会循环。

不过感觉这样的配置不是很妥当,没有做安全相关的考虑也没考虑很多情况。

 实例3,使用 rewrite 优化 mediawiki

mediawiki 是广泛使用的一款 Wiki 框架,但是在默认状态下的 URL 比较难看(如图):

QQ20141123-1@2x

想实现直接在域名后输入词条名称进入词条,可以使用 rewrite。

这里有个问题,就是 rewrite 判断 ^/(.*)$ 的时候这个/不能少,否则输入 XXX/YYY请求的页面实际上是/index.php?title=/YYY,也就是实际查找的词条前面多了个/,这里就需要注意前面如果不带/,则在替换段里要加/,如实例2中的

但是这里如果把 / 加到后面,$arg 可能会没法正常使用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

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