查看文章 |
2 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不同的请求地址使用独立的配置的方式 举例: location = / { ....配置A } location / { ....配置B } location ^~ /images/ { ....配置C } location ~* \.(gif|jpg|jpeg)$ { ....配置D } 访问 / 会使用配置A 如何判断命中哪个location暂且按下不婊, 我们在实战篇再回头来看这个问题. 现在我们只需要明白一个情况: nginx可以有多个location并使用不同的配. sever区块中如果有包含rewrite规则,则会最先执行,而且只会执行一次, 然后再判断命中哪个location的配置,再去执行该location中的rewrite, 当该location中的rewrite执行完毕时,rewrite并不会停止,而是根据rewrite过的URL再次判断location并执行其中的配置. 那么,这里就存在一个问题,如果rewrite写的不正确的话,是会在location区块间造成无限循环的.所以nginx才会加一个最多重试10次的上限. 比如这个例子 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标志的解释都是
……这他妈坑爹呢!!! 什么叫基本上都用? 什么是不基本的情况? =皿= 有兴趣的可以放狗”基本上都用这个Flag”… 我最终还是在stack overflow找到了答案: last和break最大的不同在于 - break是终止当前location的rewrite检测,而且不再进行location匹配 还是这个该死的例子 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..死循环 也就是说,上面的某位试图下载eva op不但没下到反而被HTTP 500射了一脸的例子正是因为用了last标志所以才会造成死循环,如果用break就没事了. 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的简介到这里就全部讲完了,水平及其有限,请大家指出错漏… /3 实战! WordPress的Permalink+Supercache rewrite实现这个rewrite写法其实是来自supercache作者本家的某个评论中,网上很容易查到,做了一些修改. 先给出该配置文件的全部内容..部分内容码掉了..绝对路径什么的你知道也没啥用对吧? server { listen 80; server_name cafeneko.info www.cafeneko.info; access_log ***; error_log *** ; root ***; index index.php; gzip_static on; if (-f $request_filename) { break; } set $supercache_file ''; set $supercache_uri $request_uri; if ($request_method = POST) { set $supercache_uri ''; } if ($query_string) { set $supercache_uri ''; } if ($http_cookie ~* "comment_author_|wordpress_logged_|wp-postpass_" ) { set $supercache_uri ''; } if ($supercache_uri ~ ^(.+)$) { set $supercache_file /wp-content/cache/supercache/$http_host/$1index.html; } if (-f $document_root$supercache_file) { rewrite ^(.*)$ $supercache_file break; } if (!-e $request_filename) { rewrite . /index.php last; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME ***$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } } 下面是解释: gzip_static on; 如果浏览器支持gzip,则在压缩前先寻找是否存在压缩好的同名gz文件避免再次压缩浪费资源,配合supercache的压缩功能一起使用效果最好,相比supercache原生的Apache mod_rewrite实现,nginx的实现简单的多. Apache mod_rewrite足足用了两套看起来一模一样的条件判断来分别rewrite支持gzip压缩和不支持的情况. if (-f $request_filename) { break; } //如果是直接请求某个真实存在的文件,则用break语句停止rewrite检查 set $supercache_file ''; set $supercache_uri $request_uri; //用$request_uri初始化变量 $supercache_uri. if ($request_method = POST) { set $supercache_uri ''; } //如果请求方式为POST,则不使用supercache.这里用清空$supercache_uri的方法来跳过检测,下面会看到 if ($query_string) { set $supercache_uri ''; } //因为使用了rewrite的原因,正常情况下不应该有query_string(一般只有后台才会出现query string),有的话则不使用supercache if ($http_cookie ~* "comment_author_|wordpress_logged_|wp-postpass_" ) { set $supercache_uri ''; } //默认情况下,supercache是仅对unknown user使用的.其他诸如登录用户或者评论过的用户则不使用. comment_author是测试评论用户的cookie, wordpress_logged是测试登录用户的cookie. wp-postpass不大清楚,字面上来看可能是曾经发表过文章的?只要cookie中含有这些字符串则条件成立. 原来的写法中检测登录用户cookie用的是wordpress_,但是我在测试中发现登入/登出以后还会有一个叫wordpress_test_cookie存在,不知道是什么作用,我也不清楚一般用户是否会产生这个cookie.由于考虑到登出以后这个cookie依然存在可能会影响到cache的判断,于是把这里改成了匹配wordpress_logged_ if ($supercache_uri ~ ^(.+)$) { set $supercache_file /wp-content/cache/supercache/$http_host$1index.html; } //如果变量$supercache_uri不为空,则设置cache file的路径 这里稍微留意下$http_host$1index.html这串东西,其实写成 $http_host/$1/index.html 就好懂很多 以这个rewrite形式的url为例
其中 则 $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’ if (-f $document_root$supercache_file) { rewrite ^(.*)$ $supercache_file break; } //检查cache文件是否存在,存在的话则执行rewrite,留意这里因为是rewrite到html静态文件,所以可以直接用break终止掉. if (!-e $request_filename) { rewrite . /index.php last; } //执行到此则说明不使用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 last; 说的是,任何请求都会被rewrite到index.php.因为”.”匹配任意字符,所以这条rewrite其实可以写成任何形式的能任意命中的正则.比如说 rewrite . /index.php last; rewrite ^ /index.php last; rewrite .* /index.php last; 效果都是一样的,都能做到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的匹配时,其只会选择其一的配置执行. 其寻找的方法为:
而下面几种方法当匹配时会立即终止其他location的尝试
总结:
另外还可以定义一种特殊的named location,以@开头,如location @thisissparta 不过这种location定义不用于一般的处理,而是专门用于try_file, error_page的处理,这里不再深入. 晕了没? 用前文的例子来看看 location = / { ....配置A } location / { ....配置B } location ^~ /images/ { ....配置C } location ~* \.(gif|jpg|jpeg)$ { ....配置D } 访问 / 会使用配置A -> 完全命中 那么再回头看我们刚才说的问题.为什么那个URL结果奇怪的XHR请求会命中location ~ \.php$而不是location / ? 我相信你应该已经知道答案了. 所以要解决这个问题最简单的方法就是把rewrite规则放在比location先执行的server块里面就可以了哟. 这次的研究笔记就到此为止了. 最后留一个思考题,如果不将rewrite规则放入server块,还有什么方法可以解决这个XHR 404的问题? 原来的location /块包含从location ~ \.php$到root为止的部分. 答案是存在的.在用使用目前的方法前我死脑筋的在保留location /的前提下尝试了很多种方法…请不要尝试为各种permalink构建独立的location.因为wp的permalink种类很多,包括单篇文章,页面,分类,tag,作者,存档等等..欢迎在回复中讨论 /
-EOF- 更新 @2010.10.23 之前的supercache rewrite规则适用于大部分的WP.但是并不适用于mobile press插件的移动设备支持. 因为其中并没有检测移动设备的user agent,从而导致移动设备也会被rewrite到cache上.这样的结果是在移动设备上也是看到的跟PC一样的完全版blog. 对于性能比较好的手机比如iphone安卓什么的大概没什么问题,但比较一般的比如nokia上用opera mini等看就会比较辛苦了,这次把supercache原本在htaccess中的移动设备检测的代码块也移植了过来. 在前文的配置文件中cookie检测后面加入以下代码段 # Bypass special user agent if ($http_user_agent ~* "2.0 MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo Wii|Nitro|Nokia|Opera Mini|Palm|PlayStation Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915 Obigo|LGE VX|webOS|Nokia5800") { set $supercache_uri ''; } if ($http_user_agent ~* "w3c |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-") { set $supercache_uri ''; } 这样就可以对移动设备绕开cache规则,而直接使用mobile press产生的移动版的效果了. |

