深度硬核文:Nginx的301重定向處理過程分析


一,序言

  “晚上九點,辦公室里煙霧繚繞,工作進度依然沒有什么進展。王二胖打開了十來個頁面,一篇篇技術文章打開,關閉,Nginx不停的重啟測試,在試過十來篇技術文章中的方案,經過兩個小時的測試之后,王二胖終於找到了一個解決301錯誤跳轉的可行解決方案。時間已經到了晚上十一點多."

  這樣的場景,在我們的辦公室里天天可見。互聯網上有很多Nginx 301問題處理方案的錯誤解答,比如自動加斜杠,端口丟失,暴露內部端口號等,極多量的文章基本就是人雲亦雲,沒有完全弄明白Nginx如何處理301狀態碼的。甚至對於一些關鍵性的配置信息的解釋是完全錯誤的。本着源代碼就是最正確的文檔的原則,我閱讀了一遍Nginx處理301問題的相關源代碼。

最終發現,Nginx處理301 Moved Permanently的邏輯相當簡單,只有六種分支。

二,Nginx的301狀態碼處理邏輯設計

  讓我們先看看Nginx的邏輯設計是怎么樣的。
  HTTP協議中3xx開頭的狀態響應碼都是表示重定向的響應。根據RFC的定義
  301 Moved Permanently
  302 Found
  303 See Other
  307 Temporary Redirect

  301是永久重定向。如果使用Nginx作為HTTP 服務器,那么當用戶輸入一個不存在的地址之后,基本上會有兩種情況,返回404狀態碼,或者301狀態碼。404 Not Found不做討論,只是說下301 Moved Permanently的處理過程。

  首先的關鍵問題是:頁面重定向功能會在什么樣的情況下被觸發?

  答案是:Nginx負責設置301 Moved Permanently狀態碼。但nginx.conf控制Nginx如何處理301 Moved Permanently狀態碼!換句話說,要不要進行頁面重定向,和怎么重定向,完全是用戶配置的結果!六種分支的選擇完全是根據用戶的配置來決定的。

  根據源代碼,Nginx的算法邏輯設計是分成兩個部分的。第一部分是設置狀態碼,第二部分是對應狀態碼的實際響應處理。

  Nginx和瀏覽器之間的通訊過程,比如一次正常的HTTP 訪問過程,如下圖。

file

  從邏輯順序上來說,Nginx會先設置好狀態碼,然后根據狀態碼來構造Response Header和Body,最后發送給瀏覽器,讓瀏覽器渲染頁面內容。

  301 Moved Permanently狀態碼和200 OK狀態碼的處理過程是一致的。Nginx主動設置301 Moved Permanently狀態碼只有一種情況,當用戶輸入了一個url地址,最后的部分是一個文件目錄。比如 http://www.test.com/index, Nginx在運行過程中沒有找到index這個文件,但發現了index是個目錄。於是本次訪問的狀態碼就會被設置成301 Moved Permanently。

file

  但注意!設置成301 Moved Permanently,不一定會導致瀏覽器重定向。從HTTP定義來說,導致瀏覽器觸發重定向操作是因為瀏覽器收到了一個Location response header;

  讓我們來看看Location的定義說明,很明確的說明了Location的作用。

The Location response header indicates the URL to redirect a page to. It only provides a meaning when served with a 3xx (redirection) or 201 (created) status response.

  Nginx在Response Header里寫入一個Location之后。瀏覽器可以根據Location來控制重定向過程。邏輯過程如上圖。而且nginx.conf文件中的配置將影響到Location URL的生成方式。

三,nginx.conf中配置項的作用

  nginx.conf文件在哪個環節起作用呢?答案就是設置Location之前。
file

  一般情況下,我們會在nginx.conf中配置absolute_redirect ,server_name_in_redirect和port_in_redirect,以便到達個性化的重定向功能。這三個配置項的作用是很多人明白的,但對於邏輯順序很少有文章提到。

