前面給你們講了 Nginx 是如何處理 HTTP請求頭部的,接下來就到了真正處理 HTTP 請求的階段了。先看下面這張圖,這張圖是 Nginx 處理 HTTP 請求的示意圖,雖然簡單,可是卻很好的說明了整個過程。javascript
以上這七個步驟從總體上介紹了一下處理流程,下面還會再說一下實際的處理過程。php
下面介紹一下詳細的 11 個階段,每一個階段均可能對應着一個甚至多個 HTTP 模塊,經過這樣一個模塊對比,咱們也可以很好的理解這些模塊具體是怎麼樣發揮做用的。css
接下來是確認用戶訪問權限的三個模塊:html
最後的三個階段處理響應和日誌:java
以上的這些階段都是嚴格按照順序進行處理的,固然,每一個階段中各個 HTTP 模塊的處理順序也很重要,若是某個模塊不把請求向下傳遞,後面的模塊是接收不到請求的。並且每一個階段中的模塊也不必定全部都要執行一遍,下面就接着講一下各個階段模塊之間的請求順序。node
以下圖所示,每個模塊處理之間是有序的,那麼這個順序怎麼才能獲得呢?其實很是簡單,在源碼 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 階段,是 11 個階段的第 1 個階段,這個階段剛剛獲取到了請求的頭部,尚未進行任何處理,咱們能夠拿到一些原始的信息。例如,拿到用戶的真實 IP 地址
咱們知道,TCP 鏈接是由一個四元組構成的,在四元組中,包含了源 IP 地址。而在真實的互聯網中,存在很是多的正向代理和反向代理。例如最終的用戶有本身的內網 IP 地址,運營商會分配一個公網 IP,而後訪問某個網站的時候,這個網站可能使用了 CDN 加速一些靜態文件或圖片,若是 CDN 沒有命中,那麼就會回源,回源的時候可能還要通過一個反向代理,例如阿里雲的 SLB,而後纔會到達 Nginx。
咱們要拿到的地址應該是運營商給用戶分配的公網 IP 地址 115.204.33.1,對這個 IP 來進行併發鏈接的控制或者限速,而 Nginx 拿到的倒是 2.2.2.2,那麼怎麼才能拿到真實的用戶 IP 呢?
HTTP 協議中,有兩個頭部能夠用來獲取用戶 IP:
針對這個問題,Nginx 是基於變量來使用。
例如 binary_remote_addr、remote_addr 這樣的變量,其值就是真實的 IP,這樣作鏈接限制也就是 limit_conn 模塊纔有意義,這也說明了,limit_conn 模塊只能在 preaccess 階段,而不能在 postread 階段生效。
默認不會編譯進 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
默認關閉的狀況:
關於如何編譯 Nginx,詳見: https://iziyang.github.io/202...
# 下載 nginx 源碼,在源碼目錄下執行 ./configure --prefix=本身指定的目錄 --with-http_realip_module make make install
#屏蔽默認的 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 階段分爲兩個,一個是 server_rewrite 階段,一個是 rewrite,這兩個階段都涉及到一個 rewrite 模塊,而在 rewrite 模塊中,有一個 return 指令,遇到該指令就不會再向下執行,直接返回響應。
return 指令的語法以下:
Syntax: return code [text]; return code URL; return URL; Default: — Context: server, location, if
返回狀態碼包括如下幾種:
Nginx 自定義
HTTP 1.0 標準
HTTP 1.1 標準
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!"; } }
這兩個問題咱們經過實戰驗證一下。
➜ 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。
那麼假如打開了 location
下 return
指令的註釋呢?
return
指令註釋,reload 配置文件➜ test_nginx curl ziyang.return.com/text find nothing!%
這時候,return
指令獲得了執行。也就是第一個問題,當 server
下包含 error_page
且 location
下有 return
指令的時候,會執行 return
指令。
下面再看一下 server
下的 return
指令和 location
下的 return
指令會執行哪個。
server
下 return
指令的註釋,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>
針對上面兩個問題也就有了答案:
server
下包含 error_page
且 location
下有 return
指令的時候,會執行哪個呢?會執行 location
下的 return
指令。
return
指令同時出如今 server
塊下和同時出如今 location
塊下,它們有合併關係嗎?沒有合併關係,先遇到哪一個 return
指令就先執行哪個。
rewrite
指令用於修改用戶傳入 Nginx 的 URL。來看下 rewrite
的指令規則:
Syntax: rewrite regex replacement [flag]; Default: — Context: server, location, if
它的功能主要有下面幾點:
將 regex
指定的 URL 替換成 replacement
這個新的 URL
replacement
以 http:// 或者 https:// 或者 $schema 開頭,則直接返回 302 重定向替換後的 URL 根據 flag 指定的方式進行處理
replacement
這個 URL 進行新的 location 匹配如今咱們有這樣的一個目錄結構:
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; } }
那麼咱們的問題是:
帶着這三個問題,咱們來實際演示一下。
準備工做
last flag
首先訪問 rewrite.ziyang.com/first/3.txt,結果以下:
➜ ~ curl rewrite.ziyang.com/first/3.txt second!
爲何結果是 second! 呢?應該是 third! 呀,可能有人會有這樣的疑問。實際的匹配步驟以下:
rewrite /first(.*) /second$1 last;
這條指令的存在,last 表示使用新的 URL 進行 location 匹配,所以接下來會去匹配 second/3.txtbreak 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。實際的匹配步驟以下:
rewrite /first(.*) /second$1 last;
這條指令的存在,last 表示使用新的 URL 進行 location 匹配,所以接下來會去匹配 second/3.txt所以,這個過程實際請求的 URL 是 rewrite.ziyang.com/third/3.txt,這樣天然結果就是 test3 了。你還能夠試試訪問 rewrite.ziyang.com/third/2.txt 看看會返回什麼。
redirect 和 permanent flag
配置文件中還有 4 個 location,你能夠分別試着訪問一下,結果是這樣的:
主要是一個指令 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 指令也是在 rewrite 階段生效的,它的語法以下所示:
Syntax: if (condition) { ... } Default: — Context: server, location
它的規則是:
那麼 if 指令的條件表達式包含哪些內容呢?它的規則以下:
將變量與正則表達式作匹配
下面是一些例子:
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; }
當通過 rewrite 模塊,匹配到 URL 以後,就會進入 find_config 階段,開始尋找 URL 對應的 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 時,會有幾個部分都匹配上:
訪問 /Test1/ 時,也會有幾個部分匹配上:
那麼究竟會匹配哪個呢?Nginx 實際上是遵循一套規則的,以下圖所示:
所有的前綴字符串是放置在一棵二叉樹中的,Nginx 會分爲兩部分進行匹配:
下面看下實際的響應是怎麼樣的:
➜ 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/Test3 的匹配過程:
遍歷全部能夠匹配上的前綴字符串,總共有兩個
stop regular expressions match!
下面就來到了 preaccess 階段。咱們常常會遇到一個問題,就是如何限制每一個客戶端的併發鏈接數?如何限制訪問頻率?這些就是在 preaccess 階段處理完成的,顧名思義,preaccess 就是在鏈接以前。先來看下 limit_conn 模塊。
這裏面涉及到的模塊是 ngx_http_limit_conn_module
,它的基本特性以下:
NGX_HTTP_PREACCESS_PHASE
階段http_limit_conn_module
--without-http_limit_conn_module
禁用生效範圍
這裏面有一點須要注意,就是 limit_conn key 的設計,所謂的 key 指的就是對哪一個變量進行限制,一般咱們取的都是用戶的真實 IP。
說完了 limit_conn 的模塊,再來講一下指令語法。
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; } }
在這個配置文件中,作了兩條限制,一個是 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。
在本節開頭咱們就提出了兩個問題:
第一個問題限制併發鏈接數的問題已經解決了,下面來看第二個問題。
這裏面生效的模塊是 ngx_http_limit_req_module
,它的基本特性以下:
NGX_HTTP_PREACCESS_PHASE
階段http_limit_req_module
--without-http_limit_req_module
禁用生效範圍
leaky bucket 叫漏桶算法,其餘用來限制請求速率的還有令牌環算法等,這裏面不展開講。
漏桶算法的原理是,先定義一個桶的大小,全部進入桶內的請求都會以恆定的速率被處理,若是請求太多超出了桶的容量,那麼就會馬上返回錯誤。用一張圖解釋一下。
這張圖裏面,水龍頭在不停地滴水,就像用戶發來的請求,全部的水滴都會以恆定的速率流出去,也就是被處理。漏桶算法對於突發流量有很好的限制做用,會將全部的請求平滑的處理掉。
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_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 配置同時生效時,哪一個優先級高?
nodelay 添加與否,有什麼不一樣?
通過 preaccess 階段對用戶的限流以後,就到了 access 階段。
這裏面涉及到的模塊是 ngx_http_access_module
,它的基本特性以下:
NGX_HTTP_ACCESS_PHASE
階段http_access_module
--without-http_access_module
禁用生效範圍
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 模塊提供了兩條指令 allow
和 deny
,來看幾個例子:
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 模塊是用做用戶認證的,當開啓了這個模塊以後,咱們經過瀏覽器訪問網站時,就會返回一個 401 Unauthorized,固然這個 401 用戶不會看見,瀏覽器會彈出一個對話框要求輸入用戶名和密碼。這個模塊使用的是 RFC2617 中的定義。
默認編譯進 Nginx
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
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; } }
這時候訪問 access.ziyang.com 就會彈出對話框,提示輸入密碼:
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; } }
Syntax: satisfy all | any; Default: satisfy all; Context: http, server, location
satisfy
指令有兩個值一個是 all,一個是 any,這個模塊對 acces 階段的三個模塊都生效:
若是 satisfy
指令的值是 all 的話,就表示必須全部 access 階段的模塊都要執行,都經過了纔會放行;值是 any 的話,表示有任意一個模塊獲得執行便可。
下面有幾個問題能夠加深一下理解:
return 指令屬於 rewrite 階段,在 access 階段以前,所以不會生效。
多個 access 模塊的順序有影響嗎?
ngx_http_auth_request_module, ngx_http_auth_basic_module, ngx_http_access_module,
有影響
輸對密碼,下面能夠訪問到文件嗎?
location /{ satisfy any; auth_basic "test auth_basic"; auth_basic_user_file examples/auth.pass; deny all; }
能夠訪問到,由於 satisfy
的值是 any,所以只要有模塊知足,便可放行。
依然能夠,由於各個模塊執行順序和指令的順序無關。
沒有機會,由於 allow all 是 access 模塊,先於 auth_basic 模塊執行。
講到了這裏,咱們再來回顧一下 Nginx 處理 HTTP 請求的 11 個階段:
如今咱們已經來到了 precontent 階段,這個階段只有 try_files 這一個指令。
Syntax: try_files file ... uri; try_files file ... =code; Default: — Context: server, location
ngx_http_try_files_module
模塊下面咱們實際看一個例子:
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; } }
結果以下:
這兩個結果都與配置文件是一致的。
➜ 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 模塊能夠實時拷貝流量,這對於須要同時訪問多個環境的請求是很是有用的。
模塊:ngx_http_mirror_module
模塊,默認編譯進 Nginx
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
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; } }
下面開始就到了 content 階段,先來看 content 階段的 static 模塊,雖然這是位於 content 階段的最後一個處理模塊,可是這裏先來介紹它。
先來一下 root 和 alias 這兩個指令,這兩個指令都是用來映射文件路徑的。
Syntax: alias path; Default: — Context: location
Syntax: root path; Default: root html; Context: http, server, location, if in location
下面來看一個問題:
如今有一個文件路徑:
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 在映射 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
時,這三個變量的值各爲多少?
爲了解答這個問題,咱們先來解釋三個變量:
爲了驗證這三個變量,在 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
能夠看出來,三個路徑分別是:
還有其餘的一些配置指令,例如:
靜態文件返回時的 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_redirect
和 port_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
請求頭中的域名。
ngx_http_index_module
/
結尾的目錄訪問時,返回 index 文件內容語法:
Syntax: index file ...; Default: index index.html; Context: http, server, location
這個模塊,當咱們訪問以 /
結尾的目錄時,會去找 root 或 alias 指令的文件夾下的 index.html,若是有這個文件,就會把文件內容返回,也能夠指定其餘文件。
ngx_http_autoindex_module
,默認編譯進 Nginx,使用 --without-http_autoindex_module
取消/
結尾時,嘗試以 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 這個文件。
index b.html
指令註釋。因爲 html 文件夾下並不存在 b.html 這個文件,因此請求會走到 autoindex 模塊,顯示目錄:
後面的文件大小顯示格式就是由 autoindex_exact_size on;
這條指令決定的。
下面介紹一個能夠提高小文件性能的模塊,這個模塊是由阿里巴巴開發的,在淘寶網中有普遍應用。
指令:
#在 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; } }
下面終於來到了 11 個階段的最後一個階段,記錄請求訪問日誌的 log 模塊。
ngx_http_log_module
,沒法禁用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
日誌緩存
全部待寫入磁盤的日誌大小超出緩存大小;
達到 flush 指定的過時時間;
worker 進程執行 reopen 命令,或者正在關閉。
日誌壓縮
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
日誌模塊沒有實戰。
到了這裏,咱們已經將 Nginx 處理 HTTP 請求的 11 個階段所有梳理了一遍,每一個階段基本都有對應的模塊。相信對於這樣一個全流程的解析,你們都可以看懂 Nginx 的配置了,在此之上,還可以按照需求靈活配置出本身想要的配置,這樣就真正的掌握了 11 個階段。
最後,歡迎你們關注個人我的博客:iziyang.github.io