難道只有我懂Nginx/OpenResty詳解,Nginx的rewrite模塊指令?
Nginx的rewrite模塊指令
Nginx的rewrite模塊即ngx_http_rewrite_module標(biāo)準(zhǔn)模塊,主要功能是重寫(xiě)請(qǐng)求URI,也是Nginx默認(rèn)安裝的模塊。rewrite模塊會(huì)根據(jù)PCRE正則匹配重寫(xiě)URI,然后根據(jù)指令參數(shù)或者發(fā)起內(nèi)部跳轉(zhuǎn)再一次進(jìn)行l(wèi)ocation匹配,或者直接進(jìn)行30x重定向返回客戶端。
rewrite模塊的指令就是一門(mén)微型的編程語(yǔ)言,包含set、rewrite、break、if、return等一系列指令。

set指令
set指令是由ngx_http_rewrite_module標(biāo)準(zhǔn)模塊提供的,用于向變量存放值。在Nginx配置文件中,變量只能存放一種類型的值,因?yàn)橹淮嬖谝环N類型的值,那就是字符串。
set指令的配置項(xiàng)格式如下:
set $variable value;注意:在Nginx配置文件中,變量定義和使用都要以$開(kāi)頭。Nginx變量名前面有一個(gè)$符號(hào),這是記法上的要求。所有的Nginx變量在引用時(shí)必須帶上$前綴。另外,Nginx變量不能與Nginx服務(wù)器預(yù)設(shè)的全局變量同名。比如,我們的nginx.conf文件中有下面這一行配置:
set $a "hello world";上面的語(yǔ)句中,set配置指令對(duì)變量$a進(jìn)行了賦值操作,把字符串hello world賦給了它。也可以直接把變量嵌入字符串常量中以構(gòu)造出新的字符串:
set $a "foo";
set $b "$a, $a";這個(gè)例子通過(guò)前面定義的變量$a的值來(lái)構(gòu)造變量$b的值,于是這兩條指令順序執(zhí)行完之后,$a的值是"foo",而$b的值則是"foo,foo"。把變量嵌入字符串常量中以構(gòu)造出新的字符串,這種技術(shù)在Linux Shell腳本中常常用到,并且被稱為“變量插值”(VariableInterpolation)。
set指令不僅有賦值的功能,還有創(chuàng)建Nginx變量的副作用,即當(dāng)作為賦值對(duì)象的變量尚不存在時(shí),它會(huì)自動(dòng)創(chuàng)建該變量。比如在上面這個(gè)例子中,若$a這個(gè)變量尚未創(chuàng)建,則set指令會(huì)自動(dòng)創(chuàng)建$a這個(gè)用戶變量。
Nginx變量一旦創(chuàng)建,其變量名的可見(jiàn)范圍就是整個(gè)Nginx配置,甚至可以跨越不同虛擬主機(jī)的server配置塊。但是,對(duì)于每個(gè)請(qǐng)求,所有變量都有一份獨(dú)立的副本,或者說(shuō)都有各變量用來(lái)存放值的容器的獨(dú)立副本,彼此互不干擾。Nginx變量的生命期是不可能跨越請(qǐng)求邊界的。
?rewrite指令
rewrite指令是由ngx_http_rewrite_module標(biāo)準(zhǔn)模塊提供的,主要功能是改寫(xiě)請(qǐng)求URI。rewrite指令的格式如下:
rewrite regrex replacement [flag];如果regrex匹配URI,URI就會(huì)被替換成replacement的計(jì)算結(jié)果,replacement一般是一個(gè)“變量插值”表達(dá)式,其計(jì)算之后的字符串就是新的URI。
下面的例子有兩個(gè)重新配置項(xiàng),具體如下:
location /download/ {
rewrite ^/download/(.*)/video/(.*)$ /view/$1/mp3/$2.mp3 last;
rewrite ^/download/(.*)/audio/(.*)*$ /view/$1/mp3/$2.rmvb last;
return 404;
}
location /view {
echo "uri: $uri ";
}在瀏覽器中請(qǐng)求
http://crazydemo.com/download/1/video/10,地址發(fā)生了重寫(xiě),并且發(fā)生了location的跳轉(zhuǎn),結(jié)果如圖7-17所示。

