Nginx 重寫規則指南

默北發表於2015-04-09

當運維遇到要重寫情況時,往往是要程式設計師把重寫規則寫好後,發給你,你再到生產環境下配置。對於重寫規則說到底就是正則匹配,做運維的豈能對正規表示式不瞭解的?最起碼最基本的正規表示式會寫。套用一句阿里的話(某網友說是阿里說的,不清楚到底是不是出自阿里)“不懂程式的運維,不是好運維;不懂運維的開發,不是好開發。”。 正規表示式也是一門語言哈。當你學習一門語言時,必然會遇到該門語言的正規表示式這章節的。 在這裡推薦一本非常好的正規表示式書,包含常用的語言的正則寫法如sed、perl、bash、awk、php、c#、java、javascript、python、ruby等等,《Regular Expressions Cookbook, 2nd Edition》,也有中文版的,大家可以到網路上找找。

本文介紹nginx的重寫模組,建立重寫規則嚮導,便於快捷正確的建立新的重寫規則,不求救於人。同時,如果想把apache轉換成nginx,重寫規則也是要改的咯。

一. rewrite模組介紹

nginx的重寫模組是一個簡單的正規表示式匹配與一個虛擬堆疊機結合。依賴於PCRE庫,因此需要安裝pcre。根據相關變數重定向和選擇不同的配置,從一個location跳轉到另一個location,不過這樣的迴圈最多可以執行10次,超過後nginx將返回500錯誤。同時,重寫模組包含set指令,來建立新的變數並設其值,這在有些情景下非常有用的,如記錄條件標識、傳遞引數到其他location、記錄做了什麼等等。

二. rewrite模組指令

break

語法:break

預設值:none

使用欄位:server, location, if

完成當前設定的重寫規則,停止執行其他的重寫規則。

if

語法:if (condition) { … }

預設值:none

使用欄位:server, location

注意:儘量考慮使用try_files代替。判斷的條件可以有以下值:

  1. 一個變數的名稱:空字元傳“”或者一些“0”開始的字串為false。
  2. 字串比較:使用=或!=運算子
  3. 正規表示式匹配:使用~(區分大小寫)和~*(不區分大小寫),取反運算!~和!~*。
  4. 檔案是否存在:使用-f和!-f運算子
  5. 目錄是否存在:使用-d和!-d運算子
  6. 檔案、目錄、符號連結是否存在:使用-e和!-e運算子
  7. 檔案是否可執行:使用-x和!-x運算子

return

語法:return code

預設值:none

使用欄位:server, location, if

停止處理併為客戶端返回狀態碼。非標準的444狀態碼將關閉連線,不傳送任何響應頭。可以使用的狀態碼有:204,400,402-406,408,410, 411, 413, 416與500-504。如果狀態碼附帶文欄位落,該文字將被放置在響應主體。相反,如果狀態碼後面是一個URL,該URL將成為location頭補值。沒有狀態碼的URL將被視為一個302狀態碼。

rewrite

語法:rewrite regex replacement flag

預設值:none

使用欄位:server, location, if

按照相關的正規表示式與字串修改URI,指令按照在配置檔案中出現的順序執行。可以在重寫指令後面新增標記。

注意:如果替換的字串以http://開頭,請求將被重定向,並且不再執行多餘的rewrite指令。

尾部的標記(flag)可以是以下的值:

  • last – 停止處理重寫模組指令,之後搜尋location與更改後的URI匹配。
  • break – 完成重寫指令。
  • redirect – 返回302臨時重定向,如果替換欄位用http://開頭則被使用。
  • permanent – 返回301永久重定向。

rewrite_log

語法:rewrite_log on | off

預設值:rewrite_log off

使用欄位:server, location, if

變數:無

啟用時將在error log中記錄notice級別的重寫日誌。

set

語法:set variable value

預設值:none

使用欄位:server, location, if

為給定的變數設定一個特定值。

uninitialized_variable_warn

語法:uninitialized_variable_warn on|off

預設值:uninitialized_variable_warn on

使用欄位:http, server, location, if

控制是否記錄未初始化變數的警告資訊。

三. 重寫規則組成部分

3.1 任何重寫規則的第一部分都是一個正規表示式

可以使用括號來捕獲,後續可以根據位置來將其引用,位置變數值取決於捕獲正規表示式中的順序,$1引用第一個括號中的值,$2引用第二個括號中的值,以此類推。如:

^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$

$1是兩個小寫字母組成的字串,$2是由小寫字母和0到9的數字組成的5個字元的字串,$3將是個檔名,$4是png、jpg、gif中的其中一個。

