萬字長文!一次性弄懂 Nginx 處理 HTTP 請求的 11 個階段

Nginx 處理一個 HTTP 請求的全過程

前面給你們講了 Nginx 是如何處理 HTTP請求頭部的,接下來就到了真正處理 HTTP 請求的階段了。先看下面這張圖,這張圖是 Nginx 處理 HTTP 請求的示意圖,雖然簡單,可是卻很好的說明了整個過程。javascript

  1. Read Request Headers:解析請求頭。
  2. Identify Configuration Block:識別由哪個 location 進行處理,匹配 URL。
  3. Apply Rate Limits:判斷是否限速。例如可能這個請求併發的鏈接數太多超過了限制,或者 QPS 過高。
  4. Perform Authentication:鏈接控制,驗證請求。例如可能根據 Referrer 頭部作一些防盜鏈的設置,或者驗證用戶的權限。
  5. Generate Content:生成返回給用戶的響應。爲了生成這個響應,作反向代理的時候可能會和上游服務(Upstream Services)進行通訊,而後這個過程當中還可能會有些子請求或者重定向,那麼還會走一下這個過程(Internal redirects and subrequests)。
  6. Response Filters:過濾返回給用戶的響應。好比壓縮響應,或者對圖片進行處理。
  7. Log:記錄日誌。

以上這七個步驟從總體上介紹了一下處理流程,下面還會再說一下實際的處理過程。php

Nginx 處理 HTTP 請求的 11 個階段

下面介紹一下詳細的 11 個階段,每一個階段均可能對應着一個甚至多個 HTTP 模塊,經過這樣一個模塊對比,咱們也可以很好的理解這些模塊具體是怎麼樣發揮做用的。css

  1. POST_READ:在 read 完請求的頭部以後,在沒有對頭部作任何處理以前,想要獲取到一些原始的值,就應該在這個階段進行處理。這裏面會涉及到一個 realip 模塊。
  2. SERVER_REWRITE:和下面的 REWRITE 階段同樣,都只有一個模塊叫 rewrite 模塊,通常沒有第三方模塊會處理這個階段。
  3. FIND_CONFIG:作 location 的匹配,暫時沒有模塊會用到。
  4. REWRITE:對 URL 作一些處理。
  5. POST_WRITE:處於 REWRITE 以後,也是暫時沒有模塊會在這個階段出現。

接下來是確認用戶訪問權限的三個模塊:html

  1. PREACCESS:是在 ACCESS 以前要作一些工做,例如併發鏈接和 QPS 須要進行限制,涉及到兩個模塊:limt_conn 和 limit_req
  2. ACCESS:核心要解決的是用戶能不能訪問的問題,例如 auth_basic 是用戶名和密碼,access 是用戶訪問 IP,auth_request 根據第三方服務返回是否能夠去訪問。
  3. POST_ACCESS:是在 ACCESS 以後會作一些事情,一樣暫時沒有模塊會用到。

最後的三個階段處理響應和日誌:java

  1. PRECONTENT:在處理 CONTENT 以前會作一些事情,例如會把子請求發送給第三方的服務去處理,try_files 模塊也是在這個階段中。
  2. CONTENT:這個階段涉及到的模塊就很是多了,例如 index, autoindex, concat 等都是在這個階段生效的。
  3. LOG:記錄日誌 access_log 模塊。

以上的這些階段都是嚴格按照順序進行處理的,固然,每一個階段中各個 HTTP 模塊的處理順序也很重要,若是某個模塊不把請求向下傳遞,後面的模塊是接收不到請求的。並且每一個階段中的模塊也不必定全部都要執行一遍,下面就接着講一下各個階段模塊之間的請求順序。node

11 個階段的順序處理

以下圖所示,每個模塊處理之間是有序的,那麼這個順序怎麼才能獲得呢?其實很是簡單,在源碼 ngx_module.c 中,有一個數組 ngx_module_name,其中包含了在編譯 Nginx 的時候的 with 指令所包含的全部模塊,它們之間的順序很是關鍵,在數組中順序是相反的。nginx

char *ngx_module_names[] = {
    … …
    "ngx_http_static_module",
    "ngx_http_autoindex_module",
    "ngx_http_index_module",
    "ngx_http_random_index_module",
    "ngx_http_mirror_module",
    "ngx_http_try_files_module",
    "ngx_http_auth_request_module",
    "ngx_http_auth_basic_module",
    "ngx_http_access_module",
    "ngx_http_limit_conn_module",
    "ngx_http_limit_req_module",
    "ngx_http_realip_module",
    "ngx_http_referer_module",
    "ngx_http_rewrite_module",
    "ngx_http_concat_module",
    … …
}

灰色部分的模塊是 Nginx 的框架部分去執行處理的,第三方模塊沒有機會在這裏獲得處理。git

在依次向下執行的過程當中,也可能不按照這樣的順序。例如,在 access 階段中,有一個指令叫 satisfy,它能夠指示當有一個知足的時候就直接跳到下一個階段進行處理,例如當 access 知足了,就直接跳到 try_files 模塊進行處理,而不會再執行 auth_basic、auth_request 模塊。github

在 content 階段中,當 index 模塊執行了,就不會再執行 auto_index 模塊,而是直接跳到 log 模塊。正則表達式

整個 11 個階段所涉及到的模塊和前後順序以下圖所示:

下面開始詳細講解一下各個階段。先來看下第一個階段 postread 階段,顧名思義,postread 階段是在正式處理請求以前起做用的。

postread 階段

postread 階段,是 11 個階段的第 1 個階段,這個階段剛剛獲取到了請求的頭部,尚未進行任何處理,咱們能夠拿到一些原始的信息。例如,拿到用戶的真實 IP 地址

問題:如何拿到用戶的真實 IP 地址?

咱們知道,TCP 鏈接是由一個四元組構成的,在四元組中,包含了源 IP 地址。而在真實的互聯網中,存在很是多的正向代理和反向代理。例如最終的用戶有本身的內網 IP 地址,運營商會分配一個公網 IP,而後訪問某個網站的時候,這個網站可能使用了 CDN 加速一些靜態文件或圖片,若是 CDN 沒有命中,那麼就會回源,回源的時候可能還要通過一個反向代理,例如阿里雲的 SLB,而後纔會到達 Nginx。

咱們要拿到的地址應該是運營商給用戶分配的公網 IP 地址 115.204.33.1,對這個 IP 來進行併發鏈接的控制或者限速,而 Nginx 拿到的倒是 2.2.2.2,那麼怎麼才能拿到真實的用戶 IP 呢?

HTTP 協議中,有兩個頭部能夠用來獲取用戶 IP:

  • X-Forwardex-For 是用來傳遞 IP 的,這個頭部會把通過的節點 IP 都記錄下來
  • X-Real-IP:能夠記錄用戶真實的 IP 地址,只能有一個