圖7-17 輸出結(jié)果
在這個(gè)演示例子中,replacement中的占位變量$1、$2的值是指令參數(shù)regrex正則表達(dá)式從原始URI中匹配出來(lái)的子字符串,也叫正則捕獲組,編號(hào)從1開(kāi)始。
rewrite指令可以使用的上下文為:server、location、if inlocation。
如果rewrite同一個(gè)上下文中有多個(gè)這樣的rewrite重新指令,匹配就會(huì)依照rewrite指令出現(xiàn)的順序先后依次進(jìn)行下去,匹配成功之后并不會(huì)終止,而是繼續(xù)往下匹配,直到返回最后一個(gè)匹配的為止。如果想要中途中止,不再繼續(xù)往下匹配,可以使用第3個(gè)指令參數(shù)flag。flag參數(shù)的值有l(wèi)ast、break、redirect、permanent。
如果flag參數(shù)使用last值,并且匹配成功,那么停止處理任何rewrite相關(guān)的指令,立即用計(jì)算后的新URI開(kāi)始下一輪的location匹配和跳轉(zhuǎn)。前面的例子使用的就是last參數(shù)值。
如果flag參數(shù)使用break值,就如同break指令的字面意思一樣,停止處理任何rewrite的相關(guān)指令,但是不進(jìn)行l(wèi)ocation跳轉(zhuǎn)。
將上面的rewrite例子中的last參數(shù)值改成break,代碼如下:
location /view {
echo " view : $uri ";
}
location /download_break/ {
rewrite ^/download_break/(.*)/video/(.*)$ /view/$1/mp3/$2.mp3 break;
rewrite ^/download_break/(.*)/audio/(.*)*$ /view/$1/mp3/$2.rmvb break;
echo " download_break new uri : $uri ";
}在瀏覽器中請(qǐng)求
http://crazydemo.com/download_break/1/video/10,地址發(fā)生了重寫(xiě),但是location并沒(méi)有跳轉(zhuǎn),而是直接結(jié)束了,結(jié)果如圖7-18所示。

圖7-18 顯示結(jié)果
在location上下文中,last和break是有區(qū)別的:last其實(shí)就相當(dāng)于一個(gè)新的URL,Nginx進(jìn)行了一次新的location匹配,通過(guò)last獲得一個(gè)可以轉(zhuǎn)到其他location配置中處理的機(jī)會(huì)(內(nèi)部的重定向);而break在一個(gè)location中將原來(lái)的URL(包括URI和args)改寫(xiě)之后,再繼續(xù)進(jìn)行后面的處理,這個(gè)重寫(xiě)之后的請(qǐng)求始終都是在同一個(gè)location上下文中,并沒(méi)有發(fā)生內(nèi)部跳轉(zhuǎn)。
這里要注意:last和break的區(qū)別僅僅發(fā)生在location上下文中;如果發(fā)生在server上下文,那么last和break的作用是一樣的。
還要注意:在location上下文中的rewrite指令使用last指令參數(shù)會(huì)再次以新的URI重新發(fā)起內(nèi)部重定向,再次進(jìn)行l(wèi)ocation匹配,而新的URI極有可能和舊的URI一樣再次匹配到相同的目標(biāo)location中,這樣死循環(huán)就發(fā)生了。當(dāng)循環(huán)到第10次時(shí),Nginx會(huì)終止這樣無(wú)意義的循環(huán)并返回500錯(cuò)誤。這一點(diǎn)需要特別注意。
如果rewrite指令使用的flag參數(shù)的值是permanent,就表示進(jìn)行外部重定向,也就是在客戶端進(jìn)行重定向。此時(shí),服務(wù)器將新URI地址返回給客戶端瀏覽器,并且返回301(永久重定向的響應(yīng)碼)給客戶端??蛻舳藢⑹褂眯碌闹囟ㄏ虻刂吩侔l(fā)起一次遠(yuǎn)程請(qǐng)求。
永久重定向permanent的使用示例如下:
#rewrite指令permanent參數(shù)演示
location /download_permanent/ {
rewrite ^/download_permanent/(.*)/video/(.*)$ /view/$1/mp3/$2.mp3 permanent;
rewrite ^/download_permanent/(.*)/audio/(.*)*$ /view/$1/mp3/$2.rmvb permanent; return 404;
}在瀏覽器中請(qǐng)求
http://crazydemo.com/download_permanent/1/video/10,輸出的結(jié)果如圖7-19所示。

