天天看點

前端開發掌握nginx常用功能之rewrite

上一篇博文對nginx最常用功能的server及location的比對規則進行了講解,這也是nginx實作控制通路和反向代理的基礎。掌握請求的比對規則算是對nginx有了入門,但是這些往往還是不能滿足實際的需求場景,例如請求url重寫、重定向等等,這都需要對請求的<code>path</code>進行修改操作的,比對規則是不能獨自完成實際需求的,這就需要掌握nginx的另一個常用功能rewrite,下面就來說說這個常用功能。

rewrite功能就是,使用nginx提供的全局變量或自己設定的變量,結合正規表達式和标志位實作url重寫以及重定向。

rewrite隻能放在<code>server{}</code>, <code>location{}</code>, <code>if{}</code> 中,并且隻能對域名後邊傳遞的參數外的字元串起作用,例如 http://baidu.com/a/we/index.php?id=1&amp;u=str 隻對/a/we/index.php重寫。文法:

rewrite regex replacement [flag];

如果相對域名或參數字元串起作用,可以使用全局變量比對,也可以使用proxy_pass反向代理。

表面上看rewrite和location功能有點像,都能實作跳轉,主要差別在于rewrite是在同一域名内更改擷取資源的路徑,而location是對一類路徑做控制通路或反向代理,可以proxy_pass到其他機器。很多情況下rewrite也會寫在location裡,它們的執行順序是:

執行server塊的rewrite指令

執行location比對

執行標明的location中的rewrite指令

如果其中某步URI被重寫,則重新循環執行1-3,直到找到真實存在的檔案;循環超過10次,則傳回500 Internal Server Error錯誤。

<code>last</code> : 停止執行目前<code>ngx_http_rewrite_module</code>的指令集,但是會繼續走一遍請求比對對應server或者location;

<code>break</code> : 停止執行目前<code>ngx_http_rewrite_module</code>的指令集,請求就此完成。

<code>redirect</code> : 傳回302臨時重定向,位址欄會顯示跳轉後的位址

<code>permanent</code> : 傳回301永久重定向,位址欄會顯示跳轉後的位址

因為301和302不能簡單的隻傳回狀态碼,還必須有重定向的URL,這就是return指令無法傳回301,302的原因了。

對于上面的flag,有幾點需要強調一下:

<code>last</code>與<code>break</code>對url的重寫不會改變位址欄的位址

也就是說,nginx雖然對請求url進行了重寫,但是位址欄不會有任何明顯的改變,仍然顯示nginx重寫前的位址;這與<code>redirect</code>和<code>permanent</code>不同。

<code>last</code>與<code>break</code>的處理政策不同

二者都會終止目前<code>ngx_http_rewrite_module</code>的指令集的執行,但是 <code>last</code> 立即發起新一輪的 請求比對 而 <code>break</code> 則不會。

<code>redirect</code>和<code>permanent</code>會終止後續nginx指令的執行

nginx在rewrite遇到flag是二者時,後續的指令是不會執行的。

上面例子中,我們通路 curl 127.0.0.1:8080/test,會發現,return 200 $request_uri語句沒有執行,而<code>proxy_pass</code>指令被執行了。這是因為:

<code>return</code>指令屬于ngx_http_proxy_module子產品,它會被break終止掉;而rewrite子產品它是ngx_http_proxy_module的指令,不會被<code>break</code>給中斷掉。

if判斷指令

文法為<code>if(condition){...}</code> ,對給定的條件condition進行判斷。如果為真,大括号内的rewrite指令将被執行,if條件(conditon)可以是如下任何内容:

當表達式隻是一個變量時,如果值為空或任何以0開頭的字元串都會當做false

直接比較變量和内容時,使用<code>=</code>或<code>!=</code>

<code>~</code>正規表達式比對,<code>~*</code>不區分大小寫的比對,<code>!~</code>區分大小寫的不比對

<code>-f</code>和<code>!-f</code>用來判斷是否存在檔案

<code>-d</code>和<code>!-d</code>用來判斷是否存在目錄

<code>-e</code>和<code>!-e</code>用來判斷是否存在檔案或目錄

<code>-x</code>和<code>!-x</code>用來判斷檔案是否可執行

例如:

下面是可以用作if判斷的全局變量:

<code>$args</code> : #這個變量等于請求行中的參數,同$query_string

<code>$content_length</code> : 請求頭中的Content-length字段。

<code>$content_type</code> : 請求頭中的Content-Type字段。

<code>$document_root</code> : 目前請求在root指令中指定的值。

<code>$host</code> : 請求主機頭字段,否則為伺服器名稱。

<code>$http_user_agent</code> : 用戶端agent資訊

<code>$http_cookie</code> : 用戶端cookie資訊

<code>$limit_rate</code> : 這個變量可以限制連接配接速率。

<code>$request_method</code> : 用戶端請求的動作,通常為GET或POST。

<code>$remote_addr</code> : 用戶端的IP位址。

<code>$remote_port</code> : 用戶端的端口。

<code>$remote_user</code> : 已經經過Auth Basic Module驗證的使用者名。