拿到真實用戶 IP 後如何使用?

針對這個問題,Nginx 是基於變量來使用。

例如 binary_remote_addr、remote_addr 這樣的變量,其值就是真實的 IP,這樣作鏈接限制也就是 limit_conn 模塊纔有意義,這也說明了,limit_conn 模塊只能在 preaccess 階段,而不能在 postread 階段生效。

realip 模塊

  • 默認不會編譯進 Nginx

    • 須要經過 --with-http_realip_module 啓用功能
  • 變量:若是還想要使用原來的 TCP 鏈接中的地址和端口,須要經過這兩個變量保存

    • realip_remote_addr
    • realip_remote_port
  • 功能

    • 修改客戶端地址
  • 指令

    • set_real_ip_from

      指定可信的地址,只有從該地址創建的鏈接,獲取的 realip 纔是可信的

    • real_ip_header

      指定從哪一個頭部取真實的 IP 地址,默認從 X-Real-IP 中取,若是設置從 X-Forwarded-For 中取,會先從最後一個 IP 開始取

    • real_ip_recursive

      環回地址,默認關閉,打開的時候,若是 X-Forwarded-For 最後一個地址與客戶端地址相同,會過濾掉該地址

Syntax: set_real_ip_from address | CIDR | unix:;
Default: —
Context: http, server, location

Syntax: real_ip_header field | X-Real-IP | X-Forwarded-For | proxy_protocol;
Default: real_ip_header X-Real-IP; 
Context: http, server, location

Syntax: real_ip_recursive on | off;
Default: real_ip_recursive off; 
Context: http, server, location

實戰

上面關於 real_ip_recursive 指令可能不太容易理解,咱們來實戰練習一下,先來看 real_ip_recursive 默認關閉的狀況:

  • 從新編譯一個帶有 realip 模塊的 nginx
關於如何編譯 Nginx,詳見: https://iziyang.github.io/202...
# 下載 nginx 源碼,在源碼目錄下執行
./configure --prefix=本身指定的目錄 --with-http_realip_module
make
make install
  • 而後去上一步中本身指定的 Nginx 安裝目錄
