URL重寫是指將一個URL請求重新寫成網站可以處理的另一個URL的過程。這樣說可能不是很好理解,舉個例子來說明一下,在開發中可能經常遇到這樣的需求,比如通過瀏覽器請求的http://localhost:8080/getUser?id=1,但是需要通過SEO優化等等原因,需要把請求的地址重寫為http://localhost:8080/getUser/1這樣的URL,從而符合需求或者更好的被網站閱讀。
當遇到這種請求的時候,就需要使用到UrlRewrite重寫或者使用一些網關路由,如SpringCloud的Gateway,Zuul,又或者是Nginx來實現這個功能。
和apache等web服務軟件一樣,rewrite的主要功能是實現URL地址的重定向。Nginx的rewrite功能需要PCRE軟件的支持,即通過perl兼容正則表達式語句進行規則匹配的。默認參數編譯nginx就會支持rewrite的模塊,但是也必須要PCRE的支持。
rewrite和location的功能有點相像,都能實現跳轉,主要區別在於rewrite常用於同一域名內更改獲取資源的路徑,而location是對一類路徑做控制訪問和反向代理,可以proxy_pass到其他服務器。
Nginx提供的全局變量或自己設置的變量,結合正則表達式和標志位實現url重寫以及重定向。 rewrite只能放在server{},location{},if{}中, 並且只能對域名后邊的除去傳遞的參數外的字符串起作用。
Rewrite主要的功能就是實現URL的重寫,Nginx的Rewrite規則采用Pcre,perl兼容正則表達式的語法規則匹配,如果需要Nginx的Rewrite功能,在編譯Nginx之前,需要編譯安裝PCRE庫。 通過Rewrite規則,可以實現規范的URL、根據變量來做URL轉向及選擇配置。
二、URL重寫應用場景
域名變更 (京東)
用戶跳轉 (從某個連接跳到另一個連接)
偽靜態場景 (便於CDN緩存動態頁面數據)
三、URL重寫語法
URL重寫語法
rewrite <regex> <replacement> [flag]; 關鍵字 正則 替代內容 flag標記 regex :可以使用正則或者字符串來表示相匹配的地址。 replacement:可以表示重定向的地址。 flag :flag標志的作用是用於控制當匹配到對應的rewrite規則后是否繼續檢查后續的rewrite規則。 flag值為如下四種,分別是: last:停止處理當前的rewrite指令集,而后通過重寫后的規則重新發起請求,瀏覽器地址欄URL地址不變。 break:和break指令一樣,都是停止處理當前上下文中的其他重寫模塊指令。 redirect:如果替換字符串不以“ http://”,“ https://”或“ $scheme” 開頭,返回帶有302代碼的臨時重定向,瀏覽器地址會顯示跳轉后的URL地址。 permanent:返回301代碼的永久重定向,瀏覽器地址欄會顯示跳轉后的URL地址。
rewrite參數的標簽可使用的位置
應用位置:server、location、if
nginx rewrite指令執行順序
1.執行server塊的rewrite指令(這里的塊指的是server關鍵字后{}包圍的區域,其它xx塊類似) 2.執行location匹配 3.執行選定的location中的rewrite指令
如果其中某步URI被重寫,則重新循環執行1-3,直到找到真實存在的文件。
如果循環超過10次,則返回500 Internal Server Error錯誤。
1) set設置變量量
所有的 Nginx變量在 Nginx 配置文件中引用時都須帶上 $ 前綴
在 Nginx 配置中,變量只能存放一種類型的值,有且也只存在一種類型,那就是字符串類型
set指令 自定義變量量 Syntax: set $variable value; set關鍵字 $變量名 變量值 Default: — Context: server, location,
例:
訪問主機ip本來應該進入http://www.cnblogs.com 重寫為 http://www.cnblogs.com/Nicholas0707 location / { set $name Nicholas0707; rewrite ^(.*)$ http://www.cnblogs.com/$name; }
變量創建,賦值及作用域問題
-
變量的創建和賦值操作發生在全然不同的時間階段。Nginx 變量的創建只能發生在 Nginx 配置加載的時候,或者說 Nginx 啟動的時候;而賦值操作則只會發生在請求實際處理的時候。這意味着不創建而直接使用變量會導致啟動失敗,同時也意味着我們無法在請求處理時動態地創建新的 Nginx 變量。
-
Nginx 變量一旦創建,其變量名的可見范圍就是整個 Nginx 配置,甚至可以跨越不同虛擬主機的 server 配置塊
-
Nginx變量名的可見范圍雖然是整個配置,但每個請求都有所有變量的獨立副本,或者說都有各變量用來存放值的容器的獨立副本,彼此互不干擾
例:
server { listen 8080; location /foo { echo "foo = [$foo]"; } location /bar { set $foo 32; echo "foo = [$foo]"; } }
結果:
$ curl 'http://localhost:8080/foo' foo = [] $ curl 'http://localhost:8080/bar' foo = [32] $ curl 'http://localhost:8080/foo' foo = []
分析:從這個例子我們可以看到,set 指令因為是在 location /bar 中使用的,所以賦值操作只會在訪問 /bar 的請求中執行。而請求 /foo 接口時,我們總是得到空的 $foo 值,因為用戶變量未賦值就輸出的話,得到的便是空字符串。
在不同層級的標簽中聲明的變量性的可見性規則如下:
-
location標簽中聲明的變量中對這個location塊可見
-
server標簽中聲明的變量對server塊以及server塊中的所有子塊可見
-
http標簽中聲明的變量對http塊以及http塊中的所有子塊可見
NGINX內置預定義變量
內置預定義變量即無需聲明就可以使用的變量,通常包括一個http請求或響應中一部分內容的值,以下為一些常用的內置預定義變量
變量名 定義 $arg_PARAMETER GET請求中變量名PARAMETER參數的值。 $args 這個變量等於GET請求中的參數。例如,foo=123&bar=blahblah;這個變量只可以被修改 $binary_remote_addr 二進制碼形式的客戶端地址。 $body_bytes_sent 傳送頁面的字節數 $content_length 請求頭中的Content-length字段。 $content_type 請求頭中的Content-Type字段。 $cookie_COOKIE cookie COOKIE的值。 $document_root 當前請求在root指令中指定的值。 $document_uri 與$uri相同。 $host 請求中的主機頭(Host)字段,如果請求中的主機頭不可用或者空,則為處理請求的server名稱(處理請求的server的server_name指令的值)。值為小寫,不包含端口。 $hostname 機器名使用 gethostname系統調用的值 $http_HEADER HTTP請求頭中的內容,HEADER為HTTP請求中的內容轉為小寫,-變為_(破折號變為下划線),例如:$http_user_agent(Uaer-Agent的值); $http_user_agent : 客戶端agent信息; $http_cookie : 客戶端cookie信息; $sent_http_HEADER HTTP響應頭中的內容,HEADER為HTTP響應中的內容轉為小寫,-變為_(破折號變為下划線),例如: $sent_http_cache_control, $sent_http_content_type…; $is_args 如果$args設置,值為"?",否則為""。 $limit_rate 這個變量可以限制連接速率。 $nginx_version 當前運行的nginx版本號。 $query_string 與$args相同。 $remote_addr 客戶端的IP地址。 $remote_port 客戶端的端口。 $remote_user 已經經過Auth Basic Module驗證的用戶名。 $request_filename 當前連接請求的文件路徑,由root或alias指令與URI請求生成。 $request_body 這個變量(0.7.58+)包含請求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比較有意義。 $request_body_file 客戶端請求主體信息的臨時文件名。 $request_completion 如果請求成功,設為"OK";如果請求未完成或者不是一系列請求中最后一部分則設為空。 $request_method 這個變量是客戶端請求的動作,通常為GET或POST。包括0.8.20及之前的版本中,這個變量總為main request中的動作,如果當前請求是一個子請求,並不使用這個當前請求的動作。 $request_uri 這個變量等於包含一些客戶端請求參數的原始URI,它無法修改,請查看$uri更改或重寫URI, 包含請求參數的原始URI,不包含主機名,如:”/foo/bar.php?arg=baz”。 $scheme 所用的協議,比如http或者是https,比如rewrite ^(.+)$ $scheme://example.com$1 redirect; $server_addr 服務器地址,在完成一次系統調用后可以確定這個值,如果要繞開系統調用,則必須在listen中指定地址並且使用bind參數。 $server_name 服務器名稱。 $server_port 請求到達服務器的端口號。 $server_protocol 請求使用的協議,通常是HTTP/1.0或HTTP/1.1。 $uri 請求中的當前URI(不帶請求參數,參數位於args),不同於瀏覽器傳遞的args),不同於瀏覽器傳遞的args),不同於瀏覽器傳遞的request_uri的值,它可以通過內部重定向,或者使用index指令進行修改。uri不包含主機名,如”/foo/bar.html”。
2) if 負責語句句中的判斷
語法:
Syntax: if (condition) { ... } if (表達式) { ... ... } Default: — Context: server, location
if語句中conditon規則
當表達式只是一個變量時,如果值為空或任何以0開頭的字符串都會當做false 直接比較變量和內容時,精確匹配 =或!= 匹配符號意義: ~區分大小寫正則匹配; ~*不區分大小寫正則匹配; !~區分大小寫正則不匹配; !~*不區分大小寫正則不匹配; 使用“ -f”和“ !-f”運算符檢查文件是否存在; 使用“ -d”和“ !-d”運算符檢查目錄是否存在; 使用“ -e”和“ !-e”運算符檢查文件,目錄或符號鏈接是否存在; 使用“ -x”和“ !-x”運算符檢查可執行文件。
例
server { # 如果文件不存在則返回400 if (!-f $request_filename) { return 400; } #如果瀏覽器是chrome瀏覽器則返回403 if ($http_user_agent ~* 'Chrome') { return 403; #return http://www.cnblogs.com; } # 如果請求類型不是POST則返回405 if ($request_method = POST) { return 405; } # 如果參數中有 a=1 則301到指定域名 if ($args ~ a=1) { rewrite ^ http://example.com/ permanent; } }
3) return 返回返回值或URL
語法
return 指令 定義返回數據 Syntax: return code [text]; return code URL; return URL; Default: — Context: server, location, if
例
#如果瀏覽器是chrome瀏覽器則返回403 其他瀏覽器返回博客園網址 location / { root html; index index.html index.htm; if ($http_user_agent ~* 'Chrome') { return 403; } rewrite ^/$ http://www.cnblogs.com permanent ; }
4) break 終止后續的rewrite規則
用於停止執行rewrite模塊的指令,但是其他模塊不受影響
語法
Syntax: break; Default:— Context:server, location, if
例子
#如果瀏覽器是chrome瀏覽器則返回403,但這里加了break,就不會執行return 403,返回默認的index, #也不會返回博客園 location / { if ($http_user_agent ~* 'Chrome') { break; return 403; } rewrite ^/$ http://www.cnblogs.com permanent ; }
5) rewrite 重定向URL
rewrite <regex> <replacement> [flag]; 關鍵字 正則 替代內容 flag標記 flag: last #本條規則匹配完成后,繼續向下匹配新的location URI規則 break #本條規則匹配完成即終⽌止,不不再匹配后⾯面的任何規則 redirect #返回302臨時重定向,瀏覽器器地址會顯示跳轉后的URL地址 permanent #返回301永久重定向,瀏覽器器地址欄會顯示跳轉后的URL地址 重定向就是將網頁自動轉向重定向 301永久性重定向:新網址完全繼承舊網址,舊網址的SEO網絡搜索引擎的排名等完全清零 301重定向是網頁更改地址后對搜索引擎友好的最好方法,只要不是暫時搬移的情況,都建議使用301來做轉址。 302臨時性重定向:對舊網址沒有影響,但新網址不會有排名 搜索引擎爬蟲會抓取新的內容而保留舊的網址
正則表達式規則
正則表達式匹配,其中: ~ 為區分大小寫匹配 ~* 為不區分大小寫匹配 !~和!~* 分別為區分大小寫不匹配及不區分大小寫不匹配 . 匹配除換行符以外的任意字符 \w 匹配字母或數字或下划線或漢字 \s 匹配任意的空白符 \d 匹配數字 \b 匹配單詞的開始或結束 ^ 匹配字符串的開始 $ 匹配字符串的結束 * 重復零次或更多次 + 重復一次或更多次 ? 重復零次或一次 {n} 重復n次 {n,} 重復n次或更多次 {n,m} 重復n到m次 *? 復任意次,但盡可能少重復 +? 重復1次或更多次,但盡可能少重復 ?? 重復0次或1次,但盡可能少重復 {n,m}? 重復n到m次,但盡可能少重復 {n,}? 重復n次以上,但盡可能少重復 \W 匹配任意不是字母,數字,下划線,漢字的字符 \S 匹配任意不是空白符的字符 \D 匹配任意非數字的字符 \B 匹配不是單詞開頭或結束的位置 [^x] 匹配除了x以外的任意字符 [^aeiou] 匹配除了aeiou這幾個字母以外的任意字符 (exp) 匹配exp,並捕獲文本到自動命名的組里 (?<name>exp) 匹配exp,並捕獲文本到名稱為name的組里,也可以寫成(?'name'exp) (?:exp) 匹配exp,不捕獲匹配的文本,也不給此分組分配組號 (?=exp) 匹配exp前面的位置 (?<=exp) 匹配exp后面的位置 (?!exp) 匹配后面跟的不是exp的位置 (?<!exp) 匹配前面不是exp的位置 (?#comment) 注釋分組不對正則表達式的處理產生任何影響
常用的rewrite重寫規則,用來美化網頁的鏈接。規則里面的$1$2你不知道是怎么來的話,只要記住,第一個()里面的是$1,第二個()里面的是$2.
例
server{ # 如果host不是sogou.com,則301到cnblogs.com中 if ( $host != "sogou.com" ){ rewrite ^/(.*)$ https://cnblogs.com/$1 permanent; } }
permanent標志:永久重定向
域名跳轉
域名跳轉,測試前修改host文件 192.168.199.228 www.abc.com www.abc.com 重寫為 www.cnblog.com server { listen 80; server_name www.abc.com; location / { rewrite ^/$ http://www.cnblog.com permanent; } }
redirect標志:臨時重定向
域名跳轉,測試前修改host文件 192.168.199.228 www.abc.com www.abc.com 重寫為 www.cnblog.com server { listen 80; server_name www.abc.com; location / { rewrite ^/$ http://www.cnblog.com permanent; } }
last標志:
url重寫后,馬上發起一個新的請求,再次進入server塊,重試location匹配,超過10次匹配不到報500錯誤,地址欄url不變
last 一般出現在server或if中
location / { rewrite ^/test1 /test2; rewrite ^/test2 /test3 last; rewrite ^/test3 /test4; } location /test2 { return 401; } location /test3 { return 402; } location /test4 { return 403; }
分析:
測試鏈接:http://192.168.199.328/test1 匹配到 location / {}后,被重寫為/test2,順序執行再次被重寫為/test3,因為flag為last,所以不會繼續重寫為/test4,而是發起一次location匹配,匹配到location /test3{},所以最終返回結果為402;
如果把location /{}中的last改為break,被重寫為/test3后,不再重寫為/test4,也不會發起location,最終沒有可匹配的資源,返回http404。
參考資料
[1]https://www.jianshu.com/p/f62b859bcce7
[2]