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 参数和例子

举个很经常被举的例子:

if ($http_user_agent ~ MSIE) {
  rewrite  ^(.*)$  /msie/$1  break;
}

 //如果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:

server {
        listen 80;
        server_name XXXXXX;
        rewrite ^(.*) https://$server_name$1 permanent;
}

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

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

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

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

server {
	listen 80;
	server_name XXXXX;
    	location / { 
        rewrite ^/(.*)$ http://www.XXXXX/YYYYY/$1 break; 
        }
}

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

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

server {
	listen 80;
	server_name XXXXX;
    	rewrite ^(.*) http://www.XXXXX$1 permanent;
}

server {
	server_name www.XXXXX;
	listen 80;
	
	location / {
	if ($request_uri = '/')  {
        	rewrite ^/(.*)$ http://www.XXXXX/YYYYY/$1 break;
                }
	}
}

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

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

 实例3,使用 rewrite 优化 mediawiki

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

QQ20141123-1@2x

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

    location / {
        try_files $uri $uri/ @rewrite;
    }

    location @rewrite {
        rewrite ^/(.*)$ /index.php?title=$1&$args;
    }

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

rewrite ^(.*) http://www.XXXXX$1 permanent;

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


本文链接:https://www.starduster.me/2014/11/23/nginx-rewrite-practice/
本站基于 Creactive Commons BY-NC-SA 4.0 License 允许并欢迎您在注明来源和非商业使用前提下自由地对本文进行复制、分享或基于本文进行创作。
请注意:受限于笔者水平,本站内容可能存在主观臆断或事实错误,文中信息也可能因时间推移而不再准确,在此提醒读者结合自身判断谨慎地采纳。

发表回复

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

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据