<code>$request_filename</code> : 目前請求的檔案路徑,由root或alias指令與URI請求生成。

<code>$scheme</code> : HTTP方法(如http,https)。

<code>$server_protocol</code> : 請求使用的協定,通常是HTTP/1.0或HTTP/1.1。

<code>$server_addr</code> : 伺服器位址,在完成一次系統調用後可以确定這個值。

<code>$server_name</code> : 伺服器名稱。

<code>$server_port</code> : 請求到達伺服器的端口号。

<code>$request_uri</code> : 包含請求參數的原始URI,不包含主機名,如:”/foo/bar.php?arg=baz”。

<code>$uri</code> : 不帶請求參數的目前URI,$uri不包含主機名,如”/foo/bar.html”。

<code>$document_uri</code> : 與$uri相同。

<code>.</code> : 比對除換行符以外的任意字元

<code>?</code> : 重複0次或1次

<code>+</code> : 重複1次或更多次

<code>*</code> : 重複0次或更多次

<code>\d</code> :比對數字

<code>^</code> : 比對字元串的開始

<code>$</code> : 比對字元串的結束

<code>{n}</code> : 重複n次

<code>{n,}</code> : 重複n次或更多次

<code>[c]</code> : 比對單個字元c

<code>[a-z]</code> : 比對a-z小寫字母的任意一個

小括号<code>()</code>之間比對的内容,可以在後面通過<code>$1</code>來引用,<code>$2</code>表示的是前面第二個()裡的内容。正則裡面容易讓人困惑的是<code>\</code>轉義特殊字元。

例1:

對形如<code>/images/ef/uh7b3/test.png</code>的請求,重寫到<code>/data?file=test.png</code>,于是比對到<code>location /data</code>,先看<code>/data/images/test.png</code>檔案存不存在,如果存在則正常響應,如果不存在則重寫tryfiles到新的image404 location,直接傳回404狀态碼。

例2:

對形如<code>/images/bla_500x400.jpg</code>的檔案請求,重寫到<code>/resizer/bla.jpg?width=500&amp;height=400</code>位址,并會繼續嘗試比對location。

例3:

見 ssl部分頁面加密 。

上面說過,rewrite的指令規則為:<code>rewrite regex replacement [flag];</code>

rewrite指令用指定的regex來比對請求的uri,若比對成功則用replacement來重寫請求uri。這裡需要注意的replacement字元串的内容:

1、 若replacement以<code>http://</code>、<code>https://</code>或者<code>$scheme</code>開頭,則告訴nginx這是重定向操作(flag預設為redirect),nginx則停止處理後續内容,并直接重定向傳回給用戶端。

2、replacement非以上三種情況開頭,則就是簡單的url重寫

對于上面兩種情況,還需要特别留意一個redirect端口的問題,先上一個例子:

當通路http://server.com/test1/index.html時,會命中/test1的location規則,通路server1.com對應内容一直失敗,發現重定向後響應頭的<code>Location</code>字段值為http://server1.com:8000/demo/test1,帶有8000端口,我們并沒有配置,表現的比較詭異?

通路http://server.com/test2/index.html時,命中/test2的location規則,同樣通路失敗,但是通路的重定向後響應頭<code>Location</code>字段值為http://server.com:8000/demo/test2,其帶有server.com的server_name和8000的端口,更加詭異?

看到上面的現象,疑惑重重;其實這跟nginx的<code>server_name_in_redirect</code>和<code>port_in_redirect</code>指令有關:

在絕對路徑中,<code>server_name_in_redirect</code> 和<code>port_in_redirect</code> 指令表示是否将server塊中的 server_name 和 listen 的端口作為redirect用, 重定向的完整url位址根據<code>$scheme</code>跟<code>server_name_in_redirect</code>和<code>port_in_redirect</code>來确定的。

在絕對路徑中,<code>server_name_in_redirect</code>預設是禁用的,而<code>port_in_redirect</code>是預設啟用的。對于帶有<code>$scheme</code>重定向的絕對路徑,nginx會從replacement中擷取指定的server_name和port來進行重定向:

第一種,若replacement帶請求協定http(s),而其中沒有指定port的話,nginx會預設取目前server的listen端口作為重定向的端口。這是上面通路http://server.com/test1/index.html時重定向到http://server.com:8000/demo/test2時會攜帶8000的原因。

第二種,若replacement不帶請求協定http(s),而是相對本地伺服器的絕對位址的話,如上面通路http://server.com/test2/index.html的情況,此時<code>server_name_in_redirect</code>由于禁用它會去請求的host來作為server_name,取目前server的listen端口作為重定向的端口,最終重定向到http://server.com:8000/demo/test2。

或許你會問,通路http://server.com/test2/index.html為什麼不會重定向到http://192.168.1.3:8000/demo/test2上?這是因為rewrite的redirect flag會終止後續指令的執行,是以其後的<code>proxy_pass</code>指令不會執行。

http://www.nginx.cn/216.html

http://www.ttlsa.com/nginx/nginx-rewriting-rules-guide/

https://segmentfault.com/a/1190000008102599