3.2 重寫規則的第二部分是URI

請求被改寫。該URI可能包含正規表示式中的捕獲的位置引數或這個級別下的nginx任何配置變數。如:

/data?file=$3.$4

如果這個URI不匹配nginx配置的任何location,那麼將給客戶端返回301(永久重定向)或302(臨時重定向)的狀態碼來表示重定向型別。該狀態碼可以透過第三個引數來明確指定。

3.3 重寫規則的第三部分

第三部分也就是尾部的標記(flag)。 last標記將導致重寫後的URI搜尋匹配nginx的其他location,最多可迴圈10次。如:

rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4 last;

break指令可以當做自身指令。如:

if ($bwhog) {
    limit_rate 300k;
    break;
}

另一個停止重寫模組處理指令是return, 來控制主HTTP模組處理請求。 這意味著,nginx直接返回資訊給客戶端,與error_page結合為客戶端呈現格式化的HTML頁面或啟用不同的模組來完成請求。如果狀態碼附帶文欄位落,該文字將被放置在響應主體。相反,如果狀態碼後面是一個URL,該URL將成為location頭補值。沒有狀態碼的URL將被視為一個302狀態碼。如:

location = /image404.html {
    return 404 "image not found\n";
}

四. 例項

http {
    # 定義image日誌格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 開啟重寫日誌
    rewrite_log on;
 
    server {
        root /home/www;
 
        location / {
            # 重寫規則資訊
            error_log logs/rewrite.log notice; 
            # 注意這裡要用‘’單引號引起來,避免{}
            rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
            # 注意不能在上面這條規則後面加上“last”引數,否則下面的set指令不會執行
            set $image_file $3;
            set $image_type $4;
        }
 
        location /data {
            # 指定針對圖片的日誌格式,來分析圖片型別和大小
            access_log logs/images.log main;
            root /data/images;
            # 應用前面定義的變數。判斷首先檔案在不在,不在再判斷目錄在不在,如果還不在就跳轉到最後一個url裡
            try_files /$arg_file /image404.html;
        }
        location = /image404.html {
            # 圖片不存在返回特定的資訊
            return 404 "image not found\n";
        }
}

五. 建立新的重新規則

在接到要建立新的重寫規則時,要弄清楚需求是什麼樣的,再決定怎麼做。畢竟重寫也是耗資源的有效率之分的。 下面的這些問題有些幫助的:

  1. 你的URL的模式是什麼樣的?
  2. 是否有一個以上的方法來實現?
  3. 是否需要捕獲URL部分作為變數?
  4. 重定向到另一個web上可以看到我的規則?
  5. 是否要替換查詢的字串引數?

檢查網站或應用程式佈局,清楚URL模式。囉嗦一句:我一而再再而三的強調,運維不能與開發脫節,運維要參與到開發當中。如果有不止一種方法實現,建立一個永久重定向。同時,定義一個重寫規範,來使網址清潔,還可以幫助網站更容易被找到。

例項1. 要將home目錄重定向到主頁面上,目錄結構如下:

/
/home
/home/
/home/index
/home/index/
/index
/index.php
/index.php/

重寫規則如下:

rewrite ^/(home(/index)?|index(\.php)?)/?$ $scheme:
//$host/ permanent;

指定$scheme和$host變數,因為要做一個永久重定向並希望nginx使用相同的引數來構造URL。

例項2. 如果想分別記錄各個部分的URL,可以使用正規表示式來捕獲URI,然後,給變數分配指定位置變數,見上面的例項。

例項3. 當重寫規則導致內部重定向或指示客戶端呼叫該規則本身被定義的location時,必須採取特殊的動作來避免重寫迴圈。如:在server配置段定義了一條規則帶上last標誌,在引用location時,必須使用break標誌。

server {
    rewrite ^(/images)/(.*)\.(png|jpg|gif)$ $1/$3/$2.$3 last;
    location /images/ {
        rewrite ^(/images)/(.*)\.(png|jpg|gif)$ $1/$3/$2.$3 break;
    }
}

例項4. 作為重寫規則的一部分,傳遞新的查詢字串引數是使用重寫規則的目標之一。 如:

rewrite ^/images/(.*)_(\d+)x(\d+)\.(png|jpg|gif)$ /resizer/$1.$4?width=$2&height=$3? last;

nginx重寫規則說起來挺簡單的,做起來就難,重點在於正規表示式,同時,還需要考慮到nginx執行順序。

相關文章