原文出自:http://blog.cafeneko.info/2010/10/nginx_rewrite_note/php
在新主机的迁移过程当中,最大的困难就是WP permalink rewrite的设置.css
由于旧主机是用的Apache, 使用的是WP自己就能够更改的.htaccess,没有太大的难度.而此次在VPS上跑的是Nginx,主要是由于Nginx的速度比Apache要快不少.html
可是另外一方面就不是那么舒服了,由于Nginx的rewrite跟Apache不一样,并且是在服务器上面才能更改.nginx
下面是其间的一些研究笔记.(如下用例如无特别说明均摘自nginx wiki)web
Nginx的rewrite语法其实很简单.用到的指令无非是这几个正则表达式
麻雀虽小,可御可萝五脏俱全.只是简单的几个指令却能够作出绝对不输apache的简单灵活的配置.apache
1.setapi
set主要是用来设置变量用的,没什么特别的浏览器
2.ifbash
if主要用来判断一些在rewrite语句中没法直接匹配的条件,好比检测文件存在与否,http header,cookie等,
用法: if(条件) {…}
- 当if表达式中的条件为true,则执行if块中的语句
- 当表达式只是一个变量时,若是值为空或者任何以0开头的字符串都会看成false
- 直接比较内容时,使用 = 和 !=
- 使用正则表达式匹配时,使用
~ 大小写敏感匹配
~* 大小写不敏感匹配
!~ 大小写敏感不匹配
!~* 大小写不敏感不匹配
这几句话看起来有点绕,总之记住: ~为正则匹配, 后置*为大小写不敏感, 前置!为”非”操做
随便一提,由于nginx使用花括号{}判断区块,因此当正则中包含花括号时,则必须用双引号将正则包起来.对下面讲到的rewrite语句中的正则亦是如此.
好比 “\d{4}\d{2}\.+”
- 使用-f,-d,-e,-x检测文件和目录
-f 检测文件存在
-d 检测目录存在
-e 检测文件,目录或者符号连接存在
-x 检测文件可执行
跟~相似,前置!则为”非”操做
举例
//若是UA包含”MSIE”,rewrite 请求到/msie目录下
//若是cookie匹配正则,设置变量$id等于正则引用部分
//若是提交方法为POST,则返回状态405 (Method not allowed)
//若是请求文件名不存在,则反向代理localhost
//若是query string中包含”post=140″,永久重定向到example.com
3.return
return可用来直接设置HTTP返回状态,好比403,404等(301,302不可用return返回,这个下面会在rewrite提到)
4.break
当即中止rewrite检测,跟下面讲到的rewrite的break flag功能是同样的,区别在于前者是一个语句,后者是rewrite语句的flag
5.rewrite
最核心的功能(废话)
用法: rewrite 正则 替换 标志位
其中标志位有四种
break – 中止rewrite检测,也就是说当含有break flag的rewrite语句被执行时,该语句就是rewrite的最终结果
last – 中止rewrite检测,可是跟break有本质的不一样,last的语句不必定是最终结果,这点后面会跟nginx的location匹配一块儿提到
redirect – 返回302临时重定向,通常用于重定向到完整的URL(包含http:部分)
permanent – 返回301永久重定向,通常用于重定向到完整的URL(包含http:部分)
由于301和302不能简单的只单纯返回状态码,还必须有重定向的URL,这就是return指令没法返回301,302的缘由了. 做为替换,rewrite能够更灵活的使用redirect和permanent标志实现301和302. 好比上一篇日志中提到的Blog搬家要作的域名重定向,在nginx中就会这么写
举例来讲一下rewrite的实际应用
若是请求为 /download/eva/media/op1.mp3 则请求被rewrite到 /download/eva/mp3/op1.mp3
使用起来就是这样,很简单不是么? 不过要注意的是rewrite有不少潜规则须要注意
- rewrite的生效区块为sever, location, if
- rewrite只对相对路径进行匹配,不包含hostname 好比说以上面301重定向的例子说明
这句是永远没法执行的,以这个URL为例
http://blog.cafeneko.info/2010/10/neokoseseiki_in_new_home/?utm_source=rss&utm_medium=rss&utm_campaign=neokoseseiki_in_new_home
其中cafeneko.info叫作hostname,再日后到?为止叫作相对路径,?后面的一串叫作query string
对于rewrite来讲,其正则表达式仅对”/2010/10/neokoseseiki_in_new_home”这一部分进行匹配,即不包含hostname,也不包含query string .因此除非相对路径中包含跟域名同样的string,不然是不会匹配的. 若是非要作域名匹配的话就要使用if语句了,好比进行去www跳转
- 使用相对路径rewrite时,会根据HTTP header中的HOST跟nginx的server_name匹配后进行rewrite,若是HOST不匹配或者没有HOST信息的话则rewrite到server_name设置的第一个域名,若是没有设置server_name的话,会使用本机的localhost进行rewrite
- 前面提到过,rewrite的正则是不匹配query string的,因此默认状况下,query string是自动追加到rewrite后的地址上的,若是不想自动追加query string,则在rewrite地址的末尾添加?
rewrite的基本知识就是这么多..但尚未完..还有最头疼的部分没有说…
nginx的rewrite有个很奇特的特性 — rewrite后的url会再次进行rewrite检查,最多重试10次,10次后尚未终止的话就会返回HTTP 500
用过nginx的朋友都知道location区块,location区块有点像Apache中的RewriteBase,但对于nginx来讲location是控制的级别而已,里面的内容不只仅是rewrite.
这里必须稍微先讲一点location的知识.location是nginx用来处理对同一个server不一样的请求地址使用独立的配置的方式
举例:
访问 / 会使用配置A
访问 /documents/document.html 会使用配置B
访问 /images/1.gif 会使用配置C
访问 /documents/1.jpg 会使用配置D
如何判断命中哪一个location暂且按下不婊, 咱们在实战篇再回头来看这个问题.
如今咱们只须要明白一个状况: nginx能够有多个location并使用不一样的配.
sever区块中若是有包含rewrite规则,则会最早执行,并且只会执行一次, 而后再判断命中哪一个location的配置,再去执行该location中的rewrite, 当该location中的rewrite执行完毕时,rewrite并不会中止,而是根据rewrite过的URL再次判断location并执行其中的配置. 那么,这里就存在一个问题,若是rewrite写的不正确的话,是会在location区块间形成无限循环的.因此nginx才会加一个最多重试10次的上限. 好比这个例子
若是请求为 /download/eva/media/op1.mp3 则请求被rewrite到 /download/eva/mp3/op1.mp3
结果rewrite的结果从新命中了location /download/ 虽然此次并无命中rewrite规则的正则表达式,但由于缺乏终止rewrite的标志,其仍会不停重试download中rewrite规则直到达到10次上限返回HTTP 500
认真的朋友这时就会问了,上面的rewrite规则不是有标志位last么? last不是终止rewrite的意思么?
说到这里我就要抱怨下了,网上能找到关于nginx rewrite的文章中80%对last标志的解释都是
last – 基本上都用这个Flag
……这他妈坑爹呢!!! 什么叫基本上都用? 什么是不基本的状况? =皿=
有兴趣的能够放狗”基本上都用这个Flag”…
我最终仍是在stack overflow找到了答案:
last和break最大的不一样在于
- break是终止当前location的rewrite检测,并且再也不进行location匹配
– last是终止当前location的rewrite检测,但会继续重试location匹配并处理区块中的rewrite规则
仍是这个该死的例子
上面没有写标志位,请各位自行脑补…
若是请求为 /download/acg/moive/UBW.avi
last的状况是: 在第2行rewrite处终止,并重试location /download..死循环
break的状况是: 在第2行rewrite处终止,其结果为最终的rewrite地址.
也就是说,上面的某位试图下载eva op不但没下到反而被HTTP 500射了一脸的例子正是由于用了last标志因此才会形成死循环,若是用break就没事了.
对于这个问题,我我的的建议是,若是是全局性质的rewrite,最好放在server区块中并减小没必要要的location区块.location区块中的rewrite要想清楚是用last仍是break.
有人可能会问,用break不就万无一失了么?
不对.有些状况是要用last的. 典型的例子就是wordpress的permalink rewrite
常见的状况下, wordpress的rewrite是放在location /下面,并将请求rewrite到/index.php
这时若是这里使用break乃就挂了,不信试试. b( ̄▽ ̄)d…由于nginx返回的是没有解释的index.php的源码…
这里必定要使用last才能够在结束location / 的rewrite, 并再次命中location ~ \.php$,将其交给fastcgi进行解释.其后返回给浏览器的才是解释过的html代码.
关于nginx rewrite的简介到这里就所有讲完了,水平及其有限,请你们指出错漏…
这个rewrite写法实际上是来自supercache做者本家的某个评论中,网上很容易查到,作了一些修改. 先给出该配置文件的所有内容..部份内容码掉了..绝对路径什么的你知道也没啥用对吧?
下面是解释:
若是浏览器支持gzip,则在压缩前先寻找是否存在压缩好的同名gz文件避免再次压缩浪费资源,配合supercache的压缩功能一块儿使用效果最好,相比supercache原生的Apache mod_rewrite实现,nginx的实现简单的多. Apache mod_rewrite足足用了两套看起来如出一辙的条件判断来分别rewrite支持gzip压缩和不支持的状况.
//若是是直接请求某个真实存在的文件,则用break语句中止rewrite检查
//用$request_uri初始化变量 $supercache_uri.
//若是请求方式为POST,则不使用supercache.这里用清空$supercache_uri的方法来跳过检测,下面会看到
//由于使用了rewrite的缘由,正常状况下不该该有query_string(通常只有后台才会出现query string),有的话则不使用supercache
//默认状况下,supercache是仅对unknown user使用的.其余诸如登陆用户或者评论过的用户则不使用.
comment_author是测试评论用户的cookie, wordpress_logged是测试登陆用户的cookie. wp-postpass不大清楚,字面上来看多是曾经发表过文章的?只要cookie中含有这些字符串则条件成立.
原来的写法中检测登陆用户cookie用的是wordpress_,可是我在测试中发现登入/登出之后还会有一个叫wordpress_test_cookie存在,不知道是什么做用,我也不清楚通常用户是否会产生这个cookie.因为考虑到登出之后这个cookie依然存在可能会影响到cache的判断,因而把这里改为了匹配wordpress_logged_
//若是变量$supercache_uri不为空,则设置cache file的路径
这里稍微留意下$http_host$1index.html这串东西,其实写成 $http_host/$1/index.html 就好懂不少
以这个rewrite形式的url为例
cafeneko.info/2010/09/tsukihime-doujin_part01/
其中
$http_host = ‘cafeneko.info’ , $1 = $request_uri = ‘/2010/09/tsukihime-doujin_part01/’
则 $http_host$1index.html = ‘cafeneko.info/2010/09/tsukihime-doujin_part01/index.html’
而 $http_host/$1/index.html = ‘cafeneko.info//2010/09/tsukihime-doujin_part01//index.html’
虽然在调试过程当中二者并无不一样,不过为了保持正确的路径,仍是省略了中间的/符号.
最后上例rewrite后的url = ‘cafeneko.info/wp-content/cache/supercache/cafeneko.info/2010/09/tsukihime-doujin_part01/index.html’
//检查cache文件是否存在,存在的话则执行rewrite,留意这里由于是rewrite到html静态文件,因此能够直接用break终止掉.
//执行到此则说明不使用suercache,进行wordpress的permalink rewrite
检查请求的文件/目录是否存在,若是不存在则条件成立, rewrite到index.php
顺便说一句,当时这里这句rewrite看的我百思不得其解. .
只能匹配一个字符啊?这是什么意思?
通常状况下,想调试nginx rewrite最简单的方法就是把flag写成redirect,这样就能在浏览器地址栏里看到真实的rewrite地址.
然而对于permalink rewrite却不能用这种方法,由于一旦写成redirect之后,无论点什么连接,只要没有supercache,都是跳转回首页了.
后来看了一些文章才明白了rewrite的本质,实际上是在保持请求地址不变的状况下,在服务器端将请求转到特定的页面.
乍一看supercache的性质有点像302到静态文件,因此能够用redirect调试.
可是permalink倒是性质彻底不一样的rewrite,这跟wordpress的处理方式有关. 我研究不深就很少说了,简单说就是保持URL不变将请求rewrite到index.php,WP将分析其URL结构再对其并进行匹配(文章,页面,tag等),而后再构建页面. 因此其实这条rewrite
说的是,任何请求都会被rewrite到index.php.由于”.”匹配任意字符,因此这条rewrite其实能够写成任何形式的能任意命中的正则.好比说
效果都是同样的,都能作到permalink rewrite.
最后要提的就是有人可能注意到个人rewrite规则是放在server块中的.网上能找到的大多数关于wordpress的nginx rewrite规则都是放在location /下面的,可是上面我却放在了server块中,为什么?
缘由是WP或某个插件会在当前页面作一个POST的XHR请求,原本没什么特别,但问题就出在其XHR请求的URL结构上.
正常的permalink通常为: domain.com/year/month/postname/ 或者 domain.com/tags/tagname/ 之类.
但这个XHR请求的URL倒是 domain.com/year/month/postname/index.php 或者 domain.com/tags/tagname/index.php
这样一来就命中了location ~ \.php$而交给fastcgi,但由于根本没有作过rewrite其页面不可能存在,结果就是这个XHR返回一个404
鉴于location之间匹配优先级的缘由,我将主要的rewrite功能所有放进了server区块中,这样就得以保证在进行location匹配以前是必定作过rewrite的.
这时有朋友又要问了,为何命中的是location ~ \.php$而不是location / ?
…望天…长叹…这就要扯到天杀的location匹配问题了….
locatoin并不是像rewrite那样逐条执行,而是有着匹配优先级的,当一条请求同时知足几个location的匹配时,其只会选择其一的配置执行.
其寻找的方法为:
1. 首先寻找全部的常量匹配,如location /, location /av/, 以相对路径自左向右匹配,匹配长度最高的会被使用,
2. 而后按照配置文件中出现的顺序依次测试正则表达式,如 location ~ download\/$, location ~* \.wtf, 第一个匹配会被使用
3. 若是没有匹配的正则,则使用以前的常量匹配
而下面几种方法当匹配时会当即终止其余location的尝试
1. = 彻底匹配,location = /download/
2. ^~ 终止正则测试,如location ^~ /download/ 若是这条是最长匹配,则终止正则测试,这个符号只能匹配常量
3. 在没有=或者^~的状况下,若是常量彻底匹配,也会当即终止测试,好比请求为 /download/ 会彻底命中location /download/而不继续其余的正则测试
总结:
1. 若是彻底匹配(无论有没有=),尝试会当即终止
2. 以最长匹配测试各个常量,若是常量匹配并有 ^~, 尝试会终止
3. 按在配置文件中出现的顺序测试各个正则表达式
4. 若是第3步有命中,则使用其匹配location,不然使用第2步的location
另外还能够定义一种特殊的named location,以@开头,如location @thisissparta 不过这种location定义不用于通常的处理,而是专门用于try_file, error_page的处理,这里再也不深刻.
晕了没? 用前文的例子来看看
访问 / 会使用配置A -> 彻底命中
访问 /documents/document.html 会使用配置B -> 匹配常量B,不匹配正则C和D,因此用B
访问 /images/1.gif 会使用配置C -> 匹配常量B,匹配正则C,使用首个命中的正则,因此用C
访问 /documents/1.jpg 会使用配置D -> 匹配常量B,不匹配正则C,匹配正则D,使用首个命中的正则,因此用D
那么再回头看咱们刚才说的问题.为何那个URL结果奇怪的XHR请求会命中location ~ \.php$而不是location / ? 我相信你应该已经知道答案了.
因此要解决这个问题最简单的方法就是把rewrite规则放在比location先执行的server块里面就能够了哟.
此次的研究笔记就到此为止了.
最后留一个思考题,若是不将rewrite规则放入server块,还有什么方法能够解决这个XHR 404的问题?
原来的location /块包含从location ~ \.php$到root为止的部分.
答案是存在的.在用使用目前的方法前我死脑筋的在保留location /的前提下尝试了不少种方法…请不要尝试为各类permalink构建独立的location.由于wp的permalink种类不少,包括单篇文章,页面,分类,tag,做者,存档等等..欢迎在回复中讨论 /
参考:
Nginx wiki
-EOF-
更新 @2010.10.23
以前的supercache rewrite规则适用于大部分的WP.可是并不适用于mobile press插件的移动设备支持.
由于其中并无检测移动设备的user agent,从而致使移动设备也会被rewrite到cache上.这样的结果是在移动设备上也是看到的跟PC同样的彻底版blog. 对于性能比较好的手机好比iphone安卓什么的大概没什么问题,但比较通常的好比nokia上用opera mini等看就会比较辛苦了,此次把supercache本来在htaccess中的移动设备检测的代码块也移植了过来.
在前文的配置文件中cookie检测后面加入如下代码段
这样就能够对移动设备绕开cache规则,而直接使用mobile press产生的移动版的效果了.