圖7-19 輸出的結(jié)果
從以上結(jié)果可以看出,永久重定向有兩個(gè)比較大的特點(diǎn):
(1)瀏覽器的地址欄地址變成了重定向地址
http://crazydemo.com/view/1/mp3/10.mp3。(2)從Fiddler抓包工具可以看到,第一個(gè)請(qǐng)求地址的響應(yīng)狀態(tài)碼為301,如圖7-20所示。

圖7-20 永久重定向的響應(yīng)碼示意圖
外部重定向與內(nèi)部重定向是有本質(zhì)區(qū)別的。從數(shù)量上說(shuō),外部重定向有兩次請(qǐng)求,內(nèi)部重定向只有一次請(qǐng)求。通過(guò)上面的幾個(gè)示例,大家應(yīng)該體會(huì)得相當(dāng)深刻了。
如果rewrite指令使用的flag參數(shù)的值是redirect,就表示進(jìn)行外部重定向,表現(xiàn)的行為與permanent參數(shù)值完全一樣,不同的是返回302(臨時(shí)重定向的響應(yīng)碼)給客戶端。
有關(guān)redirect參數(shù)值的實(shí)例這里不進(jìn)行演示,大家可自行下載和運(yùn)行本文的源碼并細(xì)細(xì)體會(huì)。
rewrite能夠利用正則捕獲組設(shè)置變量,作為實(shí)驗(yàn),我們可以在Nginx的配置文件中加入這么一條location規(guī)則:
location /capture_demo {
rewrite ^/capture_demo/(.*)/video/(.*)$ /view/$1/mp3/$2.mp3 break;
rewrite ^/capture_demo/(.*)/audio/(.*)*$ /view/$1/mp3/$2.rmvb break;
捕獲組
捕獲組 echo " 捕獲組1:$1;捕獲組2:$2";
}在瀏覽器中請(qǐng)求
http://crazydemo.com/capture_demo/group1/video/group2,輸出的結(jié)果如圖7-21所示。

圖7-21 輸出的結(jié)果
if條件指令
if條件指令配置項(xiàng)的格式如下:
if (condition) {...}當(dāng)if條件滿足時(shí),執(zhí)行配置塊中的配置指令。if的配置塊相當(dāng)于引入了一個(gè)新的上下文作用域。if條件指令適用于server和location兩個(gè)上下文。
condition條件表達(dá)式可以用到一系列比較操作符,大致如下:
(1)==:相等。
(2)!=:不相等。
(3)~:區(qū)分字母大小寫(xiě)模式匹配。
(4)~*:不區(qū)分字母大小寫(xiě)模式匹配。
(5)還有其他幾個(gè)專用比較符號(hào),比如判斷文件及目錄是否存在的符號(hào),等等。
下面是一個(gè)簡(jiǎn)單的演示程序,根據(jù)內(nèi)置變量$http_user_agent的值判斷客戶端的類型,代碼如下:
#if指令的演示程序
location /if_demo {
if ($http_user_agent ~*"Firefox") { #匹配Firefox瀏覽器
return 403;
}匹配谷歌瀏覽器
if ($http_user_agent ~*"Chrome") { #匹配Chrome谷歌瀏覽器
return 301;
}
if ($http_user_agent ~*"iphone") { #匹配iPhone手機(jī)
return 302;
}
if ($http_user_agent ~*"android") { #匹配安卓手機(jī)
return 404;
}
return 405; #其他瀏覽器默認(rèn)訪問(wèn)規(guī)則
}在火狐瀏覽器中訪問(wèn)
http://crazydemo.com/if_demo,結(jié)果如圖7-22所示。

