Nginx:rewrite / if / return / set 和變量 (轉載)

 原始鏈接 https://www.cnblogs.com/Dy1an/p/11240223.html

我們前面已經談了編譯安裝,基本語法,日誌處理,location 匹配,root / alias 的不同效果。這裏我們主要談談 rewrite(重寫)功能,順便說說 nginx 中自帶的變量。在談日誌格式的時候我們已經聊了一些,這裏做個補充。

 

 

指令:rewrite

 

rewrite 的實現依賴於我們編譯的時候的 PCRE 庫,我們可以通過 rewrite 功能實現將 URL 重寫的功能。

總的來說,rewrite 能夠實現以下:

1. 用戶請求到達某個 server ,如果滿足 server 內部的 rewrite 的正則匹配,那麼 rewrite 將會對用戶請求 URI 重寫。

2. 重寫完成後直接在該 server 內部去匹配 location。

3. 當匹配到 location 後,如果 location 內部又有 rewrite,那執行 rewrite 後再次在這個 server 內部去匹配 location,直到請求返回。

4. 當然這個過程不是無限的,nginx 對於這樣的跳轉就支持 10 次,如果過多甚至死循環,則會報 500 錯誤。

 

基本語法格式:

rewrite regex replacement [flag];

 

說明:

這裏的使用  regex 匹配 URI,並將匹配到的 URI 替換成新的 URI(replacement)。如果有多個 rewrite,執行順序是從上到下依次執行,匹配到一個後匹配並不會終止,會繼續匹配下去,直到返回最後一個匹配爲止。如果想中途終止,則需要設置 flag 參數。

當然上面說的都是重寫 URI,如果 placement 中包含了任何協議相關,如:http:// 和 https://,則請求就直接返回 302 重定向終止了。

當然,瀏覽器在接收到 30x 的狀態碼後,會再度根據這個返回去請求 rewrite 之後的地址,最終得到所要想要的結果。如果不是 30x 的狀態碼,則屬於 nginx 內部跳轉,瀏覽器不需要再度發起請求。

 

在 rewrite 中有 4 個 flag 參數:

參數說明
last 停止所有 rewrite 相關指令,然後使用新的 URI 進行 location 匹配。
break

停止所有 rewrite 相關指令, 和 last 不同的是,last 接着繼續使用新的 URI 匹配 location。

而 break 則是直接使用當前的 URI 進行請求處理,能避免重複 rewrite,last 一般在 server,break 一般在 location。

redirect URI 中不包含協議如 https://,但依然希望它返回 30x,讓瀏覽器二次請求然後獲取到結果就需要 redirect。
permanent 和 redirect 類似,但是直接返回 301 永久重定向。

 

在 rewrite 中常用的正則:

表達式作用
. 匹配換行符以外任意字符
? 重複0次或者1次
+ 重複1次或多次
* 重複0次或者多次
\d 匹配數字
^ 匹配開始
$ 匹配結束
{n} 重複 n 次
{n,} 重複 n 次或更多次
[c] 匹配單個字符c
[a-z] 匹配 a-z 任意一個小寫字母

使用 () 可以將匹配內容括起來,後面使用 $1 來引用,當然,第二個 () 就是 $2。

 

rewrite 示例:

示例1:直接跳轉到其它 URL,但是將參數帶過去

在 vhosts 下面新建:rewrite-demo.conf 

server {
    listen      8083;
    server_name localhost;
    rewrite_log  on;
    rewrite  ^/(.*) https://www.ezops.com/$1 permanent;
    error_log    /data/logs/nginx/rewrite-error.log;
    access_log   /data/logs/nginx/rewrite-access.log mylog;
}

我們這裏開啓 rewrite log,這樣定向錯誤會記錄到 error_log 中。配置完成後重載 nginx,訪問:

http://192.168.100.111:8083/aaa/bbb

結果如下:

 

示例2:測試 last 和 break,修改剛剛的配置,這裏我們用到 nginx 自帶變量 uri

