1.開篇
rewrite是nginx伺服器提供的一個重要功能,用于實作URL的重寫。例如我們通路https://aa.qq.com,打開的是https://age.qq.com/,這就是使用URL重寫的特性來實作的。
ngx_http_rewrite_module為實作URL重寫提供了指令支援。
官方文檔位址:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html
接下來我們來看看rewrite的相關指令。
2.rewrite相關指令
2.1 set指令
作用域:server, location, if
文法:set $variable value;
該指令可以設定一個變量。
$variable:為變量的名稱,可以看到變量的名稱以$符号開頭,且不要與nginx預設的全局變量名相同。
value:為變量的值,可以是字元串、其他變量或者兩者的組合。
既然自定義的變量名不能與nginx的全局變量名相同,那就有必要了解使用rewrite功能時常用的nginx全局變量。
rewrite常用全局變量
全局變量 | 說明 |
$args | 用于擷取請求中的參數。例如:http://192.168.110.92/test?name=tom&age=18,$args的值就為name=tom&age=18 |
$host | 用于擷取請求中的主機部分的值。例如:http://192.168.110.92/test?name=tom&age=18,$host的值為192.168.110.92。 |
$http_user_agent | 用于擷取請求中的User-Agent字段值 |
$remote_addr | 用于擷取用戶端的IP位址 |
$remote_port | 用于擷取用戶端與伺服器建立連接配接的端口号 |
$request_method | 用于擷取用戶端的請求方式,例如GET、POST等 |
$request_uri | 用于擷取目前請求的URI。例如:http://192.168.110.92/test?name=tom&age=18,$request_uri的值就為/test?name=tom&age=18 |
$query_string | 與$args作用相同 |
$scheme | 用于擷取用戶端請求使用的協定,例如http、https等 |
$server_addr | 用于擷取服務端的IP位址 |
$server_name | 用于擷取虛拟主機的名稱 |
$server_port | 用于擷取虛拟主機的監聽的端口 |
$document_uri | 用于擷取請求中的目前URI。例如:http://192.168.110.92/test?name=tom&age=18,$document_uri的值為/test |
$uri | 與$document_uri作用相同 |
$http_user_agent | 用于擷取請求頭中User-Agent字段的值 |
$request_filename | 目前請求的檔案路徑 |
下面我們設定自定義變量,順便體驗使用一下這些全局變量。
location /test {
default_type text/html;
set $username zhangsan;
return 200 <html>><p>username:$username</p><p>request_uri:$request_uri</p><p>document_uri:$document_uri</p><p>uri:$uri</p><p>query_string:$query_string</p><p>args:$args</p></html>;
}
發起請求:http://192.168.110.98/test?name=tom
2.2 if指令
作用域:server, location
文法:if (condition) { ... }
如果條件表達式為true,則執行該子產品大括号中的指令。
Tips:if和(之間有一個空格。
條件表達式有幾種形式:
1)變量名,如果變量的值為空字元串或"0",則為false,其他條件為true。
if ($variable){
}
2)使用"="和"!="比較變量和字元串是否相等,滿足條件則為true,否則為false。
if ($request_method = POST){
}
3)使用正規表達式與變量的值進行比對。變量與正規表達式之間使用~、~*、!~、!~*,如果正規表達式包含}或;,則整個表達式應該用單引号或雙引号括起來。
- ~:表示比對正規表達式,區分大小寫
- ~*:表示比對正規表達式,不區分大小寫
- !~:表示比對正規表達式,區分大小寫,并對比對後的結果取反
- !~*:表示比對正規表達式,不區分大小寫,并對比對後的結果取反
if ($http_user_agent ~ Mozilla/5.0){
}
4)判斷檔案是否存在:-f和!-f
if (-f $request_filename){
}
if (!-f $request_filename){
}
示例:
location /test {
default_type text/html;
if (!-f $request_filename){
return 200 "<h1>file not exist</h1>";
}
root html;
}
我們已經在html目錄下準備了一個test.html。
通路http://192.168.110.98/test.html,可以正常顯示。
通路http://192.168.110.98/test,因為檔案不存在,是以執行if條件塊的指令。
5)判斷目錄是否存在:-d和!-d
6)判斷檔案、目錄或符号連結是否存在:-e和!-e
7)判斷檔案是否可以執行:-x和!-x
2.3 break指令
作用域:server, location, if;
文法:break;
在同一作用域中,中斷該指令之後的其他指令,位于其前面的指令配置生效,位于其後面的指令配置則無效。
示例:如果URL中存在參數,則執行if邏輯。
location /testBreak {
default_type text/plain;
set $username lisi;
if ($args){
set $username wangwu;
break;
set $username zhaoliu;
}
add_header username $username;
return 200 $username;
}
通路http://192.168.110.98/testBreak,指令都正常執行。
通路http://192.168.110.98/testBreak?name=zhangsan,說明執行了if邏輯。
按break;語句的定義來說,在其執行後,其作用域外後面的指令應該正常執行才對,但是這裡直接傳回了404。這個時候,就需要我們檢視error.log。
可以發現,錯誤提示為檔案未找到,根據錯誤提示,我們需要在html目錄下建立一個testBreak目錄,然後在testBreak目錄下建立一個index.html檔案
cd /usr/local/nginx
mkdir testBreak
vim index.html
<html>
<body>this is testBreak</body>
</html>
再次通路http://192.168.110.98/testBreak?name=zhangsan,可以看到break語句執行後,其作用域外後面的指令正常執行。
2.4 return指令
作用域:server, location, if
文法:return code [text]; return code URL; return URL;
該指令可以停止處理并指定的響應碼傳回給前端。既可以傳回文本,也可以重定向URL。
示例:
location /testReturn {
default_type text/plain;
return 200 "test return";
}
location /testReturn {
return 302 https://www.baidu.com;
}
location /testReturn {
return https://www.baidu.com;
}
2.5 rewrite指令
在了解set、if、break、return指令後,重頭戲rewrite指令登場。
作用域:server, location, if;
文法:rewrite regex replacement [flag];
regex:用來比對URI的正規表達式。
replacement:正則比對成功後,用來替換URI的字元串。如果該字元串以http://、https://或$scheme開頭,則處理将停止,并重定向URI到用戶端。
flag:是一個可選參數,其有4個候選值。
flag值 | 說明 |
last | 停止處理rewrite指令,并使用重寫的URI去與各個location進行比對 |
break | 停止處理rewrite指令,與break;效果一緻 |
redirect | 如果replacement字元串不是以http://、https://或$scheme開頭,則重定向到重寫的URI,響應碼為302 |
permanent | 重定向到重寫的URI,響應碼為301 |
rewrite指令通過正規表達式比對URI,并修改URI。可同時存在多個rewrite指令,按照順序依次對URI進行比對和處理。
示例:
location /rewrite {
rewrite ^/rewrite/aaa\w+$ https://www.baidu.com;
rewrite ^/rewrite/(bbb)\w+$ /$1 last;
rewrite ^/rewrite/(ccc)\w+$ /$1 break;
rewrite ^/rewrite/(ddd)\w+$ /$1 redirect;
rewrite ^/rewrite/(eee)\w+$ /$1 permanent;
}
location /bbb {
default_type text/plain;
return 200 "this is bbb";
}
location /ccc {
default_type text/plain;
return 200 "this is ccc";
}
location /ddd {
default_type text/plain;
return 200 "this is ddd";
}
location /eee {
default_type text/plain;
return 200 "this is eee";
}
2.6 rewrite_log指令
作用域:http, server, location, if
文法:rewrite_log on | off;
預設值:rewrite_log off;
該指令可以配置是否将ngx_http_rewrite_module指令的處理結果以notice級别的日志寫入到error_log中。
示例:
location /rewrite {
# 開啟rewrite_log
rewrite_log on;
# 配置error_log
error_log logs/error.log notice;
rewrite ^/rewrite/aaa\w+$ https://www.baidu.com;
rewrite ^/rewrite/(bbb)\w+$ /$1 last;
rewrite ^/rewrite/(ccc)\w+$ /$1 break;
rewrite ^/rewrite/(ddd)\w+$ /$1 redirect;
rewrite ^/rewrite/(eee)\w+$ /$1 permanent;
}
這樣我們就可以在error.log中看到notice級别的日志。
3.使用場景
在熟悉了ngx_http_rewrite_module的相關指令後,我們來看看rewrite的相關使用場景。
3.1 域名重定向
場景:公司官網上線的時候位址為www.aaa.com,随着公司的不斷發展,需要将官網位址更新為www.bbb.com,但是需要在通路www.aaa.com能夠自動跳轉到www.bbb.com。
Tips:www.aaa.com和www.bbb.com需要指向同一IP。
解決方案:使用rewrite指令重寫URI。
server {
listen 80;
server_name www.aaa.com;
rewrite ^(.*) https://www.bbb.com$1;
}
3.2 優雅處理防盜鍊
前面我們在【Nginx靜态資源防盜鍊】一文中已經簡單的實作了靜态資源的防盜鍊,但是展示在頁面的是一個裂開的小圖檔,不夠美觀。
我們可以如下配置:
location ~^/.*\.(png|jpg|gif|jfif) {
valid_referers www.example.com;
if ($invalid_referer){
rewrite ^/ http://192.168.110.98/images/forbidden.png;
}
root html;
}
如果出現盜鍊的情況,将會出現類似于如下效果:
以上就是Nginx之rewrite實作URL重寫,Nginx是多子產品化的,還有很多進階功能,我們後面繼續探索。