Nginx rewrite基本語法
Nginx的rewrite語法其實很簡單.用到的指令無非是這幾個:
- set
- if
- return
- break
- rewrite
麻雀雖小,可御可蘿五臟俱全.只是簡單的幾個指令卻可以做出絕對不輸apache的簡單靈活的配置.
** 1. set **
set主要是用來設置變量用的,沒什么特別的
2. if
if主要用來判斷一些在rewrite語句中無法直接匹配的條件,比如檢測文件存在與否,http header,cookie等,
用法: if(條件) {…}
- 當if表達式中的條件為true,則執行if塊中的語句
- 當表達式只是一個變量時,如果值為空或者任何以0開頭的字符串都會當作false
- 直接比較內容時,使用
= 和 !=
- 使用正則表達式匹配時,使用
4.1~
區分大小寫
4.2~*
不區分大小寫
4.3!~
區分大小寫的不匹配
4.4!~*
不區分大小寫的不匹配
這幾句話看起來有點繞,總之記住:~
為正則匹配, 后置*
為大小寫不敏感, 前置!
為”非”操作
隨便一提,因為nginx使用花括號{}
判斷區塊,所以當正則中包含花括號時,則必須用雙引號將正則包起來.對下面講到的rewrite語句中的正則亦是如此.
比如“\d{4}\d{2}\.+”
-
使用-f,-d,-e,-x
檢測文件和目錄
-f
檢測文件存在
-d
檢測目錄存在
-e
檢測文件,目錄或者符號鏈接存在
-x
檢測文件可執行
跟~類似,前置!
則為”非”
操作
舉例
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
//如果UA包含”MSIE”,rewrite 請求到/msie目錄下
if ($http_cookie ~* "id=([^;] +)(?:;|$)" ) {
set $id $1;
}
//如果cookie匹配正則,設置變量$id等于正則引用部分
if ($request_method = POST ) {
return 405;
}
//如果提交方法為POST,則返回狀態405 (Method not allowed)
if (!-f $request_filename) {
break;
proxy_pass http://127.0.0.1;
}
//如果請求文件名不存在,則反向代理localhost
if ($args ~ post=140){
rewrite ^ http://example.com/ permanent;
}
//如果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 ^(.*)$ http://newdomain.com/ permanent;
舉例來說一下rewrite的實際應用
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重定向的例子說明
rewrite ~* cafeneko\.info http://newdomain.com/ 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跳轉
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地址的末尾添加?
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不同的請求地址使用獨立的配置的方式
舉例:
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區塊中:
1, 如果有包含rewrite規則,則會最先執行,而且只會執行一次,
2, 然后再判斷命中哪個location的配置,再去執行該location中的rewrite,
3, 當該location中的rewrite執行完畢時,rewrite并不會停止,而是根據rewrite過的URL再次判斷location并執行其中的配置.
4, 那么,這里就存在一個問題,如果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標志的解釋都是
last
– 基本上都用這個Flag
……這他媽坑爹呢!!! 什么叫基本上都用? 什么是不基本的情況? =皿=
有興趣的可以放狗”基本上都用這個Flag”…
我最終還是在stack overflow找到了答案:
last和break最大的不同在于
- break
是終止當前location的rewrite檢測,而且不再進行location匹配
– last
是終止當前location的rewrite檢測,但會繼續重試location匹配并處理區塊中的rewrite規則
還是這個該死的例子
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就沒事了.
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為例
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’
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 /, location /av/, 以相對路徑自左向右匹配,匹配長度最高的會被使用,
然后按照配置文件中出現的順序依次測試正則表達式,如 location ~ download/$, location ~* .wtf, 第一個匹配會被使用
如果沒有匹配的正則,則使用之前的常量匹配
而下面幾種方法當匹配時會立即終止其他location的嘗試
= 完全匹配,location = /download/
^~ 終止正則測試,如location ^~ /download/ 如果這條是最長匹配,則終止正則測試,這個符號只能匹配常量
在沒有=或者^~的情況下,如果常量完全匹配,也會立即終止測試,比如請求為 /download/ 會完全命中location /download/而不繼續其他的正則測試
總結:
如果完全匹配(不管有沒有=),嘗試會立即終止
以最長匹配測試各個常量,如果常量匹配并有 ^~, 嘗試會終止
按在配置文件中出現的順序測試各個正則表達式
如果第3步有命中,則使用其匹配location,否則使用第2步的location
另外還可以定義一種特殊的named location,以@開頭,如location @thisissparta 不過這種location定義不用于一般的處理,而是專門用于try_file, error_page的處理,這里不再深入.
暈了沒? 用前文的例子來看看
location = / {
....配置A
}
location / {
....配置B
}
location ^~ /images/ {
....配置C
}
location ~* \.(gif|jpg|jpeg)$ {
....配置D
}
訪問 / 會使用配置A -> 完全命中
訪問 /documents/document.html 會使用配置B -> 匹配常量B,不匹配正則C和D,所以用B
訪問 /images/1.gif 會使用配置C -> 匹配常量B,匹配正則C,使用首個命中的正則,所以用C
訪問 /documents/1.jpg 會使用配置D -> 匹配常量B,不匹配正則C,匹配正則D,使用首個命中的正則,所以用D