你真的了解 Nginx rewrite 麼?

2021-02-28 小米技術

網站在使用 Nginx 時都會進行個性化配置滿足自己的業務需要,而 URL 重寫幾乎是每個網站都必做的事情,Nginx 的 URL 重寫規則不像 Apache 那樣簡單直接,邏輯相對要複雜一些,本文將通過例子的方式幫助大家理解 Nginx rewrite 原理,希望能對您有些啟發。

Nginx 中重定向有多種方式:

外部重定向

return 指令返回 301 或 302(return 也可以返回其他狀態碼),可以放在 server 或 location 塊中。例如:

return 301 https://www.mi.com;or
return 302 https://www.mi.com;

還可以使用 rewrite 指令,例如:

rewrite ^/(.*)$   http://www.mi.com/$1;orrewrite ^/(.*)$   http://www.mi.com/ redirect;

內部重定向

return + error_page 指令的組合,或 try_files 指令和 rewrite 指令,非常靈活。

本文主要講解 rewrite 的工作原理,其他指令的使用方法大家可以自行查閱 Nginx 官網。在使用 Nginx 的 rewrite 指令時,flag 可以設置為 last 和 break,這兩個 flag 很容易混淆,後面我們會比較這兩個 flag 的區別,下面通過示例我們來認識一下 rewrite 指令。

Syntax:    rewrite regex replacement [flag];Context:   server, location, if

regex: 對請求的 URI 做正則匹配

replacement:目標 uri 匹配成功後替換的 url

可以使用的 flag 有以下 4 個(flag 也可以為空):

redirect:返回 302 臨時重定向,客戶端地址欄會顯示跳轉後的地址;

permanent:返回 301 永久重定向,客戶端地址欄會顯示跳轉後的地址;

last:內部重定向,停止處理後續 rewrite 模塊中的指令(客戶端無感知);

break:內部重定向,停止處理後續 rewrite 模塊中的指令(客戶端無感知)。


NOTE:

1. regex 匹配的是 uri,不包含 hostname 和 query string,默認 query string 是被追加到 replacement 末尾,如果不希望在末尾追加請求的 query string,可以在 replacement 的末尾加一個 "?"。

server {    rewrite ^/mi_one/(.*)$  /mi/$1;    rewrite ^/mi_two/(.*)$  /mi/$1?x=0&y=1?;    location /mi/ {        
       echo $uri$is_args$args;    }}$ curl "http://localhost/mi_one/hello?a=1&b=2"
output: /mi/hello?a=1&b=2

$ curl "http://localhost/mi_two/hello?a=1&b=2"
output: /mi/hello?x=0&y=1

2. 如果 replacement 是以 "http://","https://" 或 $scheme" 開始的字符串,那麼 rewrite 指令停止後面的處理,直接返回給客戶端,沒有指定 flag 時,與 redirect 效果相同。

3. 沒有 flag 的 rewrite 指令根據出現的順序執行,flag 可以控制指令的執行順序。

4. 在配置中開啟 rewrite_log 指令,日誌文件中會記錄 rewrite 的匹配過程,有助於調試 rewrite 問題。

在講 rewrite 前我們先來簡單了解下 Nginx 請求處理流程,為什麼需要了解請求處理流程呢?因為 rewrite 操作與其中幾個 phase 關係很密切,熟悉了請求處理流程,理解 rewrite 執行邏輯就會很容易。在 Nginx 內部將請求處理劃分為 11 個 phase,每個 phase 會執行對應的 handler,這裡我們不打算逐個進行講解。在 11 個 phase 中與 rewrite 指令邏輯有關的只有 4 個,所以在本文我們主要關注 SERVER_REWRITE、FIND_CONFIG、REWRITE 和 POST_REWRITE 這四個 phase。

首先我們要清楚的是:

server 塊中的 rewrite 模塊指令在 SERVER_REWRITE 階段解析;

location 塊中的 rewrite 模塊指令在 REWRITE 階段解析;

SERVER_REWRITE - 請求到達後首先處理這個階段的 rewrite 指令操作

FIND_CONFIG - 根據 SERVER_REWRITE 階段得到的 uri 查找 location

REWRITE - 確定 location 後執行 locaton 中 rewrite 操作

POST_REWRITE - 根據上一階段的 uri 重寫結果做決策,可能跳回 FIND_CONFIG 階段重新查找 location,也可能繼續執行後邊的 phase。例如:在 location 中配置了 rewrite 指令並且指定 flag=break,執行完本條 rewrite 終止後邊的 rewrite 匹配,然後執行 PREACCESS 階段中的 handler。同樣的場景下 flag=last,執行完本條 rewrite 終止後邊的 rewrite 匹配,然後跳到 FIND_CONFIG 階段再次查找 location。未指定 flag 的情況與 flag=last 類似,唯一區別是在同一層級中未指定 flag 的 rewrite 語句不會終止後續的 rewrite 匹配。

未指定 flag 的 rewrite 會按照出現順序進行匹配,server 塊中 rewrite 匹配完以後根據改寫的 uri 查找 location,然後再匹配 location 中的 rewrite,location 中的 rewrite 指令匹配成功後會再次查找 location。

server {    rewrite ^/(.*)$         /mi_one/$1;    rewrite ^/mi_one/(.*)$  /mi_two/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }}$ curl http://localhost/mi_zero/helloThis is mi_two location

說明:

匹配第一條 rewrite 成功,uri 被改寫為 / mi_one/mi_zero/hello,沒有指定 flag 的 rewrite 繼續匹配後面的 rewrite

匹配第二條 rewrite 成功,此時 uri 被改寫為 / mi_two/mi_zero/hello

查找 location,mi_two 被確定為最終的 location

server {    rewrite ^/(.*)$         /mi_one/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        rewrite ^/mi_one/(.*)$  /mi_two/$1;        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }}$ curl http://localhost/helloThis is mi_two location

說明:

匹配 server 塊中的 rewrite 成功,uri 被改寫為 / mi_one/hello

server 塊中只有一條 rewrite 指令,開始查找 location

location mi_one 被找到,開始匹配 location 中 rewrite

location 中的 rewrite 匹配成功,uri 被改寫為 / mi_two/hello

再次查找 location,mi_two 被確定為最終使用的 location

再來看一個例子:

server {    rewrite ^/(.*)$         http://www.mi.com/$1;    rewrite ^/mi_one/(.*)$  /mi_two/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }}$ curl -I http://localhost/mi_one/hellohttp code: 302
Location: http://www.mi.com/mi_one/hello

說明:

匹配第一條 rewrite 成功,由於 replacement 是以 http:// 開始的字符串,所以 rewrite 指令直接返回給客戶端 302,並且停止匹配後續的 rewrite。


指定 flag 為 redirect 時,rewrite 匹配成功後直接返回給客戶端 302,不會繼續匹配後續的 rewrite。

server {    rewrite ^/(.*)$         /mi_one/$1 redirect;    rewrite ^/mi_one/(.*)$  /mi_two/$1;    rewrite ^/mi_two/(.*)$  /mi_three/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl -I http://localhost/mi_zero/hellohttp code: 302
Location: http://localhost/mi_one/mi_zero/hello$ curl -I http://localhost/mi_one/hellohttp code: 302
Location: http://localhost/mi_one/mi_one/hello$ curl -I http://localhost/mi_two/hellohttp code: 302
Location: http://localhost/mi_one/mi_two/hello

再來看一個例子:

server {    rewrite ^/mi_one/(.*)$  /mi_two/$1;    rewrite ^/mi_two/(.*)$  /mi_three/$1 redirect;    rewrite ^/(.*)$         /mi_one/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl -I http://localhost/mi_one/hellohttp code: 302
Location: http://localhost/mi_three/hello$ curl -I http://localhost/mi_two/hellohttp code: 302
Location: http://localhost/mi_three/hello$ curl http://localhost/mi_zero/helloThis is mi_one location

指定 flag=permanent 時,與 redirect 效果相同,唯一的區別 http code 返回 301。

rewrite 的 last 和 break 這兩個 flag 使用場景很多並且也很容易混淆,他們的共同點都會停止當前層級後續的 rewrite 匹配,區別需要分兩種情況:第一種使用在 server block 中,last 和 break 沒有區別。第二種使用在 location block 中,last 會根據改寫的 uri 重新查找 location,break 不會重新查找 location,而是在當前 location 中執行後續的指令。(註:last 和 break 不僅停止 rewrite 的匹配,同時還會停止 Nginx rewrite 模塊中其他指令的執行,例如:set、return 指令)

server {    rewrite ^/mi_one/(.*)$  /mi_two/$1 last;    rewrite ^/mi_two/(.*)$  /mi_three/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl http://localhost/mi_one/helloThis is mi_two location

說明:

匹配第一條 rewrite 成功,uri 被改寫為 / mi_two/hello,由於 flag 指定為 last 會停止後續的 rewrite 的匹配(僅停止 server block 中的 rewrite 匹配),所以會根據改寫的 uri 查找 location

再來看一個 last 在 location block 中使用的例子:

server {    rewrite ^/(.*)$  /mi_one/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        rewrite ^/(.*)$             /mi_three/$1 last;        rewrite ^/mi_three/(.*)$    /;        
       
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl http://localhost/helloThis is mi_three location

說明:

匹配 server block 中第一條 rewrite 成功,uri 被改寫為 / mi_one/hello

查找 location,mi_one 被確定為使用的 location

匹配 location block 中的 rewrite,location 中的第一條 rewrite 匹配成功,uri 被改寫為 / mi_three/mi_one/hello,由於 flag 指定為 last,所以停止 location 中後續的 rewrite 匹配,此時再根據 uri=/mi_three/mi_one/hello 查找 location,最終 mi_three 被確定為使用的 location

server {    rewrite ^/mi_one/(.*)$  /mi_two/$1 break;    rewrite ^/mi_two/(.*)$  /mi_three/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        
       echo "This is mi_one location";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl http://localhost/mi_one/helloThis is mi_two location

說明:

匹配 server block 中第一條 rewrite 成功,uri 被改寫為 / mi_two/hello,由於 flag 指定為 break 所以會停止 server block 中後續的 rewrite 匹配,根據 uri=/mi_two/hello 查找 location,最終 mi_two 被確定為使用的 location

server {    rewrite ^/(.*)$  /mi_one/$1;    location / {        
       echo "This is default location";    }    location /mi_one/ {        rewrite ^/(.*)$         /mi_three/$1 break;        rewrite ^/mi_two/(.*)$  /;        
       
       echo "This is mi_one location";        
       echo "uri: ${uri}";    }    location /mi_two/ {        
       echo "This is mi_two location";    }    location /mi_three/ {        
       echo "This is mi_three location";    }}$ curl http://localhost/helloThis is mi_one locationuri: /mi_three/mi_one/hello

說明:

匹配 server block 中第一條 rewrite 成功,uri 被改寫為 / mi_one/hello

根據 uri=/mi_one/hello 查找 location,mi_one 被確定為使用的 location

匹配 location block 中的 rewrite,location 中的第一條 rewrite 匹配成功,uri 被改寫為 / mi_three/mi_one/hello,由於 flag 指定為 break,所以停止 location 中後續的 rewrite 匹配,並且把當前 location 作為最終使用的 location,不會重新查找 location(last 會繼續查找 location)

在 Nginx 的配置中可以實現簡單的編程,理解起來相對有點難度,通過閱讀此文希望能對你有些啟發,能夠根據項目需求可以配置更複雜的 rewrite 規則。想要更深入的理解 rewrite,還需要大家自己動手實踐。

https://github.com/agile6v/awesome-nginx

http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

https://www.nginx.com/blog/creating-nginx-rewrite-rules/

https://www.nginx.com/blog/converting-apache-to-nginx-rewrite-rules

http://www.thegeekstuff.com/2017/08/nginx-rewrite-examples/

http://winginx.com/en/htaccess

https://w3techs.com/technologies/overview/web_server/all

http://nginx.org/en/docs/dev/development_guide.html#httpphases

相關焦點

  • nginx status狀態頁配置方法和中文說明
    西崑雲這篇文章主要介紹了nginx status狀態頁配置方法和中文說明,重點在配置例子和status的中文說明,需要的朋友可以參考下  nginx和php-fpm一樣內建了一個狀態頁,對於想了解nginx
  • 如何評價動畫《Rewrite》對遊戲劇情的大幅改動?
    答|百度派 @楊雯說實話,key改我只服京阿尼,rewrite的改編無疑是失敗的。這裡我借京阿尼的三部曲與rewrite做個對比說明下問題所在。key改的巔峰是在京阿尼的三部曲的cl。三部曲同時讓key社和京阿尼兩家揚名立萬。
  • 遊戲壁紙,《Rewrite》,高清精美
    rewrite本篇沒有講述的故事在此登場!捲入了圍繞著整個星球展開的命運之中的主人公,天王寺瑚太朗。但是,他所改寫的命運並不是只有那些。作為一個人,他所選擇的道路前方,存在著無數的可能性世界。在那其中,甚至也有像是節日一般熱鬧歡樂的未來。
  • Rewrite:催淚番?後宮番?不!這只是一部正經且勵志的環保番
    rewrite如果你是一名key社的老粉,或者看過大多數key社改編的動漫,你就會發現key社作品或多或少都帶有催淚的元素,rewrite自然也不會缺少這一點有一說一,男主的後宮是真的多。rewrite如果你能理清這部動漫的思路,內容和劇情就會很容易理解。
  • 2018/11/23 新歌推薦:Rewrite The Stars - James Arthur & Anne-Marie
    [Chorus 1: James Arthur]第一段副歌What if we rewrite the stars?如果我們重繪星辰呢?這會取決於你,這也取決於我No one can say what we get to be沒有別人能說我們會成為什麼樣子So why don't we rewrite the stars?
  • 《Rewrite+》確認登陸歐美地區 追加全新要素與劇情
    據了解《Rewrite+》收錄了全新的要素、追加事件等,同時加入了此前未收錄的全新CG,更多角色與劇情也都將在本作中出現。   此時瑚太朗也發現自己被賦予了一種名為「rewrite」的、可增強自身力量與速度的超能力,在與千裡朱音、神戶小鳥、鳳千早、此花露西婭以及中津靜流的同學合作之下,他們將一同揭開這些隱藏在超自然社會中的秘密,共同譜寫一段關於友情、愛情、以及衝突的故事。   《Rewrite+》現已登陸PC平臺。
  • 全棧開發——動手打造屬於自己的直播間(Vue+SpringBoot+Nginx)
    說到直播我們先了解下幾個常用的直播流協議,看了挺多的流媒體協議文章博客,但都是非常粗略,這裡有個比較詳細的 流媒體協議介紹(地址:http://blog.csdn.net/tttyd/article/details/12032357/),如果想詳細了解協議內容估計去要看看專業書籍了。這裡我們用到的只是rtmp和hls,實踐後發現:rtmp只能夠在電腦端播放,hls只能夠在手機端播放。
  • 你真的了解丙綸防水卷材麼?一定有你不知道的
    那麼,你真的了解這種防水卷材麼丙綸防水卷材介紹聚乙烯丙綸高分子複合防水卷材主要是以無紡布和聚乙烯為主要原料,經添加抗老化劑,穩定劑、助粘劑等與高強度新型丙綸滌綸長絲無紡布,經過自動化生產線一次複合而成的新型防水卷材。
  • 你真的了解民謠麼?不知道這些人,真的不能算上民謠迷!
    導語:你真的了解民謠麼?不知道這些人,真的不能算上民謠迷!你真的了解民謠麼?不知道這些人,真的不能算上民謠迷!民謠走過了43年,火了趙雷,李健,但是在他們之前堅持唱民謠的人都有誰呢?今天小子靈就為大家說一說那些我們不熟悉的,但是為了民謠默默付出的那些前輩們。
  • Node.js 實戰系列:幫黃老師完善餓了麼項目
    to.path.split('/vue')[1] }      })    }    store.commit('common/SETUSERINFO', res.data || {})  }  next()})項目中的圖標都是引入的阿里矢量圖標,在阿里矢量圖標庫官網裡註冊完帳號後新建一個倉庫,將你需要的圖標都加到你的新建倉庫裡
  • 你真的了解Fate麼?這些Fate系列作品你都補完了麼?
    Fate,是TYPE-MOON原作的系列作品之一,也是其代表作。(這絕對不是B站FGO手遊廣告),小夥伴們看了那麼多難道心裡沒有一點B數麼?大新聞!就在本周!PS:小編最後還是要補充一句~(閉嘴你這個舊劍梅林廚!)PPS:祝小夥伴們!!!!!
  • 我的世界:你真的了解「末影人」的身世麼?那你不得不看看這些
    你知道《我的世界》每一隻怪物背後的「故事」麼?你知道它們被創造的原型,在真實世界是種是一種怎樣的存在麼?顯然,你了解的並不透徹!Zombie殭屍的原型生物及背景《我的世界》Zombie,常被我們中國玩家叫做「殭屍」,然而大家都忽略了一點,在西方,實際上沒有殭屍一說。殭屍的英文翻譯其實就是Jiang Shi,而並非Zombie。
  • 關於追星,你真的認真了麼?
    小編就有個疑惑,這就是正常的對一位偶像的喜歡麼?這真的是理智的追星麼?相比鹿晗粉絲的不理智,小編覺得鹿晗做的是真的十分爺們兒,在自己大火的時候公布戀情。於是小編仔細了解了一下,發現招黑的不止打籃球,還有他的粉絲們對他無腦的支持,各種的不理智行為,也不知道是真的喜歡他,還是他的黑粉們。
  • 你真的了解麼?陳冠希與SSUR
    提起SSUR,你想到的是什麼?
  • 英語作為副科真的可以麼?你會後悔麼?
    不把英語學好,能把歐美國家的先進的科學技術學到手麼?我認為基本上所有的專業學習都是需要英語的,理工類的專業,就不用多說了,大三就開始學習專業英語,大四就可能就會涉及英文前沿科技了解甚至科學研究,即使是音樂專業也是需要英語的,現代音樂起源於歐洲,不學英語能學好音樂麼?我認為如果有什麼專業英語用到的概率比較低的話,那就是漢語言文學或者韓語、日語等小語種專業了。
  • 碳肥你真的了解麼?360度全面呵護作物健康!
    碳肥你真的了解麼?360度全面呵護作物健康!那麼,你對有機水溶碳肥了解多少?
  • 你真的會強制存錢麼
    傳遞的根本就是知識差、信息差,如何辨別知識的差距和真偽需要知識,放心作為主播的他們不會告訴你我正在割你的韭菜,你不會告訴你我準確收取你的智商稅,而我們真正要做的就是去讀讀書,五千年的文化傳承告訴我們書中自有黃金屋,你不讀書就無法辨別知識的真偽,你沒有知識如何獲取信息差,總是慢人一步還總是埋怨被割韭菜,沒辦法,不讀書就是你的錯,分不清楚利弊不要說任何人,只能說自己知識不夠。
  • Spring Cloud學習筆記——Nginx和Zuul以及正向代理和反向代理
    項目架構
  • 微博上線「悄悄關注」功能 你會潛伏關注誰(1)
    404 Not Found404 Not Foundnginx 404 Not Found404 Not Foundnginx 404 Not Found404 Not Foundnginx 404 Not Found404 Not Foundnginx