曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3199|回复: 0
打印 上一主题 下一主题

Nginx Rewrite 研究笔记

[复制链接]

716

主题

734

帖子

2946

积分

超级版主

Rank: 9Rank: 9Rank: 9

积分
2946
跳转到指定楼层
楼主
发表于 2014-11-25 10:55:25 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在新主机的迁移过程中,最大的困难就是WP permalink rewrite的设置.

因为旧主机是用的 Apache, 使用的是 WP 本身就可以更改的 .htaccess ,没有太大的难度.而这次在 VPS 上跑的是 Nginx,主要是因为 Nginx 的速度比 Apache 要快很多.

但是另一方面就不是那么舒服了,因为 Nginx 的 rewrite 跟 Apache 不同,而且是在服务器上面才能更改.

下面是其间的一些研究笔记.(以下用例如无特别说明均摘自nginx wiki)

一、Ngnix rewrite 基本语法

Nginx 的 rewrite 语法其实很简单.用到的指令无非是这几个
  • set
  • if
  • return
  • break
  • rewrite

麻雀虽小,可御可萝五脏俱全.只是简单的几个指令却可以做出绝对不输apache的简单灵活的配置.

1.set
set主要是用来设置变量用的,没什么特别的。

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

用法:
  1. if(条件) { ... }
复制代码


- 当if表达式中的条件为true,则执行if块中的语句

- 当表达式只是一个变量时,如果值为空或者任何以 0 开头的字符串都会当作 false

- 直接比较内容时,使用 = 和 !=

- 使用正则表达式匹配时,使用
~ 大小写敏感匹配
~* 大小写不敏感匹配   
!~ 大小写敏感不匹配   
!~* 大小写不敏感不匹配

这几句话看起来有点绕,总之记住: ~为正则匹配, 后置 * 为大小写不敏感, 前置 ! 为”非”操作 。

随便一提,因为 nginx 使用花括号 {} 判断区块,所以当正则中包含花括号时,则必须用双引号将正则包起来.对下面讲到的 rewrite 语句中的正则亦是如此. 比如 “\d{4}\d{2}\.+

- 使用-f,-d,-e,-x检测文件和目录:
-f 检测文件存在
-d 检测目录存在
-e 检测文件,目录或者符号链接存在
-x 检测文件可执行


跟~类似,前置!则为”非”操作 。

举例

1. 如果 UA 包含”MSIE”, rewrite 请求到 /msie 目录下
[PHP] 纯文本查看 复制代码
if ($http_user_agent ~ MSIE) {
  rewrite  ^(.*)$  /msie/$1  break;
}




2. 如果 cookie 匹配正则,设置变量 $id 等于正则引用部分
[PHP] 纯文本查看 复制代码
if ($http_cookie ~* "id=([^;] +)(?:;|$)" ) {
  set  $id  $1;
}



3. 如果提交方法为POST,则返回状态405 (Method not allowed)
[PHP] 纯文本查看 复制代码
if ($request_method = POST ) {
  return 405;
}



4. 如果请求文件名不存在, 则反向代理 localhost
[PHP] 纯文本查看 复制代码
if (!-f $request_filename) {
  break;
  proxy_pass  [url]http://127.0.0.1[/url];
}



5. 如果query string中包含”post=140″,永久重定向到example.com
[PHP] 纯文本查看 复制代码
if ($args ~ post=140){
  rewrite ^ [url]http://example.com/[/url] permanent;
}






return
return 可用来直接设置 HTTP 返回状态,比如 403,404 等 (301,302不可用 return 返回,这个下面会在 rewrite 提到)

break
立即停止 rewrite 检测,跟下面讲到的 rewrite 的 break flag 功能是一样的,区别在于前者是一个语句,后者是 rewrite 语句的 flag 。

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 中就会这么写:
[PHP] 纯文本查看 复制代码
rewrite ^(.*)$ [url]http://newdomain.com/[/url] permanent;



举例来说一下 rewrite 的实际应用:
[PHP] 纯文本查看 复制代码
rewrite  ^(/download/.*)/media/(.*)\..*$  $1/mp3/$2.mp3  last;



如果请求为 /download/eva/media/op1.mp3 则请求被rewrite到 /download/eva/mp3/op1.mp3

使用起来就是这样,很简单不是么? 不过要注意的是rewrite有很多潜规则需要注意

- rewrite 的生效区块为sever, location, if
- rewrite 只对相对路径进行匹配,不包含 hostname 比如说以上面 301 重定向的例子说明

[PHP] 纯文本查看 复制代码
rewrite ~* cafeneko\.info [url]http://newdomain.com/[/url] permanent;


这句是永远无法执行的,以这个 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 跳转:
[PHP] 纯文本查看 复制代码
if ($host ~* ^www\.(cafeneko\.info)) {
  set $host_without_www $1;
  rewrite ^(.*)$ http://$host_without_www$1 permanent;
}



- 使用相对路径 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 地址的末尾添加?
[PHP] 纯文本查看 复制代码
rewrite  ^/users/(.*)$  /show?user=$1?  last;


rewrite的基本知识就是这么多..但还没有完..还有最头疼的部分没有说…





Nginx location 和 rewrite retry
nginx 的 rewrite 有个很奇特的特性 ---- rewrite后的 url 会再次进行 rewrite 检查,最多重试 10 次,10 次后还没有终止的话就会返回 HTTP 500

用过nginx的朋友都知道 location 区块, location 区块有点像 Apache 中的 RewriteBase, 但对于 nginx 来说 location 是控制的级别而已, 里面的内容不仅仅是rewrite.
这里必须稍微先讲一点 location 的知识。location 是 nginx 用来处理对同一个 server 不同的请求地址使用独立的配置的方式

举例:
[PHP] 纯文本查看 复制代码
location  = / {
  ....配置A
}
 
location  / {
  ....配置B
}
 
location ^~ /images/ {
  ....配置C
}
 
location ~* \.(gif|jpg|jpeg)$ {
  ....配置D
}



访问 / 会使用配置 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次的上限.  比如这个例子
[PHP] 纯文本查看 复制代码
location /download/ {
  rewrite  ^(/download/.*)/media/(.*)\..*$  $1/mp3/$2.mp3  last;
}


如果请求为 /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 规则


还是这个该死的例子:
[PHP] 纯文本查看 复制代码
location /download/ {
  rewrite  ^(/download/.*)/media/(.*)\..*$  $1/mp3/$2.mp3  ;
  rewrite  ^(/download/.*)/movie/(.*)\..*$  $1/avi/$2.mp3  ;
  rewrite  ^(/download/.*)/avvvv/(.*)\..*$  $1/rmvb/$2.mp3 ;
}


上面没有写标志位,请各位自行脑补…

如果请求为 /download/acg/moive/UBW.avi
last 的情况是: 在第 2 行 rewrite 处终止,并重试 location /download..死循环
break 的情况是: 在第2行rewrite处终止,其结果为最终的 rewrite 地址.
也就是说,上面的某位试图下载 eva op 不但没下到反而被 HTTP 500 射了一脸的例子正是因为用了 last 标志所以才会造成死循环,如果用 break 就没事了.
[PHP] 纯文本查看 复制代码
location /download/ {
  rewrite  ^(/download/.*)/media/(.*)\..*$  $1/mp3/$2.mp3  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的简介到这里就全部讲完了,水平及其有限,请大家指出错漏…

转载自:http://blog.cafeneko.info/2010/10/nginx_rewrite_note/



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 19:52 , Processed in 0.080023 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表