server {
    listen      8083;
    server_name localhost;
    rewrite_log  on;
    rewrite  ^/(.*) /hello/$1 last;
    error_log    /data/logs/nginx/rewrite-error.log;
    access_log   /data/logs/nginx/rewrite-access.log mylog;

    location ^~ /hello {
        echo "URI 1: $uri";
        rewrite ^/hello/(.*)  /world/$1 last;
        echo "URI 2: $uri";
    }

    location ^~ /world {
        echo "URI 3: $uri";
    }
}

重載訪問:

此時把 location 中 last 改爲 break 測試:

可以發現:

如果 rewrite 是 last 作 flag 並不影響接下來繼續去匹配 location,且該 location 下面就執行了 rewrite 操作,其它的都沒有執行到。

但是當 break 作 flag 時,rewrite 就終止於目前的這個 location 了,在完成重寫 URI 之後就開始執行該 location 下面的其他操作了。

 

示例3:參數後面 ? 的作用測試,修改配置,我們用到另外一個變量 args

server {
    listen      8083;
    server_name localhost;
    rewrite_log  on;
    error_log    /data/logs/nginx/rewrite-error.log;
    access_log   /data/logs/nginx/rewrite-access.log mylog;

    location ^~ /hello {
        rewrite ^/(.*)  /world/?from=$1 break;
        echo "URI: $uri";
        echo "ARG: $args";
    }
}

訪問結果如下:

修改 rewrite 配置,添加 ?:rewrite ^/(.*)  /world/?from=$1? break; 重載訪問:

可以發現:

如果 replacement 中包含參數,那默認舊 URI 中的請求參數也會拼接到 replacement 後面作爲新的 URI,如果不希望這樣,只需在 replacement 的後面加上 ?。

 

 

指令:set

 

我們一直都在說,nginx 爲我們提供了很多的內部變量,但是有些時候這些變量並不能滿足我們的需求,我們需要其它的一些自定變量來協助我們完成一個比較複雜的需求。set 就是這樣一個指定,用來定義屬於我們自己的變量,它的基本語法如下:

set $variable value;

舉個例子,我們在 vhosts 下新增配置:set-demo.conf

server {
    listen      8084;
    server_name localhost;
    set $STEP 1;

    location / {
        set $STEP $STEP-2;
        echo $STEP;
    }
}

重載配置,訪問測試:

 

 

指令:if 和 try_files

 

在 nginx 中,我們也可以像在其它編程語言一樣添加邏輯判斷,其中就有 if 和 try_files,if 一般在舊版中使用,但是新版中並不影響。語法格式:

if (判斷條件) {...}

 

判斷只能在 server 和 location 中使用。

1. 當判斷條件只是一個變量的時候,只有該變量的值爲空或者 0 的時候才爲 false。

2. 變量可以通過 = 或者 != 來判斷,如:$var = 123。

3. 判斷條件裏面也可以是一個正則匹配,如:$var ~ regex。

4. 其它的一些文件,目錄校驗符號,如:-d / -f / -e / -x。

 

文件校驗符如下:

符號作用
-f 檢驗文件是否存在,可以取反:!-f
-d 檢驗目錄是否存在
-e 檢驗文件/目錄/鏈接文件是否存在
-x 檢驗文件是否爲可執行文件

 

舉個例子測試,在 vhosts 目錄下創建:if-demo.conf

server {
    listen      8085;
    server_name localhost;
    set $VAR 1;
    set $STEP $VAR;
    
    location / {
        if ($VAR) {
            echo "URL 0: VARIABLE TEST 0";
            set $STEP $STEP-0;
        }
    
        if ($VAR = 1) {
            echo "URL 1: VARIABLE TEST 1";
            set $STEP $STEP-1;
        }
        
        if ($http_user_agent ~* Mozilla) {
            echo "URL 2: BROWSER";
            set $STEP $STEP-2;
        }
        
        if ($http_user_agent ~ curl) {
            echo "URL 3: COMMAND";
            set $STEP $STEP-3;
        }
        
        if (-f /tmp/test.txt) {
            echo "URL 4: FILE EXIST";
            set $STEP $STEP-4;
        }
        
        if (!-f /tmp/test.txt) {
            echo "URL 5: FILE NOT EXIST";
            set $STEP $STEP-5;
            echo "STEP: $STEP";
        }
    }
}