#屏蔽默認的 nginx.conf 文件的 server 塊內容,並添加一行
include /Users/mtdp/myproject/nginx/test_nginx/conf/example/*.conf;
# 在 example 目錄下創建 realip.conf,set_real_ip_from 能夠設置爲本身的本機 IP
server {
    listen 80;
    server_name ziyang.realip.com;
    error_log /Users/mtdp/myproject/nginx/nginx/logs/myerror.log debug;
    set_real_ip_from 192.168.0.108;
    #real_ip_header X-Real-IP;
    real_ip_recursive off;
    # real_ip_recursive on;
    real_ip_header X-Forwarded-For;

    location / {
        return 200 "Client real ip: $remote_addr\n";
    }
}

在上面的配置文件中,我設置了可信代理地址爲本機地址,real_ip_recursive 爲默認的 off,real_ip_header 設爲從 X-Forwarded-For 中取。

  • 重載配置文件
./sbin/nginx -s reload
  • 測試響應結果
➜  test_nginx curl -H 'X-Forwarded-For: 1.1.1.1,192.168.0.108' ziyang.realip.com
Client real ip: 192.168.0.108

而後再來測試 real_ip_recursive 打開的狀況:

  • 配置文件中打開 real_ip_recursive
server {
    listen 80;
    server_name ziyang.realip.com;
    error_log /Users/mtdp/myproject/nginx/nginx/logs/myerror.log debug;
    set_real_ip_from 192.168.0.108;
    #real_ip_header X-Real-IP;
    #real_ip_recursive off;
    real_ip_recursive on;
    real_ip_header X-Forwarded-For;

    location / {
        return 200 "Client real ip: $remote_addr\n";
    }
}
  • 測試響應結果
➜  test_nginx curl -H 'X-Forwarded-For: 1.1.1.1,2.2.2.2,192.168.0.108' ziyang.realip.com
Client real ip: 2.2.2.2

因此這裏面也可看出來,若是使用 X-Forwarded-For 獲取 realip 的話,須要打開 real_ip_recursive,而且,realip 依賴於 set_real_ip_from 設置的可信地址。

那麼有人可能就會問了,那直接用 X-Real-IP 來選取真實的 IP 地址不就行了。這是能夠的,可是 X-Real-IP 是 Nginx 獨有的,不是 RFC 規範,若是客戶端與服務器之間還有其餘非 Nginx 軟件實現的代理,就會形成取不到 X-Real-IP 頭部,因此這個要根據實際狀況來定。

rewrite 階段的 rewrite 模塊

下面來看一下 rewrite 模塊。

首先 rewrite 階段分爲兩個,一個是 server_rewrite 階段,一個是 rewrite,這兩個階段都涉及到一個 rewrite 模塊,而在 rewrite 模塊中,有一個 return 指令,遇到該指令就不會再向下執行,直接返回響應。

return 指令

return 指令的語法以下:

  • 返回狀態碼,後面跟上 body
  • 返回狀態碼,後面跟上 URL
  • 直接返回 URL
Syntax: return code [text];
        return code URL;
        return URL;
Default: —
Context: server, location, if

返回狀態碼包括如下幾種:

  • Nginx 自定義

    • 444:馬上關閉鏈接,用戶收不到響應
  • HTTP 1.0 標準

    • 301:永久重定向
    • 302:臨時重定向,禁止被緩存
  • HTTP 1.1 標準

    • 303:臨時重定向,容許改變方法,禁止被緩存
    • 307:臨時重定向,不容許改變方法,禁止被緩存
    • 308:永久重定向,不容許改變方法

return 指令與 error_page

error_page 的做用你們確定常常見到。當訪問一個網站出現 404 的時候,通常不會直接出現一個 404 NOT FOUND,而是會有一個比較友好的頁面,這就是 error_page 的功能。

Syntax: error_page code ... [=[response]] uri;
Default: —
Context: http, server, location, if in location

咱們來看幾個例子:

1. error_page 404 /404.html; 
2. error_page 500 502 503 504 /50x.html;
3. error_page 404 =200 /empty.gif; 
4. error_page 404 = /404.php; 
5. location / { 
       error_page 404 = @fallback; 
   } 
   location @fallback { 
       proxy_pass http://backend; 
   } 
6. error_page 403 http://example.com/forbidden.html; 
7. error_page 404 =301 http://example.com/notfound.html;

那麼如今就會有兩個問題,你們看下下面這個配置文件:

server {
    server_name ziyang.return.com;
    listen 80;
    root html/;
    error_page 404 /403.html;
    #return 405;
    location / {
        #return 404 "find nothing!";
    }
}
  1. 當 server 下包含 error_page 且 location 下有 return 指令的時候,會執行哪個呢?
  2. return 指令同時出如今 server 塊下和同時出如今 location 塊下,它們有合併關係嗎?

這兩個問題咱們經過實戰驗證一下。

實戰

  • 將上面的配置添加到配置文件 return.conf
  • 在本機的 hosts 文件中綁定 ziyang.return.com 爲本地的 IP 地址
  • 訪問一個不存在的頁面
➜  test_nginx curl  ziyang.return.com/text
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

這個時候能夠看到,是 error_page 生效了,返回的響應是 403。

那麼假如打開了 locationreturn 指令的註釋呢?

  • 打開 return 指令註釋,reload 配置文件
  • 從新訪問頁面
➜  test_nginx curl  ziyang.return.com/text
find nothing!%

這時候,return 指令獲得了執行。也就是第一個問題,當 server 下包含 error_pagelocation 下有 return 指令的時候,會執行 return 指令。

下面再看一下 server 下的 return 指令和 location 下的 return 指令會執行哪個。

  • 打開 serverreturn 指令的註釋,reload 配置文件
  • 從新訪問頁面
➜  test_nginx curl  ziyang.return.com/text
<html>
<head><title>405 Not Allowed</title></head>
<body>
<center><h1>405 Not Allowed</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

針對上面兩個問題也就有了答案:

  1. server 下包含 error_pagelocation 下有 return 指令的時候,會執行哪個呢?

    會執行 location 下的 return 指令。

  2. return 指令同時出如今 server 塊下和同時出如今 location 塊下,它們有合併關係嗎?

    沒有合併關係,先遇到哪一個 return 指令就先執行哪個。

rewrite 指令

rewrite 指令用於修改用戶傳入 Nginx 的 URL。來看下 rewrite 的指令規則:

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

它的功能主要有下面幾點:

  • regex 指定的 URL 替換成 replacement 這個新的 URL

    • 可使用正則表達式及變量提取
  • replacementhttp:// 或者 https:// 或者 $schema 開頭,則直接返回 302 重定向
  • 替換後的 URL 根據 flag 指定的方式進行處理

    • last:用 replacement 這個 URL 進行新的 location 匹配
    • break:break 指令中止當前腳本指令的執行,等價於獨立的 break 指令
    • redirect:返回 302 重定向
    • permanent:返回 301 重定向

指令示例

如今咱們有這樣的一個目錄結構:

html/first/
└── 1.txt
html/second/
└── 2.txt
html/third/
└── 3.txt

配置文件以下所示:

server {
    listen 80;
    server_name rewrite.ziyang.com;
    rewrite_log on;
    error_log logs/rewrite_error.log notice;

    root html/;
    location /first {
        rewrite /first(.*) /second$1 last;
        return 200 'first!\n';
    }

    location /second {
        rewrite /second(.*) /third$1;
        return 200 'second!\n';
    }

    location /third {
        return 200 'third!\n';
    }
    location /redirect1 {
        rewrite /redirect1(.*) $1 permanent;
    }

    location /redirect2 {
        rewrite /redirect2(.*) $1 redirect;
    }

    location /redirect3 {
        rewrite /redirect3(.*) http://rewrite.ziyang.com$1;
    }

    location /redirect4 {
        rewrite /redirect4(.*) http://rewrite.ziyang.com$1 permanent;
    }
}

那麼咱們的問題是:

  1. return 指令 與 rewrite 指令的順序關係?
  2. 訪問 /first/3.txt,/second/3.txt,/third/3.txt 分別返回的是什麼?
  3. 若是不攜帶 flag 會怎麼樣?

帶着這三個問題,咱們來實際演示一下。

實戰

準備工做

  • 將上面的配置添加到配置文件 rewrite.conf
  • 在本機的 hosts 文件中綁定 rewrite.ziyang.com 爲 127.0.0.1

last flag

首先訪問 rewrite.ziyang.com/first/3.txt,結果以下:

➜  ~ curl rewrite.ziyang.com/first/3.txt
second!

爲何結果是 second! 呢?應該是 third! 呀,可能有人會有這樣的疑問。實際的匹配步驟以下:

  • curl rewrite.ziyang.com/first/3.txt
  • 因爲 rewrite /first(.*) /second$1 last; 這條指令的存在,last 表示使用新的 URL 進行 location 匹配,所以接下來會去匹配 second/3.txt
  • 匹配到 /second 塊以後,會依次執行指令,最後返回 200
  • 注意,location 塊中雖然也改寫了 URL,可是並不會去繼續匹配,由於後面沒有指定 flag。

break flag

下面將 rewrite /second(.*) /third$1; 這條指令加上 break flag,rewrite /second(.*) /third$1 break;

繼續訪問 rewrite.ziyang.com/first/3.txt,結果以下:

➜  ~ curl rewrite.ziyang.com/first/3.txt
test3%

這時候返回的是 3.txt 文件的內容 test3。實際的匹配步驟以下:

  • curl rewrite.ziyang.com/first/3.txt
  • 因爲 rewrite /first(.*) /second$1 last; 這條指令的存在,last 表示使用新的 URL 進行 location 匹配,所以接下來會去匹配 second/3.txt
  • 匹配到 /second 塊以後,因爲 break flag 的存在,會繼續匹配 rewrite 事後的 URL
  • 匹配 /third location

所以,這個過程實際請求的 URL 是 rewrite.ziyang.com/third/3.txt,這樣天然結果就是 test3 了。你還能夠試試訪問 rewrite.ziyang.com/third/2.txt 看看會返回什麼。

redirect 和 permanent flag

配置文件中還有 4 個 location,你能夠分別試着訪問一下,結果是這樣的:

  • redirect1:返回 301
  • redirect2:返回 302
  • redirect3:返回 302
  • redirect4:返回 301

rewrite 行爲記錄日誌

主要是一個指令 rewrite_log

Syntax: rewrite_log on | off;
Default: rewrite_log off; 
Context: http, server, location, if

這個指令打開以後,會把 rewrite 的日誌寫入 logs/rewrite_error.log 日誌文件中,這是請求 /first/3.txt 的日誌記錄:

2020/05/06 06:24:05 [notice] 86959#0: *25 "/first(.*)" matches "/first/3.txt", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"
2020/05/06 06:24:05 [notice] 86959#0: *25 rewritten data: "/second/3.txt", args: "", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"
2020/05/06 06:24:05 [notice] 86959#0: *25 "/second(.*)" matches "/second/3.txt", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"
2020/05/06 06:24:05 [notice] 86959#0: *25 rewritten data: "/third/3.txt", args: "", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"

if 指令

if 指令也是在 rewrite 階段生效的,它的語法以下所示:

Syntax: if (condition) { ... }
Default: —
Context: server, location

它的規則是:

  • 條件 condition 爲真,則執行大括號內的指令;同時還遵循值指令的繼承規則(詳見我以前的文章 Nginx 的配置指令

那麼 if 指令的條件表達式包含哪些內容呢?它的規則以下:

  1. 檢查變量爲空或者值是否爲 0
  2. 將變量與字符串作匹配,使用 = 或 !=
  3. 將變量與正則表達式作匹配

    • 大小寫敏感,~ 或者 !~
    • 大小寫不敏感,~ 或者 !~
  4. 檢查文件是否存在,使用 -f 或者 !-f
  5. 檢查目錄是否存在,使用 -d 或者 !-d
  6. 檢查文件、目錄、軟連接是否存在,使用 -e 或者 !-e
  7. 檢查是否爲可執行文件,使用 -x 或者 !-x

下面是一些例子:

if ($http_user_agent ~ MSIE) { # 與變量 http_user_agent 匹配
    rewrite ^(.*)$ /msie/$1 break; 
} 
if ($http_cookie ~* "id=([^;]+)(?:;|$)") { # 與變量 http_cookie 匹配
    set $id $1; 
} 
if ($request_method = POST) { # 與變量 request_method 匹配,獲取請求方法
    return 405; 
} 
if ($slow) { # slow 變量在 map 模塊中自定義,也能夠進行匹配
    limit_rate 10k; 
} 
if ($invalid_referer) { 
    return 403; 
}

find_config 階段

當通過 rewrite 模塊,匹配到 URL 以後,就會進入 find_config 階段,開始尋找 URL 對應的 location 配置。

location 指令

指令語法

仍是老規矩,我們先來看一下 location 指令的語法:

Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
        location @name { ... }
Default: —
Context: server, location

Syntax: merge_slashes on | off;
Default: merge_slashes on; 
Context: http, server

這裏面有一個 merge_slashes 指令,這個指令的做用是,加入 URL 中有兩個重複的 /,那麼會合併爲一個,這個指令默認是打開的,只有當對 URL 進行 base64 之類的編碼時才須要關閉。

匹配規則

location 的匹配規則是僅匹配 URI,忽略參數,有下面三種大的狀況:

  • 前綴字符串

    • 常規匹配
    • =:精確匹配
    • ^~:匹配上後則再也不進行正則表達式匹配
  • 正則表達式

    • ~:大小寫敏感的正則匹配
    • ~*:大小寫不敏感
  • 用戶內部跳轉的命名 location

    • @

對於這些規則剛看上去確定是很懵的,徹底不知道在說什麼,下面來實戰看幾個例子。

實戰

先看一下 Nginx 的配置文件:

server {
    listen 80;
    server_name location.ziyang.com;
    error_log  logs/error.log  debug;
    #root html/;
    default_type text/plain;
    merge_slashes off;
    
    location ~ /Test1/$ {
        return 200 'first regular expressions match!\n';
    }
    location ~* /Test1/(\w+)$ {
        return 200 'longest regular expressions match!\n';
    }
    location ^~ /Test1/ {
        return 200 'stop regular expressions match!\n';
    }
    location /Test1/Test2 {
        return 200 'longest prefix string match!\n';
    }
    location /Test1 {
        return 200 'prefix string match!\n';
    }
    location = /Test1 {
        return 200 'exact match!\n';
    }
}

問題就來了,訪問下面幾個 URL 會分別返回什麼內容呢?

/Test1
/Test1/
/Test1/Test2
/Test1/Test2/
/test1/Test2

例如訪問 /Test1 時,會有幾個部分都匹配上:

  1. 常規前綴匹配:location /Test1
  2. 精確匹配:location = /Test1

訪問 /Test1/ 時,也會有幾個部分匹配上:

  1. location ~ /Test1/$
  2. location ^~ /Test1/

那麼究竟會匹配哪個呢?Nginx 實際上是遵循一套規則的,以下圖所示:

所有的前綴字符串是放置在一棵二叉樹中的,Nginx 會分爲兩部分進行匹配:

  1. 先遍歷全部的前綴字符串,選取最長的一個前綴字符串,若是這個字符串是 = 的精確匹配或 ^~ 的前綴匹配,會直接使用
  2. 若是第一步中沒有匹配上 = 或 ^~,那麼會先記住最長匹配的前綴字符串 location
  3. 按照 nginx.conf 文件中的配置依次匹配正則表達式
  4. 若是全部的正則表達式都沒有匹配上,那麼會使用最長匹配的前綴字符串

下面看下實際的響應是怎麼樣的:

➜  test_nginx curl location.ziyang.com/Test1
exact match!
➜  test_nginx curl location.ziyang.com/Test1/
stop regular expressions match!
➜  test_nginx curl location.ziyang.com/Test1/Test2
longest regular expressions match!
➜  test_nginx curl location.ziyang.com/Test1/Test2/
longest prefix string match!
➜  test_nginx curl location.ziyang.com/Test1/Test3
stop regular expressions match!
  • /Test1 匹配 location = /Test1
  • /Test1/ 匹配 location ^~ /Test1/
  • /Test1/Test2 匹配 location ~* /Test1/(w+)$
  • /Test1/Test2/ 匹配 location /Test1/Test2
  • /Test1/Test3 匹配 location ^~ /Test1/

這裏面重點解釋一下 /Test1/Test3 的匹配過程:

  1. 遍歷全部能夠匹配上的前綴字符串,總共有兩個

    • ^~ /Test1/
    • /Test1
  2. 選取最長的前綴字符串 /Test1/,因爲前面有 ^~ 禁止正則表達式匹配,所以直接使用 location ^~ /Test1/ 的規則
  3. 返回 stop regular expressions match!

preaccess 階段

下面就來到了 preaccess 階段。咱們常常會遇到一個問題,就是如何限制每一個客戶端的併發鏈接數?如何限制訪問頻率?這些就是在 preaccess 階段處理完成的,顧名思義,preaccess 就是在鏈接以前。先來看下 limit_conn 模塊。

limit_conn 模塊

這裏面涉及到的模塊是 ngx_http_limit_conn_module,它的基本特性以下:

  • 生效階段:NGX_HTTP_PREACCESS_PHASE 階段
  • 模塊:http_limit_conn_module
  • 默認編譯進 Nginx,經過 --without-http_limit_conn_module 禁用
  • 生效範圍

    • 所有 worker 進程(基於共享內存)
    • 進入 preaccess 階段前不生效
    • 限制的有效性取決於 key 的設計:依賴 postread 階段的 realip 模塊取到真實 IP

這裏面有一點須要注意,就是 limit_conn key 的設計,所謂的 key 指的就是對哪一個變量進行限制,一般咱們取的都是用戶的真實 IP。

說完了 limit_conn 的模塊,再來講一下指令語法。

指令語法

  • 定義共享內存(包括大小),以及 key 關鍵字
Syntax: limit_conn_zone key zone=name:size;
Default: —
Context: http
  • 限制併發鏈接數
Syntax: limit_conn zone number;
Default: —
Context: http, server, location
  • 限制發生時的日誌級別
Syntax: limit_conn_log_level info | notice | warn | error;
Default: limit_conn_log_level error; 
Context: http, server, location
  • 限制發生時向客戶端返回的錯誤碼
Syntax: limit_conn_status code;
Default: limit_conn_status 503; 
Context: http, server, location

實戰

下面又到了實戰的環節了,經過一個實際的例子來看一下以上的幾個指令是怎麼起做用的。

老規矩,先上配置文件:

limit_conn_zone $binary_remote_addr zone=addr:10m;
#limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m;

server {
    listen 80;
    server_name limit.ziyang.com;
    root html/;
    error_log logs/myerror.log info;
    location /{
        limit_conn_status 500;
        limit_conn_log_level  warn;
        limit_rate 50;
        limit_conn addr 1;
        #limit_req zone=one burst=3 nodelay;
        #limit_req zone=one;
    }
}
  • 在本地的 hosts 文件中添加 limit.ziyang.com 爲本機 IP

在這個配置文件中,作了兩條限制,一個是 limit_rate 限制爲 50 個字節,併發鏈接數 limit_conn 限制爲 1。

➜  test_nginx curl limit.ziyang.com

這時候訪問 limit.ziyang.com 這個站點,會發現速度很是慢,由於每秒鐘只有 50 個字節。

若是再同時訪問這個站點的話,則會返回 500。

我在另外一個終端裏面同時訪問:

➜  ~ curl limit.ziyang.com
<html>
<head><title>500 Internal Server Error</title></head>
<body>
<center><h1>500 Internal Server Error</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

能夠看到,Nginx 直接返回了 500。

limit_req 模塊

在本節開頭咱們就提出了兩個問題:

  • 如何限制每一個客戶端的併發鏈接數?
  • 如何限制訪問頻率?

第一個問題限制併發鏈接數的問題已經解決了,下面來看第二個問題。

這裏面生效的模塊是 ngx_http_limit_req_module,它的基本特性以下:

  • 生效階段:NGX_HTTP_PREACCESS_PHASE 階段
  • 模塊:http_limit_req_module
  • 默認編譯進 Nginx,經過 --without-http_limit_req_module 禁用
  • 生效算法:leaky bucket 算法
  • 生效範圍

    • 所有 worker 進程(基於共享內存)
    • 進入 preaccess 階段前不生效

leaky bucket 算法

leaky bucket 叫漏桶算法,其餘用來限制請求速率的還有令牌環算法等,這裏面不展開講。

漏桶算法的原理是,先定義一個桶的大小,全部進入桶內的請求都會以恆定的速率被處理,若是請求太多超出了桶的容量,那麼就會馬上返回錯誤。用一張圖解釋一下。

這張圖裏面,水龍頭在不停地滴水,就像用戶發來的請求,全部的水滴都會以恆定的速率流出去,也就是被處理。漏桶算法對於突發流量有很好的限制做用,會將全部的請求平滑的處理掉。

指令語法

  • 定義共享內存(包括大小),以及 key 關鍵字和限制速率
Syntax: limit_req_zone key zone=name:size rate=rate ;
Default: —
Context: http
rate 單位爲 r/s 或者 r/m(每分鐘或者每秒處理多少個請求)
  • 限制併發鏈接數
Syntax: limit_req zone=name [burst=number] [nodelay];
Default: —
Context: http, server, location
  • burst 默認爲 0
  • nodelay,若是設置了這個參數,那麼對於漏桶中的請求也會馬上返回錯誤
  • 限制發生時的日誌級別
Syntax: limit_req_log_level info | notice | warn | error;
Default: limit_req_log_level error; 
Context: http, server, location
  • 限制發生時向客戶端返回的錯誤碼
Syntax: limit_req_status code;
Default: limit_req_status 503; 
Context: http, server, location

實戰

在實際驗證以前呢,須要注意兩個問題:

  • limit_req 與 limit_conn 配置同時生效時,哪一個優先級高?
  • nodelay 添加與否,有什麼不一樣?

添加配置文件,這個配置文件與上一節的配置文件實際上是相同的只不過須要註釋一下:

limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m;

server {
    listen 80;
    server_name limit.ziyang.com;
    root html/;
    error_log logs/myerror.log info;
    
    location /{
        limit_conn_status 500;
        limit_conn_log_level  warn;
        #limit_rate 50;
        #limit_conn addr 1;
        #limit_req zone=one burst=3 nodelay;
        limit_req zone=one;
    }
}

結論:在 limit_req zone=one 指令下,超出每分鐘處理的請求數後就會馬上返回 503。

➜  test_nginx curl limit.ziyang.com
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body>
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

改變一下注釋的指令:

limit_req zone=one burst=3;
#limit_req zone=one;

在沒有添加 burst 參數時,會馬上返回錯誤,而加上以後,不會返回錯誤,而是等待請求限制解除,直到能夠處理請求時再返回。

再來看一下 nodelay 參數:

limit_req zone=one burst=3 nodelay;

添加了 nodelay 以後,請求在沒有達到 burst 限制以前均可以馬上被處理並返回,超出了 burst 限制以後,纔會返回 503。

如今能夠回答一下剛開始提出的兩個問題:

  • limit_req 與 limit_conn 配置同時生效時,哪一個優先級高?

    • limit_req 在 limit_conn 處理以前,所以是 limit_req 會生效
  • nodelay 添加與否,有什麼不一樣?

    • 不添加 nodelay,請求會等待,直到可以處理請求;添加 nodelay,在不超出 burst 的限制的狀況下會馬上處理並返回,超出限制則會返回 503。

access 階段

通過 preaccess 階段對用戶的限流以後,就到了 access 階段。

access 模塊

這裏面涉及到的模塊是 ngx_http_access_module,它的基本特性以下:

  • 生效階段:NGX_HTTP_ACCESS_PHASE 階段
  • 模塊:http_access_module
  • 默認編譯進 Nginx,經過 --without-http_access_module 禁用
  • 生效範圍

    • 進入 access 階段前不生效

指令語法

Syntax: allow address | CIDR | unix: | all;
Default: —
Context: http, server, location, limit_except

Syntax: deny address | CIDR | unix: | all;
Default: —
Context: http, server, location, limit_except

access 模塊提供了兩條指令 allowdeny,來看幾個例子:

location / { 
    deny 192.168.1.1; 
    allow 192.168.1.0/24; 
    allow 10.1.1.0/16; 
    allow 2001:0db8::/32; 
    deny all; 
}

對於用戶訪問來講,這些指令是順序執行的,當知足了一條以後,就不會再向下執行。這個模塊比較簡單,咱們這裏不作實戰演練了。

auth_basic 模塊

auth_basic 模塊是用做用戶認證的,當開啓了這個模塊以後,咱們經過瀏覽器訪問網站時,就會返回一個 401 Unauthorized,固然這個 401 用戶不會看見,瀏覽器會彈出一個對話框要求輸入用戶名和密碼。這個模塊使用的是 RFC2617 中的定義。

指令語法

  • 基於 HTTP Basic Authutication 協議進行用戶密碼的認證
  • 默認編譯進 Nginx

    • --without-http_auth_basic_module
    • disable ngx_http_auth_basic_module
Syntax: auth_basic string | off;
Default: auth_basic off; 
Context: http, server, location, limit_except

Syntax: auth_basic_user_file file;
Default: —
Context: http, server, location, limit_except

這裏面咱們會用到一個工具叫 htpasswd,這個工具能夠用來生成密碼文件,而 auth_basic_user_file 就依賴這個密碼文件。

htpasswd 依賴安裝包 httpd-tools

生成密碼的命令爲:

htpasswd –c file –b user pass

生成的密碼文件的格式爲:

# comment 
name1:password1 
name2:password2:comment 
name3:password3

實戰

  • 在 example 目錄下生成密碼文件 auth.pass
htpasswd -bc auth.pass ziyang 123456
  • 添加配置文件
server {
    server_name access.ziyang.com;
    listen 80;
    error_log  logs/error.log  debug;
    default_type text/plain;
    location /auth_basic {
        satisfy any;
        auth_basic "test auth_basic";
        auth_basic_user_file example/auth.pass;
        deny all;
    }
}
  • 重載 Nginx 配置文件
  • 在 /etc/hosts 文件中添加 access.ziyang.com

這時候訪問 access.ziyang.com 就會彈出對話框,提示輸入密碼:

auth_request 模塊

  • 功能:向上遊的服務轉發請求,若上游服務返回的響應碼是 2xx,則繼續執行,若上游服務返回的響應碼是 2xx,則繼續執行,若上游服務返回的是 401 或者 403,則將響應返回給客戶端
  • 原理:收到請求後,生成子請求,經過反向代理技術把請求傳遞給上游服務
  • 默認未編譯進 Nginx,須要經過 --with-http_auth_request_module 編譯進去

指令語法

Syntax: auth_request uri | off;
Default: auth_request off; 
Context: http, server, location

Syntax: auth_request_set $variable value;
Default: —
Context: http, server, location

實戰

  • 在上一個配置文件中添加如下內容
server {
    server_name access.ziyang.com;
    listen 80;
    error_log  logs/error.log  debug;
    #root html/;
    default_type text/plain;
    location /auth_basic {
        satisfy any;
        auth_basic "test auth_basic";
        auth_basic_user_file example/auth.pass;
        deny all;
    }
    location / {
        auth_request /test_auth;
    }
    location = /test_auth {
        proxy_pass http://127.0.0.1:8090/auth_upstream;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
}
  • 這個配置文件中,/ 路徑下會將請求轉發到另一個服務中去,能夠用 nginx 再搭建一個服務
  • 若是這個服務返回 2xx,那麼鑑權成功,若是返回 401 或 403 則鑑權失敗

限制全部 access 階段模塊的 satisfy 指令

指令語法

Syntax: satisfy all | any;
Default: satisfy all; 
Context: http, server, location

satisfy 指令有兩個值一個是 all,一個是 any,這個模塊對 acces 階段的三個模塊都生效:

  • access 模塊
  • auth_basic 模塊
  • auth_request 模塊
  • 其餘模塊

若是 satisfy 指令的值是 all 的話,就表示必須全部 access 階段的模塊都要執行,都經過了纔會放行;值是 any 的話,表示有任意一個模塊獲得執行便可。

下面有幾個問題能夠加深一下理解:

  1. 若是有 return 指令,access 階段會生效嗎?

    return 指令屬於 rewrite 階段,在 access 階段以前,所以不會生效。

  2. 多個 access 模塊的順序有影響嗎?

    ngx_http_auth_request_module,
    ngx_http_auth_basic_module,
    ngx_http_access_module,

    有影響

  3. 輸對密碼,下面能夠訪問到文件嗎?

    location /{
        satisfy any;
        auth_basic "test auth_basic";
        auth_basic_user_file examples/auth.pass;
        deny all;
    }

    能夠訪問到,由於 satisfy 的值是 any,所以只要有模塊知足,便可放行。

  4. 若是把 deny all 提到 auth_basic 以前呢?

    依然能夠,由於各個模塊執行順序和指令的順序無關。

  5. 若是改成 allow all,有機會輸入密碼嗎?

    沒有機會,由於 allow all 是 access 模塊,先於 auth_basic 模塊執行。

precontent 階段

講到了這裏,咱們再來回顧一下 Nginx 處理 HTTP 請求的 11 個階段:

如今咱們已經來到了 precontent 階段,這個階段只有 try_files 這一個指令。

try_files 模塊

指令語法

Syntax: try_files file ... uri;
        try_files file ... =code;
Default: —
Context: server, location
  • 模塊:ngx_http_try_files_module 模塊
  • 依次試圖訪問多個 URL 對應的文件(由 root 或者 alias 指令指定),當文件存在時,直接返回文件內容,若是全部文件都不存在,則按照最後一個 URL 結果或者 code 返回

實戰

下面咱們實際看一個例子:

server {
    server_name tryfiles.ziyang.com;
    listen 80;
    error_log  logs/myerror.log  info;
    root html/;
    default_type text/plain;
    location /first {
        try_files /system/maintenance.html
            $uri $uri/index.html $uri.html
            @lasturl;
    }
    location @lasturl {
        return 200 'lasturl!\n';
    }
    location /second {
        try_files $uri $uri/index.html $uri.html =404;
    }
}

結果以下:

  • 訪問 /first 實際上到了 lasturl,而後返回 200
  • 訪問 /second 則返回了 404

這兩個結果都與配置文件是一致的。

➜  test_nginx curl tryfiles.ziyang.com/second
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
➜  test_nginx curl tryfiles.ziyang.com/first 
lasturl!

mirror 模塊

mirror 模塊能夠實時拷貝流量,這對於須要同時訪問多個環境的請求是很是有用的。

指令語法

  • 模塊:ngx_http_mirror_module 模塊,默認編譯進 Nginx

    • 經過 --without-http_mirror_module 移除模塊
  • 功能:處理請求時,生成子請求訪問其餘服務,對子請求的返回值不作處理
Syntax: mirror uri | off;
Default: mirror off; 
Context: http, server, location

Syntax: mirror_request_body on | off;
Default: mirror_request_body on; 
Context: http, server, location

實戰

  • 配置文件以下所示,須要再開啓另一個 Nginx 來接收請求
server {
    server_name mirror.ziyang.com;
    listen 8001;
    error_log logs/error_log debug;
    location / {
        mirror /mirror;
        mirror_request_body off;
    }
    location = /mirror {
        internal;
        proxy_pass http://127.0.0.1:10020$request_uri;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
}
  • 在 access.log 文件中能夠看到有請求記錄日誌

content 階段

下面開始就到了 content 階段,先來看 content 階段的 static 模塊,雖然這是位於 content 階段的最後一個處理模塊,可是這裏先來介紹它。

static 模塊

root 和 alias 指令

先來一下 root 和 alias 這兩個指令,這兩個指令都是用來映射文件路徑的。

Syntax: alias path;
Default: —
Context: location
Syntax: root path;
Default: root html; 
Context: http, server, location, if in location
  • 功能:將 URL 映射爲文件路徑,以返回靜態文件內容
  • 差異:root 會將完整 URL 映射進文件路徑中,alias 只會將 location 後的 URL 映射到文件路徑

實戰

下面來看一個問題:

如今有一個文件路徑:

html/first/
└── 1.txt

配置文件以下所示:

server {
    server_name static.ziyang.com;
    listen 80;
    error_log  logs/myerror.log  info;
    location /root {
        root html;
    }
    location /alias {
        alias html;
    }
    location ~ /root/(\w+\.txt) {
        root html/first/$1;
    }
    location ~ /alias/(\w+\.txt) {
        alias html/first/$1;
    }
    location  /RealPath/ {
        alias html/realpath/;
        return 200 '$request_filename:$document_root:$realpath_root\n';
    }
}

那麼訪問如下 URL 會獲得什麼響應呢?

/root
/alias
/root/1.txt
/alias/1.txt
➜  test_nginx curl static.ziyang.com/alias/1.txt
test1%
➜  test_nginx curl static.ziyang.com/alias/     
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
➜  test_nginx curl static.ziyang.com/root/      
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
➜  test_nginx curl static.ziyang.com/root/1.txt
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

訪問這四個路徑分別獲得的結果是:

  • /root:404
  • /alias:200
  • /root/1.txt:404
  • /alias/1.txt:200

這是爲何呢?是由於,root 在映射 URL 時,會把 location 中的路徑也加進去,也就是:

  • static.ziyang.com/root/ 實際訪問的是 html/root/
  • static.ziyang.com/root/1.txt 實際是 html/first/1.txt/root/1.txt
  • static.ziyang.com/alias/ 其實是正確訪問到了 html 文件夾,因爲後面有 / 的存在,所以實際訪問的是 html/index.html
  • static.ziyang.com/alias/1.txt 實際訪問的是 html/first/1.txt,文件存在

三個相關變量

仍是上面的配置文件:

location  /RealPath/ {
    alias html/realpath/;
    return 200 '$request_filename:$document_root:$realpath_root\n';
}

這裏有一個問題,在訪問 /RealPath/1.txt 時,這三個變量的值各爲多少?

爲了解答這個問題,咱們先來解釋三個變量:

  • request_filename:待訪問文件的完整路徑
  • document_root:由 URI 和 root/alias 指令生成的文件夾路徑(可能包含軟連接的路徑)
  • realpath_root:將 document_root 中的軟連接替換成真實路徑

爲了驗證這三個變量,在 html 目錄下創建一個軟連接指向 first 文件夾:

ln -s first realpath
➜  html curl static.ziyang.com/realpath/1.txt
/Users/mtdp/myproject/nginx/test_nginx/html/realpath/1.txt:/Users/mtdp/myproject/nginx/test_nginx/html/realpath/:/Users/mtdp/myproject/nginx/test_nginx/html/first

能夠看出來,三個路徑分別是:

  • /Users/mtdp/myproject/nginx/test_nginx/html/realpath/1.txt
  • /Users/mtdp/myproject/nginx/test_nginx/html/realpath/
  • /Users/mtdp/myproject/nginx/test_nginx/html/first

還有其餘的一些配置指令,例如:

靜態文件返回時的 Content-Type

Syntax: types { ... }
Default: types { text/html html; image/gif gif; image/jpeg jpg; } 
Context: http, server, location

Syntax: default_type mime-type;
Default: default_type text/plain; 
Context: http, server, location

Syntax: types_hash_bucket_size size;
Default: types_hash_bucket_size 64; 
Context: http, server, location

Syntax: types_hash_max_size size;
Default: types_hash_max_size 1024; 
Context: http, server, location

未找到文件時的錯誤日誌

Syntax: log_not_found on | off;
Default: log_not_found on; 
Context: http, server, location

在生產環境中,常常可能會有找不到文件的狀況,錯誤日誌中就會打印出來:

[error] 10156#0: *10723 open() "/html/first/2.txt/root/2.txt" failed (2: No such file or directory)

若是不想記錄日誌,能夠關掉。

重定向跳轉的域名

如今有另一個問題,當咱們訪問目錄時最後沒有帶 /,static 模塊會返回 301 重定向,那麼這個規則是怎麼定義的呢,看下面三個指令:

# 該指令決定重定向時的域名,能夠決定返回哪一個域名
Syntax: server_name_in_redirect on | off;
Default: server_name_in_redirect off; 
Context: http, server, location
# 該指令決定重定向時的端口
Syntax: port_in_redirect on | off;
Default: port_in_redirect on; 
Context: http, server, location
# 該指令決定是否填域名,默認是打開的,也就是返回絕對路徑
Syntax: absolute_redirect on | off;
Default: absolute_redirect on; 
Context: http, server, location

這三個指令的實際用法來實戰演示一下,先來看配置文件:

server {
    server_name return.ziyang.com dir.ziyang.com;
    server_name_in_redirect on;
    listen 8088;
    port_in_redirect on;
    absolute_redirect off;

    root html/;
}

absolute_redirect 默認是打開的,咱們把它關閉了,看下是怎麼返回的:

➜  test_nginx curl localhost:8088/first -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.17.8
Date: Tue, 12 May 2020 00:31:36 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: /first/

這個時候看到返回的頭部 Location 中沒有加上域名。

下面再把 absolute_redirect 打開(默認是打開的,所以註釋掉就好了),看下返回什麼:

  • absolute_redirect on
  • server_name_in_redirect on
  • port_in_redirect on
➜  test_nginx curl localhost:8088/first -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.17.8
Date: Tue, 12 May 2020 00:35:49 GMT
Content-Type: text/html
Content-Length: 169
Location: http://return.ziyang.com:8088/first/
Connection: keep-alive

能夠看到,這時候就返回了域名,並且返回的是咱們配置的主域名加端口號,這是由於,server_name_in_redirectport_in_redirect 這兩個指令打開了,若是關閉掉這兩個指令,看下返回什麼:

  • absolute_redirect on
  • server_name_in_redirect off
  • port_in_redirect off
➜  test_nginx curl localhost:8088/first -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.17.8
Date: Tue, 12 May 2020 00:39:31 GMT
Content-Type: text/html
Content-Length: 169
Location: http://localhost/first/
Connection: keep-alive

這兩個指令都設置爲 off 以後,會發現返回的再也不是主域名加端口號,而是咱們請求的域名和端口號,若是在請求頭中加上 Host,那麼就會用 Host 請求頭中的域名。

index 模塊

  • 模塊:ngx_http_index_module
  • 功能:指定 / 結尾的目錄訪問時,返回 index 文件內容
  • 語法:

    Syntax: index file ...;
    Default: index index.html; 
    Context: http, server, location
  • 先於 autoindex 模塊執行

這個模塊,當咱們訪問以 / 結尾的目錄時,會去找 root 或 alias 指令的文件夾下的 index.html,若是有這個文件,就會把文件內容返回,也能夠指定其餘文件。

autoindex 模塊

  • 模塊:ngx_http_autoindex_module,默認編譯進 Nginx,使用 --without-http_autoindex_module 取消
  • 功能:當 URL 以 / 結尾時,嘗試以 html/xml/json/jsonp 等格式返回 root/alias 中指向目錄的目錄結構
  • 語法:

    # 開啓或關閉
    Syntax: autoindex on | off;
    Default: autoindex off; 
    Context: http, server, location
    # 當以 HTML 格式輸出時,控制是否轉換爲 KB/MB/GB
    Syntax: autoindex_exact_size on | off;
    Default: autoindex_exact_size on; 
    Context: http, server, location
    # 控制以哪一種格式輸出
    Syntax: autoindex_format html | xml | json | jsonp;
    Default: autoindex_format html; 
    Context: http, server, location
    # 控制是否以本地時間格式顯示仍是 UTC 格式
    Syntax: autoindex_localtime on | off;
    Default: autoindex_localtime off; 
    Context: http, server, location

實戰

  • 配置文件以下:
server {
    server_name autoindex.ziyang.com;
    listen 8080;
    location / {
        alias html/;
        autoindex on;
        #index b.html;
        autoindex_exact_size on;
        autoindex_format html;
        autoindex_localtime on;
    }
}

這裏我把 index b.html 這條指令給註釋掉了,而 index 模塊是默認編譯進 Nginx 的,且默認指令是 index index.html,所以,會去找是否有 index.html 這個文件。

  • 打開瀏覽器,訪問 autoindex.ziyang.com:8080,html 目錄下默認是有 index.html 文件的,所以顯示結果爲:

  • 打開 index b.html 指令註釋。因爲 html 文件夾下並不存在 b.html 這個文件,因此請求會走到 autoindex 模塊,顯示目錄:

後面的文件大小顯示格式就是由 autoindex_exact_size on; 這條指令決定的。

concat模塊

下面介紹一個能夠提高小文件性能的模塊,這個模塊是由阿里巴巴開發的,在淘寶網中有普遍應用。

  • 模塊:ngx_http_concat_module
  • 模塊開發者:Tengine(https://github.com/alibaba/ng... --add-module=../nginx-http-concat/
  • 功能:合併多個小文件請求,能夠明顯提高 HTTP 請求的性能
  • 指令:

    #在 URI 後面加上 ??,經過 」,「 分割文件,若是還有參數,則在最後經過 ? 添加參數
    concat on | off
    default concat off
    Context http, server, location
    
    concat_types MIME types
    Default concat_types: text/css application/x-javascript
    Context http, server, location
    
    concat_unique on | off
    Default concat_unique on
    Context http, server, location
    
    concat_max_files numberp
    Default concat_max_files 10
    Context http, server, location
    
    concat_delimiter string
    Default NONE
    Context http, server, locatione
    concat_ignore_file_error on | off
    Default off
    Context http, server, location

打開淘寶主頁,會發現小文件都是經過這個模塊來提升性能的:

這裏就不作實戰了,感興趣的同窗能夠本身去編譯一下這個模塊,作一下實驗,我把配置文件放在這裏:

server {
    server_name concat.ziyang.com;
    error_log logs/myerror.log debug;
    concat on;
    root html;
    location /concat {
        concat_max_files 20;
        concat_types text/plain;
        concat_unique on;
        concat_delimiter ':::';
        concat_ignore_file_error on;
    }
}

log 階段

下面終於來到了 11 個階段的最後一個階段,記錄請求訪問日誌的 log 模塊。

  • 功能:將 HTTP 請求相關信息記錄到日誌
  • 模塊:ngx_http_log_module,沒法禁用

access 日誌格式

Syntax: log_format name [escape=default|json|none] string ...;
Default: log_format combined "..."; 
Context: http

默認的 combined 日誌格式:

log_format combined '$remote_addr - $remote_user [$time_local] ' 
'"$request" $status $body_bytes_sent ' '"$http_referer" 
"$http_user_agent"';

配置日誌文件路徑

Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
        access_log off;
Default: access_log logs/access.log combined; 
Context: http, server, location, if in location, limit_except
  • path 路徑能夠包含變量:不打開 cache 時每記錄一條日誌都須要打開、關閉日誌文件
  • if 經過變量值控制請求日誌是否記錄
  • 日誌緩存

    • 功能:批量將內存中的日誌寫入磁盤
    • 寫入磁盤的條件:

      全部待寫入磁盤的日誌大小超出緩存大小;

      達到 flush 指定的過時時間;

      worker 進程執行 reopen 命令,或者正在關閉。

  • 日誌壓縮

    • 功能:批量壓縮內存中的日誌,再寫入磁盤
    • buffer 大小默認爲 64KB
    • 壓縮級別默認爲 1(1最快壓縮率最低,9最慢壓縮率最高)
    • 打開日誌壓縮時,默認打開日誌緩存功能

對日誌文件名包含變量時的優化

Syntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
        open_log_file_cache off;
Default: open_log_file_cache off; 
Context: http, server, location
  • max:緩存內的最大文件句柄數,超出後用 LRU 算法淘汰
  • inactive:文件訪問完後在這段時間內不會被關閉。默認 10 秒
  • min_uses:在 inactive 時間內使用次數超過 min_uses 纔會繼續存在內存中。默認 1
  • valid:超出 valid 時間後,將對緩存的日誌文件檢查是否存在。默認 60 秒
  • off:關閉緩存功能

日誌模塊沒有實戰。


到了這裏,咱們已經將 Nginx 處理 HTTP 請求的 11 個階段所有梳理了一遍,每一個階段基本都有對應的模塊。相信對於這樣一個全流程的解析,你們都可以看懂 Nginx 的配置了,在此之上,還可以按照需求靈活配置出本身想要的配置,這樣就真正的掌握了 11 個階段。

最後,歡迎你們關注個人我的博客:iziyang.github.io

相關文章
相關標籤/搜索