圖7-22 火狐瀏覽器的訪問(wèn)結(jié)果
在谷歌瀏覽器中訪問(wèn)
http://crazydemo.com/if_demo,結(jié)果如圖7-23所示。

圖7-23 谷歌瀏覽器的訪問(wèn)結(jié)果
在演示代碼中使用到了return指令,用于返回HTTP的狀態(tài)碼。
return指令會(huì)停止同一個(gè)作用域的剩余指令處理,并返回給客戶端指定的響應(yīng)碼。
return指令可以用于server、location、if上下文中,執(zhí)行階段是rewrite階段。其指令的格式如下:
#格式一:返回響應(yīng)的狀態(tài)碼和提示文字,提示文字可選
return code [text];
#格式二:返回響應(yīng)的重定向狀態(tài)碼(如301)和重定向URL
return code URL;
#格式三:返回響應(yīng)的重定向URL,默認(rèn)的返回狀態(tài)碼是臨時(shí)重定向302
return URL;add_header指令
response header一般是以key:value的形式,例如Content-Encoding:
gzip、Cache-Control:no-store,設(shè)置的命令如下:
add_header Cache-Control no-store
add_header Content-Encoding gzip但是,有一個(gè)十分常用的response header為Content-Type,可以在它設(shè)置了類型的同時(shí)指定charset,例如text/html;charset=utf-8,由于其存在分號(hào),而分號(hào)在配置文件中作為結(jié)束符,因此在配置時(shí)需要用引號(hào)把其引起來(lái),配置如下:
add_header Content-Type 'text/html; charset=utf-8';另外,由于沒(méi)有單獨(dú)設(shè)置charset的key,因此要設(shè)置響應(yīng)的charset就需要使用Content-Type來(lái)指定charset。
使用AJAX進(jìn)行跨域請(qǐng)求時(shí),瀏覽器會(huì)向跨域資源的服務(wù)端發(fā)送一個(gè)OPTIONS請(qǐng)求,用于判斷實(shí)際請(qǐng)求是否安全或者判斷服務(wù)端是否允許跨域訪問(wèn),這種請(qǐng)求也叫作預(yù)檢請(qǐng)求。跨域訪問(wèn)的預(yù)檢請(qǐng)求是瀏覽器自動(dòng)發(fā)出的,用戶程序往往不知情,如果不進(jìn)行特別的配置,那么客戶端發(fā)出一次請(qǐng)求,在服務(wù)端往往會(huì)收到兩個(gè)請(qǐng)求;一個(gè)是預(yù)檢請(qǐng)求;另一個(gè)是正式的請(qǐng)求。后端的服務(wù)器(PHP或者Tomcat)如果不經(jīng)過(guò)特殊的過(guò)濾,那么很容易將OPTIONS預(yù)檢請(qǐng)求當(dāng)成正式的數(shù)據(jù)請(qǐng)求。
對(duì)于客戶端而言,只有預(yù)檢請(qǐng)求返回成功,客戶端才開(kāi)始正式請(qǐng)求。在實(shí)際的使用場(chǎng)景中,預(yù)檢請(qǐng)求比較影響性能,用戶往往會(huì)有兩倍請(qǐng)求的感覺(jué),所以一般會(huì)在Nginx代理服務(wù)端對(duì)預(yù)檢請(qǐng)求進(jìn)行提前攔截,同時(shí)對(duì)預(yù)檢請(qǐng)求設(shè)置比較長(zhǎng)時(shí)間的有效期。
upstream zuul {
#server 192.168.233.1:7799;
server "192.168.233.128:7799";
keepalive 1000;
}
server {
listen 80;
server_name nginx.server *.nginx.server;
default_type 'text/html';
charset utf-8;
#轉(zhuǎn)發(fā)到上游服務(wù)器,但是 'OPTIONS' 請(qǐng)求直接返回空
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Max-Age 1728000;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'Keep-Alive,User-Agent,X-Requested-With,\
If-Modified-Since,Cache-Control,Content-Type,token';
return 204;
}
proxy_pass http://zuul/ ;
}
}配置Nginx,加入Access-Control-Max-Age請(qǐng)求頭,用來(lái)指定本次預(yù)檢請(qǐng)求的有效期,單位為秒。上面結(jié)果中的有效期是20天(1 728 000秒),即允許緩存該條回應(yīng)1 728 000秒,在此期間客戶端不用發(fā)出另一條預(yù)檢請(qǐng)求。
?指令的執(zhí)行順序
大多數(shù)Nginx新手都會(huì)頻繁遇到這樣一個(gè)困惑:當(dāng)同一個(gè)location配置塊使用了多個(gè)Nginx模塊的配置指令時(shí),這些指令的執(zhí)行順序很可能會(huì)跟它們的書(shū)寫(xiě)順序大相徑庭?,F(xiàn)在就來(lái)看這樣一個(gè)令人困惑的例子:
location /sequence_demo_1 {
set $a foo;
echo $a;
set $a bar;
echo $a;
}上面的代碼先給變量$a賦值foo,隨后輸出,再給變量$a賦值bar,隨后輸出。如果這是一段Java代碼,毫無(wú)疑問(wèn),最終的輸出結(jié)果一定為“foo bar”。然而不幸的是,事實(shí)并非如此,在瀏覽器中訪問(wèn)
http://crazydemo.com/sequence_demo_1,結(jié)果如圖7-24所示。

圖7-24 輸出的結(jié)果
為什么出現(xiàn)了這種不合常理的現(xiàn)象呢?
前面講到,Nginx的請(qǐng)求處理階段共有11個(gè),分別是post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content以及l(fā)og。其中3個(gè)比較常見(jiàn)的按照?qǐng)?zhí)行時(shí)的先后順序依次是rewrite階段、access階段以及content階段。
Nginx的配置指令一般只會(huì)注冊(cè)并運(yùn)行在其中的某一個(gè)處理階段,比如set指令就是在rewrite階段運(yùn)行的,而echo指令只會(huì)在content階段運(yùn)行。在一次請(qǐng)求處理流程中,rewrite階段總是在content階段之前執(zhí)行。因此,屬于rewrite階段的配置指令(示例中的set)總是會(huì)無(wú)條件地在content階段的配置指令(示例中的echo)之前執(zhí)行,即便是echo配置項(xiàng)出現(xiàn)在set配置項(xiàng)的前面。
上面例子中的指令按照請(qǐng)求處理階段的先后次序排序,實(shí)際的執(zhí)行次序如下:
location /sequence_demo_1 {
#rewrite階段的配置指令,執(zhí)行在前面
set $a foo;
set $a bar;
#content階段的配置指令,執(zhí)行在后面
echo $a;
echo $a;
}所以,輸出的結(jié)果就是bar bar了。
本文給大家講解的內(nèi)容是Nginx/OpenResty詳解,Nginx的rewrite模塊指令
下篇文章給大家講解的是 Nginx/OpenResty詳解,反向代理與負(fù)載均衡配置;
覺(jué)得文章不錯(cuò)的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒(méi)有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號(hào)里找我,我等你哦。