我們定義了兩個變量,VAR 和 STEP,VAR 用於測試判斷,STEP 用於記錄執行了哪些 if,重載訪問測試:

可以發現:

我們明明執行了 0 1 3 5 這 4 個 if 判斷,但是真正執行的 echo 的卻只有最後一個 5。

if 常常被我們用來做客戶端驗證,比如我們一個網站,如果是電腦打開,我們讓他跳轉到電腦版,手機打開跳轉到手機版。

try_files 其實就是 if 語句精簡版,但是個人其實更喜歡 if 一點,所有對 try_files 感興趣的可以詳細的瞭解一下,我們這裏舉個簡單的例子:

location  / {
    root      /data/www/demo;
    index     index.html index.htm;
    try_files $uri $uri/ @rewrites;
}
  
location @rewrites {
    rewrite ^(.+)$ /index.html last;
}

比如:用戶訪問 http://192.168.100.111/hello/world,那麼 $uri 就是 /hello/world,那麼 try_files 就會去指定的 root 下查找這個文件是否存在,如果存在則直接返回,如果不存在就訪問第二個參數,還不存在就繼續,直到最後一個參數,我們把它跳轉到對應的 location 上面。在 try_files 會自行判斷是文件還是目錄。

雖然很方便,但是個人還是更喜歡 if 一些!

注意:

在 if 中不支持嵌套,也不支持 else,嵌套 if 可以使用多個 if 來實現它。

 

 

指令:return

 

停止一切處理,返回結果給客戶端,如果返回的狀態碼是 444,則斷開 TCP 連接,不發送任何東西。

可以使用的狀態碼有:204,400,402-406,408,410, 411, 413, 416 與 500-504。

如果不帶狀態碼直接返回 URL 則被視爲 302。簡單示例:

在 vhosts 下面新建:return-demo.conf

server {
    listen      8086;
    server_name localhost;

    location / {
        if ($http_user_agent ~ curl) {
            return 200 'COMMAND USER\n';
        }   
        if ($http_user_agent ~ Mozilla) {
            return 302 http://www.baidu.com?$args;
        }      
        return 404;
    }
}

命令行測試:

瀏覽器訪問:http://192.168.100.111:8086/hello?user=world

 

 

綜合示例

 

我們這裏做一個結合前面的知識點一起完成的一個示例:

server {
    listen      8087;
    server_name localhost;
    default_type text/html;    

    # 默認來源 PC / 移動端
    set $MACHINE pc;
    if ( $http_user_agent ~* "(mobile|nokia|iphone|ipad|android|samsung|htc|blackberry)" ){
        set $MACHINE mobile;
    }

    # 多重判斷變量設計
    set $FROM pc;
    set $TAG '';
    
    location ^~ /pc/ {
        # 拼接來源和請求 URI 頭
        set $FROM pc;
        set $TAG $MACHINE-$FROM;
        
        # 根據來源和 URI 頭採取不同的處理
        if ($TAG = pc-pc) {
            rewrite '^/pc/([a-z0-9]{2})/([a-z0-9]{2})/(.*)\.(png|jpg|gif)$'  /data?file=$3.$4 last;
        }
        if ($TAG = mobile-pc) {
            return 302 http://m.jd.com?$args;
        }
        return 200 '<h1>PC INDEX WEB!</h1>';
    }
    
    location ^~ /mobile/ {
        # 拼接來源和請求 URI 頭
        set $FROM mobile;
        set $TAG $MACHINE-$FROM;
        
        # 根據來源和 URI 頭採取不同的處理
        if ($TAG = pc-mobile) {
            return 302 http://www.baidu.com?$args;
        }
        if ($TAG = mobile-mobile) {
            return 200 '<h1>WELCOME TO MOBILE WEB!</h1>';
        }
        return 200 '<h1>MOBILE INDEX WEB!</h1>';
    }
    
    location ^~ /data {
        root    /data/www/images;
        try_files /$arg_file /image404.html;
    }

    location = /image404.html {
        return 200 '<h1>IMAGE NOT FOUND!</h1>';
    }
    
    # 默認處理
    location / {
        if ($MACHINE = pc) {
            return 200 '<h1>PC DEFAULT WEB</h1>';
        }
        if ($MACHINE = mobile) {
            return 200 '<h1>MOBILE DEFAULT WEB</h1>';
        }
    }
}

 

說明:

1. 我們將訪問區分電腦端和移動端,當電腦端訪問默認端口的時候顯示:

 

2. 當 uri 部分以 /pc/ 開頭,則返回電腦的默認頁面:

 

3. 當電腦端訪問 /mobile/ 開頭並帶有參數,則保留參數跳轉到百度,如訪問: http://192.168.100.111:8087/mobile/test?name=hello

 

4. 我們新建 /data/www/images 目錄並上傳 login.png 作爲測試,此時電腦端訪問指定匹配規則的圖片返回圖片: 

 

5. 訪問不存在的圖片報錯:

 

6. 移動端一個意思,這裏就不做演示。

7. 配置中包含了多重判斷,我們使用一箇中間變量了存儲作爲判斷區分。

8. 最後值得注意的是,我們使用 return 返回 HTML 文本,需要定義默認類型:default_type text/html; 否則會變成下載文件。

 

 

自帶變量

 

nginx 自帶了很多變量,可以參考如下表:我們以請求 http://www.baidu.com/hello/world?name=dylan&age=25 爲例

變量說明
$args 請求中的完整參數。就是示例中:name=dylan&age=25
$arg_PARAMETER 獲取指定參數,如:$arg_name,就能獲取到 name 參數
$binary_remote_addr 二進制客戶端地址,如:\x0A\xE0B\x0E
$body_bytes_sent 向客戶端發送 HTTP 響應中包體部分的字節數,前面日誌中用過
$content_length 客戶端請求頭部中 Content_Length 字段
$content_type 客戶端請求頭部中 Content_Type 字段
$cookie_COOKIE 獲取指定 cookie 的值
$document_root 當前請求所使用的 root 配置項的值
$uri 當前請求的 uri,不帶任何參數
$document_uri 與 uri 含義相同
$request_uri 原始請求 uri,帶完整的參數。$uri 和 $document_uri 可能是內部重定向後的。
$host 請求頭部 Host 字段,字段不存在,則以實際 server(虛擬主機)名稱代替。
$hostname nginx 所在機器的名稱。
$http_HEADER 當前 HTTP 請求相應頭部的值,全小寫
$sent_http_HEADER 返回客戶端 HTTP 響應中相應頭部的值
$is_args 請求中的 uri 是否帶參數,如果帶參數,值爲 ?,如果不帶參數,值爲空
$limit_rate 當前連接限速爲多少,0 表示不限速
$nginx_version 當前 nginx 的版本號
$query_string 請求 uri 中的參數,與 args 相同,只讀的
$remote_addr 客戶端地址
$remote_port 客戶端連接使用端口
$remote_user 客戶端連接使用賬戶,使用 auth basic module 時定義的用戶名
$request_filename 請求中 uri 經過 root 或 alias 轉換以後的路徑
$request_body HTTP 請求中的包體,該參數只在 proxy_pass 或 fastcgi_pass 中有意義
$request_body_file HTTP 請求中的包體存儲的臨時文件名
$request_completion 請求全部完成時,值爲"ok",若沒完成,就返回客戶端,值爲空
$request_method HTTP 請求的方法名,如get,put,post
$scheme scheme,如請求:https://www.baidu.com,值爲 https
$server_addr 服務器地址
$server_name 服務器名稱
$server_port 服務器端口
$server_protocol 服務器向客戶端發送響應的協議,如 HTTP/1.1 或者 HTTP/1.0 表示不限速

紅色部分爲常用的變量!

 

 

小結

 

rewrite 是一個很實用的功能,能夠解決我們很多問題,但是同時我們又不推薦把這個做的太爲複雜,不適合維護。同樣,if set 也是我們日常用的比較多的。這其中牽扯到正則表達式。可能需要多花點時間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章