四,重定向三配置
  absolute_redirect ,server_name_in_redirect和port_in_redirect三個設置項中,根據Nginx的源代碼中Response Header處理算法邏輯,Nginx能夠控制重定向的關鍵配置項是:absolute_redirect,在整個Nginx代碼中,absolute_redirect在控制在Response Header如何增加Location url。

  absolute_redirect設置成On,則生成absolute url作為Location url。
  absolute_redirect設置成Off,則生成relative url作為 Location url。

  absolute url是包含完整信息的url,比如http://www.test.com:8080/index/1.html 這樣的URL地址
  relative url 則省略了服務器名字和端口號,比如 /index/1.html

  因為relative url沒有端口號,沒有Host名字,所以absolute_redirect 設置On的時候,server_name_in_redirect和port_in_redirect兩項設置才會起作用。

  我花了點時間仔細閱讀了Nginx的相關源代碼,並畫了流程圖。

file

  從以上邏輯過程, absolute_redirect,server_name_in_redirect,port_in_redirect 三項配置,共同控制了生成字符串 “Location: http://server_name:port/test/”的結果。

  server_name_in_redirect 控制URL中的Server Name,也就是Host使用哪個值,port_in_redirect控制URL中的port是使用哪個值。通過這三個配置項,最終決定了Nginx返回給瀏覽器的Location內容。

  用MindMap來表達就是

file
  備注:header_in.server是nginx源代碼中的變量,指向用戶輸入的url的服務器名字部分。
  根據以上腦圖,我們可以很清楚的看到,最終我們只有六個分支結果。

五,案例分析
  依據上面的分析,我們具體舉個非常疑難的例子,看看如何解決問題。

  1,問題:http://www.test.com:888/index 被錯誤的重定向至 http://www.test.com/index/
這種情況多見於使用NAT做端口映射,或者是用容器來運行Nginx。內部服務器或者容器中nginx監聽的是80端口號

  而我們的期望答案是:http://www.test.com:888/index 重定向至 http://www.test.com:888/index/

  2,分析和解答:
  假設Nginx使用默認設置
  absolute_redirect:on
  server_name_in_redirect:off
  port_in_redirect :on

  這個問題看似丟失了端口號,實際上是暴露了nginx的內部端口號。這種情況下,port_in_redirect不管設置成on或者off都不會起作用。

  port_in_redirect = on,nginx監聽80端口,默認80端口號不需要在url中設置【3號分支】,結果是http://www.test.com/index/

  port_in_redirect = off,nginx不設置port數據【5號分支】,結果還是http://www.test.com/index/

  我們會發現,在這里無論怎么調整server_name_in_redirect【1,2號分支】和port_in_redirect的on,off都是無效的。因為1號至5號分支,都沒有包含這種情況。 解決問題的方向錯了,解決方案當然就是錯誤的。

  實際的解決方案有兩個:

  rewrite 【分支以外】
  absolute_redirect:off 【六號分支】

  rewrite是通過腳本來控制nginx的運行過程,不在上面的配置分支中。具體操作可以參考下面的設置

  rewrite [^/]$ $scheme://$http_host$uri/ permanent;

  備注:rewrite的操作方式依據不同的目錄結構,可能略有不同,請根據實際情況來設置。

  absolute_redirect:off,就是六號分支
  absolute_redirect:off之后,Nginx返回Location:/index/,但這個方式也許會帶來其他問題。

六, 結語:

  文章分析到這里基本上已經沒有什么內容好說的了。上面的思維導圖已經把所有的解決問題的分支全部展現出來了。
但最后,有感於互聯網上中文技術文章的抄襲現象,我想說源代碼就是最好的文檔,作為程序員不要人雲亦雲。
  有問題,多看源代碼!

七,參考資料:

  nginx源代碼
  src\http\v2\ngx_http_v2_filter_module.c
  src\http\ngx_http_header_filter_module.c

謝謝閱讀
本文完成於2019-09-26


  **寫多不如寫精!
  我是丁長老,十多年開發經驗。
  歡迎關注我的技術公眾號。
  個人微信號nine-ding,歡迎加我微信瞎聊。
  如需轉發文章請加微信號獲取轉發授權。 **


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM