整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          PHP-GD庫中文亂碼問題

          PHP-GD庫中文亂碼問題

          、原因

          1.編碼原因。在用GD庫輸出中文字符串時,要使用imagettftext()函數(shù)。但如果PHP編譯時啟用–enable-gd-jis-conv選項的話,那么非ASCII字符(例如漢字、拼音、希臘文和箭頭) 會被當(dāng)成EUC-JP編碼 (phpinfo中美其名曰“支持JIS編碼的字體”), 從而導(dǎo)致亂碼(由于西文字體沒有假名或漢字,一般表現(xiàn)為全部是方框)。

          2.沒有選擇正確的TTF字體文件。使用gd庫時如果要輸出中文字符,需要選擇正確的TTF字體文件。




          二、解決方法

          1.建議整站使用UTF8編碼,如果你已使用的是GB2312或GBK編碼,請使用iconv或自定義的gb2312與utf8轉(zhuǎn)換函數(shù)進(jìn)行字符編碼轉(zhuǎn)換。

          2.如果你是剛進(jìn)行php環(huán)境搭建,建議將Apache配置文件中的默認(rèn)字符集修改為UTF8,即AddDefaultCharset UTF8。

          3.如果上述方法還不行,請檢查你在編譯gd庫時是否添加了“–enable-gd-jis-conv”選項,此選項是為了讓gd庫支持日文編碼的字庫,請取消此選項并重新編譯。此方法本人已驗證過,Linux系統(tǒng)下安裝配置nginx+php環(huán)境,搭建Zabbix服務(wù)時碰到,添加了“–enable-gd-jis-conv”選項,Zabbix使用中文界面,更換字體文件前后,查看主機(jī)監(jiān)控圖標(biāo)信息都會出現(xiàn)亂碼。

          4.使用php gd庫產(chǎn)生中文亂碼的另一個原因是沒有選擇正確的TTF字體,你需要選擇支持中文的字體,常用的中文字體文件是simsun.ttc和simhei.ttf。

          按照上述方法,基本上使用php gd庫產(chǎn)生中文亂碼時都能夠解決。只要仔細(xì)排查其實(shí)gd庫的中文亂碼解決起來非常方便。



          參考:

          https://www.php.cn/php-ask-456070.html

          https://blog.csdn.net/lizhenjian521/article/details/51038547

          ginx的并發(fā)能力在同類型網(wǎng)頁服務(wù)器中的表現(xiàn),相對而言是比較好的,因此受到了很多企業(yè)的青睞,我國使用Nginx網(wǎng)站的知名用戶包括騰訊、淘寶、百度、京東、新浪、網(wǎng)易等等。Nginx是網(wǎng)頁服務(wù)器運(yùn)維人員必備技能之一,下面為大家整理了一些比較常見的Nginx相關(guān)面試題,僅供參考:

          1、請解釋一下什么是Nginx?

          Nginx是一個web服務(wù)器和方向代理服務(wù)器,用于HTTP、HTTPS、SMTP、POP3和IMAP協(xié)議。

          2、請列舉Nginx的一些特性。

          Nginx服務(wù)器的特性包括:

          反向代理/L7負(fù)載均衡器

          嵌入式Perl解釋器

          動態(tài)二進(jìn)制升級

          可用于重新編寫URL,具有非常好的PCRE支持

          3、請列舉Nginx和Apache 之間的不同點(diǎn)。

          4、請解釋Nginx如何處理HTTP請求。

          Nginx使用反應(yīng)器模式。主事件循環(huán)等待操作系統(tǒng)發(fā)出準(zhǔn)備事件的信號,這樣數(shù)據(jù)就可以從套接字讀取,在該實(shí)例中讀取到緩沖區(qū)并進(jìn)行處理。單個線程可以提供數(shù)萬個并發(fā)連接。

          5、在Nginx中,如何使用未定義的服務(wù)器名稱來阻止處理請求?

          只需將請求刪除的服務(wù)器就可以定義為:

          Server {

          listen 80;

          server_name “ “ ;

          return 444;

          }

          這里,服務(wù)器名被保留為一個空字符串,它將在沒有“主機(jī)”頭字段的情況下匹配請求,而一個特殊的Nginx的非標(biāo)準(zhǔn)代碼444被返回,從而終止連接。

          6、 使用“反向代理服務(wù)器”的優(yōu)點(diǎn)是什么?

          反向代理服務(wù)器可以隱藏源服務(wù)器的存在和特征。它充當(dāng)互聯(lián)網(wǎng)云和web服務(wù)器之間的中間層。這對于安全方面來說是很好的,特別是當(dāng)您使用web托管服務(wù)時。

          7、請列舉Nginx服務(wù)器的最佳用途。

          Nginx服務(wù)器的最佳用法是在網(wǎng)絡(luò)上部署動態(tài)HTTP內(nèi)容,使用SCGI、WSGI應(yīng)用程序服務(wù)器、用于腳本的FastCGI處理程序。它還可以作為負(fù)載均衡器。

          8、請解釋Nginx服務(wù)器上的Master和Worker進(jìn)程分別是什么?

          Master進(jìn)程:讀取及評估配置和維持

          Worker進(jìn)程:處理請求

          9、請解釋你如何通過不同于80的端口開啟Nginx?

          為了通過一個不同的端口開啟Nginx,你必須進(jìn)入/etc/Nginx/sites-enabled/,如果這是默認(rèn)文件,那么你必須打開名為“default”的文件。編輯文件,并放置在你想要的端口:

          Like server { listen 81; }

          10、請解釋是否有可能將Nginx的錯誤替換為502錯誤、503?

          502=錯誤網(wǎng)關(guān)

          503=服務(wù)器超載

          有可能,但是您可以確保fastcgi_intercept_errors被設(shè)置為ON,并使用錯誤頁面指令。

          Location / {

          fastcgi_pass 127.0.01:9001;

          fastcgi_intercept_errors on;

          error_page 502=503/error_page.html;

          #…

          }

          11、在Nginx中,解釋如何在URL中保留雙斜線?

          要在URL中保留雙斜線,就必須使用merge_slashes_off;

          語法:merge_slashes [on/off]

          默認(rèn)值: merge_slashes on

          環(huán)境: http,server

          12、請解釋ngx_http_upstream_module的作用是什么?

          ngx_http_upstream_module用于定義可通過fastcgi傳遞、proxy傳遞、uwsgi傳遞、memcached傳遞和scgi傳遞指令來引用的服務(wù)器組。

          13、請解釋什么是C10K問題?

          C10K問題是指無法同時處理大量客戶端(10,000)的網(wǎng)絡(luò)套接字。

          14、請陳述stub_status和sub_filter指令的作用是什么?

          Stub_status指令:該指令用于了解Nginx當(dāng)前狀態(tài)的當(dāng)前狀態(tài),如當(dāng)前的活動連接,接受和處理當(dāng)前讀/寫/等待連接的總數(shù)

          Sub_filter指令:它用于搜索和替換響應(yīng)中的內(nèi)容,并快速修復(fù)陳舊的數(shù)據(jù)

          15、解釋Nginx是否支持將請求壓縮到上游?

          您可以使用Nginx模塊gunzip將請求壓縮到上游。gunzip模塊是一個過濾器,它可以對不支持“gzip”編碼方法的客戶機(jī)或服務(wù)器使用“內(nèi)容編碼:gzip”來解壓縮響應(yīng)。

          16、解釋如何在Nginx中獲得當(dāng)前的時間?

          要獲得Nginx的當(dāng)前時間,必須使用SSI模塊、$date_gmt和$date_local的變量。

          Proxy_set_header THE-TIME $date_gmt;

          17、用Nginx服務(wù)器解釋-s的目的是什么?

          用于運(yùn)行Nginx -s參數(shù)的可執(zhí)行文件。

          18、解釋如何在Nginx服務(wù)器上添加模塊?

          在編譯過程中,必須選擇Nginx模塊,因為Nginx不支持模塊的運(yùn)行時間選擇。

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

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

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

          Nginx 處理 HTTP 請求的 11 個階段

          下面介紹一下詳細(xì)的 11 個階段,每個階段都可能對應(yīng)著一個甚至多個 HTTP 模塊,通過這樣一個模塊對比,我們也能夠很好的理解這些模塊具體是怎么樣發(fā)揮作用的。

          1. POST_READ:在 read 完請求的頭部之后,在沒有對頭部做任何處理之前,想要獲取到一些原始的值,就應(yīng)該在這個階段進(jìn)行處理。這里面會涉及到一個 realip 模塊。
          2. SERVER_REWRITE:和下面的 REWRITE 階段一樣,都只有一個模塊叫 rewrite 模塊,一般沒有第三方模塊會處理這個階段。
          3. FIND_CONFIG:做 location 的匹配,暫時沒有模塊會用到。
          4. REWRITE:對 URL 做一些處理。
          5. POST_WRITE:處于 REWRITE 之后,也是暫時沒有模塊會在這個階段出現(xiàn)。

          接下來是確認(rèn)用戶訪問權(quán)限的三個模塊:

          1. PREACCESS:是在 ACCESS 之前要做一些工作,例如并發(fā)連接和 QPS 需要進(jìn)行限制,涉及到兩個模塊:limt_conn 和 limit_req
          2. ACCESS:核心要解決的是用戶能不能訪問的問題,例如 auth_basic 是用戶名和密碼,access 是用戶訪問 IP,auth_request 根據(jù)第三方服務(wù)返回是否可以去訪問。
          3. POST_ACCESS:是在 ACCESS 之后會做一些事情,同樣暫時沒有模塊會用到。

          最后的三個階段處理響應(yīng)和日志:

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

          以上的這些階段都是嚴(yán)格按照順序進(jìn)行處理的,當(dāng)然,每個階段中各個 HTTP 模塊的處理順序也很重要,如果某個模塊不把請求向下傳遞,后面的模塊是接收不到請求的。而且每個階段中的模塊也不一定所有都要執(zhí)行一遍,下面就接著講一下各個階段模塊之間的請求順序。

          11 個階段的順序處理

          如下圖所示,每一個模塊處理之間是有序的,那么這個順序怎么才能得到呢?其實(shí)非常簡單,在源碼 ngx_module.c 中,有一個數(shù)組 ngx_module_name,其中包含了在編譯 Nginx 的時候的 with 指令所包含的所有模塊,它們之間的順序非常關(guān)鍵,在數(shù)組中順序是相反的。

          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 的框架部分去執(zhí)行處理的,第三方模塊沒有機(jī)會在這里得到處理。

          在依次向下執(zhí)行的過程中,也可能不按照這樣的順序。例如,在 access 階段中,有一個指令叫 satisfy,它可以指示當(dāng)有一個滿足的時候就直接跳到下一個階段進(jìn)行處理,例如當(dāng) access 滿足了,就直接跳到 try_files 模塊進(jìn)行處理,而不會再執(zhí)行 auth_basic、auth_request 模塊。

          在 content 階段中,當(dāng) index 模塊執(zhí)行了,就不會再執(zhí)行 auto_index 模塊,而是直接跳到 log 模塊。

          整個 11 個階段所涉及到的模塊和先后順序如下圖所示:

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

          postread 階段

          postread 階段,是 11 個階段的第 1 個階段,這個階段剛剛獲取到了請求的頭部,還沒有進(jìn)行任何處理,我們可以拿到一些原始的信息。例如,拿到用戶的真實(shí) IP 地址

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

          我們知道,TCP 連接是由一個四元組構(gòu)成的,在四元組中,包含了源 IP 地址。而在真實(shí)的互聯(lián)網(wǎng)中,存在非常多的正向代理和反向代理。例如最終的用戶有自己的內(nèi)網(wǎng) IP 地址,運(yùn)營商會分配一個公網(wǎng) IP,然后訪問某個網(wǎng)站的時候,這個網(wǎng)站可能使用了 CDN 加速一些靜態(tài)文件或圖片,如果 CDN 沒有命中,那么就會回源,回源的時候可能還要經(jīng)過一個反向代理,例如阿里云的 SLB,然后才會到達(dá) Nginx。

          我們要拿到的地址應(yīng)該是運(yùn)營商給用戶分配的公網(wǎng) IP 地址 115.204.33.1,對這個 IP 來進(jìn)行并發(fā)連接的控制或者限速,而 Nginx 拿到的卻是 2.2.2.2,那么怎么才能拿到真實(shí)的用戶 IP 呢?

          HTTP 協(xié)議中,有兩個頭部可以用來獲取用戶 IP:

          • X-Forwardex-For 是用來傳遞 IP 的,這個頭部會把經(jīng)過的節(jié)點(diǎn) IP 都記錄下來
          • X-Real-IP:可以記錄用戶真實(shí)的 IP 地址,只能有一個

          拿到真實(shí)用戶 IP 后如何使用?

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

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

          realip 模塊

          • 默認(rèn)不會編譯進(jìn) Nginx需要通過 --with-http_realip_module 啟用功能
          • 變量:如果還想要使用原來的 TCP 連接中的地址和端口,需要通過這兩個變量保存realip_remote_addrrealip_remote_port
          • 功能修改客戶端地址
          • 指令set_real_ip_from指定可信的地址,只有從該地址建立的連接,獲取的 realip 才是可信的real_ip_header指定從哪個頭部取真實(shí)的 IP 地址,默認(rèn)從 X-Real-IP 中取,如果設(shè)置從 X-Forwarded-For 中取,會先從最后一個 IP 開始取real_ip_recursive環(huán)回地址,默認(rèn)關(guān)閉,打開的時候,如果 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
          

          實(shí)戰(zhàn)

          上面關(guān)于 real_ip_recursive 指令可能不太容易理解,我們來實(shí)戰(zhàn)練習(xí)一下,先來看 real_ip_recursive 默認(rèn)關(guān)閉的情況:

          • 重新編譯一個帶有 realip 模塊的 nginx

          關(guān)于如何編譯 Nginx,詳見:https://iziyang.github.io/2020/03/10/1-nginx/

          # 下載 nginx 源碼,在源碼目錄下執(zhí)行
          ./configure --prefix=自己指定的目錄 --with-http_realip_module
          make
          make install
          
          • 然后去上一步中自己指定的 Nginx 安裝目錄
          #屏蔽默認(rèn)的 nginx.conf 文件的 server 塊內(nèi)容,并添加一行
          include /Users/mtdp/myproject/nginx/test_nginx/conf/example/*.conf;
          
          # 在 example 目錄下建立 realip.conf,set_real_ip_from 可以設(shè)置為自己的本機(jī) 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";
              }
          }
          

          在上面的配置文件中,我設(shè)置了可信代理地址為本機(jī)地址,real_ip_recursive 為默認(rèn)的 off,real_ip_header 設(shè)為從 X-Forwarded-For 中取。

          • 重載配置文件
          ./sbin/nginx -s reload
          
          • 測試響應(yīng)結(jié)果
          ?  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";
              }
          }
          
          • 測試響應(yīng)結(jié)果
          ?  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 設(shè)置的可信地址。

          那么有人可能就會問了,那直接用 X-Real-IP 來選取真實(shí)的 IP 地址不就好了。這是可以的,但是 X-Real-IP 是 Nginx 獨(dú)有的,不是 RFC 規(guī)范,如果客戶端與服務(wù)器之間還有其他非 Nginx 軟件實(shí)現(xiàn)的代理,就會造成取不到 X-Real-IP 頭部,所以這個要根據(jù)實(shí)際情況來定。

          rewrite 階段的 rewrite 模塊

          下面來看一下 rewrite 模塊。

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

          return 指令

          return 指令的語法如下:

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

          返回狀態(tài)碼包括以下幾種:

          • Nginx 自定義444:立刻關(guān)閉連接,用戶收不到響應(yīng)
          • HTTP 1.0 標(biāo)準(zhǔn)301:永久重定向302:臨時重定向,禁止被緩存
          • HTTP 1.1 標(biāo)準(zhǔn)303:臨時重定向,允許改變方法,禁止被緩存307:臨時重定向,不允許改變方法,禁止被緩存308:永久重定向,不允許改變方法

          return 指令與 error_page

          error_page 的作用大家肯定經(jīng)常見到。當(dāng)訪問一個網(wǎng)站出現(xiàn) 404 的時候,一般不會直接出現(xiàn)一個 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;
          

          那么現(xiàn)在就會有兩個問題,大家看下下面這個配置文件:

          server {
              server_name ziyang.return.com;
              listen 80;
              root html/;
              error_page 404 /403.html;
              #return 405;
              location / {
                  #return 404 "find nothing!";
              }
          }
          
          1. 當(dāng) server 下包含 error_page 且 location 下有 return 指令的時候,會執(zhí)行哪一個呢?
          2. return 指令同時出現(xiàn)在 server 塊下和同時出現(xiàn)在 location 塊下,它們有合并關(guān)系嗎?

          這兩個問題我們通過實(shí)戰(zhàn)驗證一下。

          實(shí)戰(zhàn)

          • 將上面的配置添加到配置文件 return.conf
          • 在本機(jī)的 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 生效了,返回的響應(yīng)是 403。

          那么假如打開了 location 下 return 指令的注釋呢?

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

          這時候,return 指令得到了執(zhí)行。也就是第一個問題,當(dāng) server 下包含 error_page 且 location 下有 return 指令的時候,會執(zhí)行 return 指令。

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

          • 打開 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>
          

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

          1. 當(dāng) server 下包含 error_page 且 location 下有 return 指令的時候,會執(zhí)行哪一個呢?會執(zhí)行 location 下的 return 指令。
          2. return 指令同時出現(xiàn)在 server 塊下和同時出現(xiàn)在 location 塊下,它們有合并關(guān)系嗎?沒有合并關(guān)系,先遇到哪個 return 指令就先執(zhí)行哪一個。

          rewrite 指令

          rewrite 指令用于修改用戶傳入 Nginx 的 URL。來看下 rewrite 的指令規(guī)則:

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

          它的功能主要有下面幾點(diǎn):

          • 將 regex 指定的 URL 替換成 replacement 這個新的 URL可以使用正則表達(dá)式及變量提取
          • 當(dāng) replacement 以 http:// 或者 https:// 或者 $schema 開頭,則直接返回 302 重定向
          • 替換后的 URL 根據(jù) flag 指定的方式進(jìn)行處理last:用 replacement 這個 URL 進(jìn)行新的 location 匹配break:break 指令停止當(dāng)前腳本指令的執(zhí)行,等價于獨(dú)立的 break 指令redirect:返回 302 重定向permanent:返回 301 重定向

          指令示例

          現(xiàn)在我們有這樣的一個目錄結(jié)構(gòu):

          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 指令的順序關(guān)系?
          2. 訪問 /first/3.txt,/second/3.txt,/third/3.txt 分別返回的是什么?
          3. 如果不攜帶 flag 會怎么樣?

          帶著這三個問題,我們來實(shí)際演示一下。

          實(shí)戰(zhàn)

          準(zhǔn)備工作

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

          last flag

          首先訪問 rewrite.ziyang.com/first/3.txt,結(jié)果如下:

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

          為什么結(jié)果是 second! 呢?應(yīng)該是 third! 呀,可能有人會有這樣的疑問。實(shí)際的匹配步驟如下:

          • curl rewrite.ziyang.com/first/3.txt
          • 由于 rewrite /first(.*) /second last; 這條指令的存在,last 表示使用新的 URL 進(jìn)行 location 匹配,因此接下來會去匹配 second/3.txt
          • 匹配到 /second 塊之后,會依次執(zhí)行指令,最后返回 200
          • 注意,location 塊中雖然也改寫了 URL,但是并不會去繼續(xù)匹配,因為后面沒有指定 flag。

          break flag

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

          繼續(xù)訪問 rewrite.ziyang.com/first/3.txt,結(jié)果如下:

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

          這時候返回的是 3.txt 文件的內(nèi)容 test3。實(shí)際的匹配步驟如下:

          • curl rewrite.ziyang.com/first/3.txt
          • 由于 rewrite /first(.*) /second last; 這條指令的存在,last 表示使用新的 URL 進(jìn)行 location 匹配,因此接下來會去匹配 second/3.txt
          • 匹配到 /second 塊之后,由于 break flag 的存在,會繼續(xù)匹配 rewrite 過后的 URL
          • 匹配 /third location

          因此,這個過程實(shí)際請求的 URL 是 rewrite.ziyang.com/third/3.txt,這樣自然結(jié)果就是 test3 了。你還可以試試訪問 rewrite.ziyang.com/third/2.txt 看看會返回什么。

          redirect 和 permanent flag

          配置文件中還有 4 個 location,你可以分別試著訪問一下,結(jié)果是這樣的:

          • 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
          

          它的規(guī)則是:

          • 條件 condition 為真,則執(zhí)行大括號內(nèi)的指令;同時還遵循值指令的繼承規(guī)則(詳見我之前的文章 Nginx 的配置指令)

          那么 if 指令的條件表達(dá)式包含哪些內(nèi)容呢?它的規(guī)則如下:

          1. 檢查變量為空或者值是否為 0
          2. 將變量與字符串做匹配,使用=或 !=
          3. 將變量與正則表達(dá)式做匹配大小寫敏感,~ 或者 !~大小寫不敏感,~* 或者 !~*
          4. 檢查文件是否存在,使用 -f 或者 !-f
          5. 檢查目錄是否存在,使用 -d 或者 !-d
          6. 檢查文件、目錄、軟鏈接是否存在,使用 -e 或者 !-e
          7. 檢查是否為可執(zhí)行文件,使用 -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 模塊中自定義,也可以進(jìn)行匹配
              limit_rate 10k; 
          } 
          if ($invalid_referer) { 
              return 403; 
          }
          

          find_config 階段

          當(dāng)經(jīng)過 rewrite 模塊,匹配到 URL 之后,就會進(jìn)入 find_config 階段,開始尋找 URL 對應(yīng)的 location 配置。

          location 指令

          指令語法

          還是老規(guī)矩,咱們先來看一下 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 中有兩個重復(fù)的 /,那么會合并為一個,這個指令默認(rèn)是打開的,只有當(dāng)對 URL 進(jìn)行 base64 之類的編碼時才需要關(guān)閉。

          匹配規(guī)則

          location 的匹配規(guī)則是僅匹配 URI,忽略參數(shù),有下面三種大的情況:

          • 前綴字符串常規(guī)匹配=:精確匹配^~:匹配上后則不再進(jìn)行正則表達(dá)式匹配
          • 正則表達(dá)式~:大小寫敏感的正則匹配~*:大小寫不敏感
          • 用戶內(nèi)部跳轉(zhuǎn)的命名 location@

          對于這些規(guī)則剛看上去肯定是很懵的,完全不知道在說什么,下面來實(shí)戰(zhàn)看幾個例子。

          實(shí)戰(zhàn)

          先看一下 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 會分別返回什么內(nèi)容呢?

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

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

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

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

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

          那么究竟會匹配哪一個呢?Nginx 其實(shí)是遵循一套規(guī)則的,如下圖所示:

          全部的前綴字符串是放置在一棵二叉樹中的,Nginx 會分為兩部分進(jìn)行匹配:

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

          下面看下實(shí)際的響應(yīng)是怎么樣的:

          ?  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/

          這里面重點(diǎn)解釋一下 /Test1/Test3 的匹配過程:

          1. 遍歷所有可以匹配上的前綴字符串,總共有兩個^~ /Test1//Test1
          2. 選取最長的前綴字符串 /Test1/,由于前面有 ^~ 禁止正則表達(dá)式匹配,因此直接使用 location ^~ /Test1/ 的規(guī)則
          3. 返回 stop regular expressions match!

          preaccess 階段

          下面就來到了 preaccess 階段。我們經(jīng)常會遇到一個問題,就是如何限制每個客戶端的并發(fā)連接數(shù)?如何限制訪問頻率?這些就是在 preaccess 階段處理完成的,顧名思義,preaccess 就是在連接之前。先來看下 limit_conn 模塊。

          limit_conn 模塊

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

          • 生效階段:NGX_HTTP_PREACCESS_PHASE 階段
          • 模塊:http_limit_conn_module
          • 默認(rèn)編譯進(jìn) Nginx,通過 --without-http_limit_conn_module 禁用
          • 生效范圍全部 worker 進(jìn)程(基于共享內(nèi)存)進(jìn)入 preaccess 階段前不生效限制的有效性取決于 key 的設(shè)計:依賴 postread 階段的 realip 模塊取到真實(shí) IP

          這里面有一點(diǎn)需要注意,就是 limit_conn key 的設(shè)計,所謂的 key 指的就是對哪個變量進(jìn)行限制,通常我們?nèi)〉亩际怯脩舻恼鎸?shí) IP。

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

          指令語法

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

          實(shí)戰(zhàn)

          下面又到了實(shí)戰(zhàn)的環(huán)節(jié)了,通過一個實(shí)際的例子來看一下以上的幾個指令是怎么起作用的。

          老規(guī)矩,先上配置文件:

          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 為本機(jī) IP

          在這個配置文件中,做了兩條限制,一個是 limit_rate 限制為 50 個字節(jié),并發(fā)連接數(shù) limit_conn 限制為 1。

          ?  test_nginx curl limit.ziyang.com
          

          這時候訪問 limit.ziyang.com 這個站點(diǎn),會發(fā)現(xiàn)速度非常慢,因為每秒鐘只有 50 個字節(jié)。

          如果再同時訪問這個站點(diǎn)的話,則會返回 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 模塊

          在本節(jié)開頭我們就提出了兩個問題:

          • 如何限制每個客戶端的并發(fā)連接數(shù)?
          • 如何限制訪問頻率?

          第一個問題限制并發(fā)連接數(shù)的問題已經(jīng)解決了,下面來看第二個問題。

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

          • 生效階段:NGX_HTTP_PREACCESS_PHASE 階段
          • 模塊:http_limit_req_module
          • 默認(rèn)編譯進(jìn) Nginx,通過 --without-http_limit_req_module 禁用
          • 生效算法:leaky bucket 算法
          • 生效范圍全部 worker 進(jìn)程(基于共享內(nèi)存)進(jìn)入 preaccess 階段前不生效

          leaky bucket 算法

          leaky bucket 叫漏桶算法,其他用來限制請求速率的還有令牌環(huán)算法等,這里面不展開講。

          漏桶算法的原理是,先定義一個桶的大小,所有進(jìn)入桶內(nèi)的請求都會以恒定的速率被處理,如果請求太多超出了桶的容量,那么就會立刻返回錯誤。用一張圖解釋一下。

          這張圖里面,水龍頭在不停地滴水,就像用戶發(fā)來的請求,所有的水滴都會以恒定的速率流出去,也就是被處理。漏桶算法對于突發(fā)流量有很好的限制作用,會將所有的請求平滑的處理掉。

          指令語法

          • 定義共享內(nèi)存(包括大小),以及 key 關(guān)鍵字和限制速率
          Syntax: limit_req_zone key zone=name:size rate=rate ;
          Default: —
          Context: http
          

          rate 單位為 r/s 或者 r/m(每分鐘或者每秒處理多少個請求)

          • 限制并發(fā)連接數(shù)
          Syntax: limit_req zone=name [burst=number] [nodelay];
          Default: —
          Context: http, server, location
          

          burst 默認(rèn)為 0nodelay,如果設(shè)置了這個參數(shù),那么對于漏桶中的請求也會立刻返回錯誤

          • 限制發(fā)生時的日志級別
          Syntax: limit_req_log_level info | notice | warn | error;
          Default: limit_req_log_level error; 
          Context: http, server, location
          
          • 限制發(fā)生時向客戶端返回的錯誤碼
          Syntax: limit_req_status code;
          Default: limit_req_status 503; 
          Context: http, server, location
          

          實(shí)戰(zhàn)

          在實(shí)際驗證之前呢,需要注意兩個問題:

          • limit_req 與 limit_conn 配置同時生效時,哪個優(yōu)先級高?
          • nodelay 添加與否,有什么不同?

          添加配置文件,這個配置文件與上一節(jié)的配置文件其實(shí)是相同的只不過需要注釋一下:

          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;
              }
          }
          

          結(jié)論:在 limit_req zone=one 指令下,超出每分鐘處理的請求數(shù)后就會立刻返回 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 參數(shù)時,會立刻返回錯誤,而加上之后,不會返回錯誤,而是等待請求限制解除,直到可以處理請求時再返回。

          再來看一下 nodelay 參數(shù):

          limit_req zone=one burst=3 nodelay;
          

          添加了 nodelay 之后,請求在沒有達(dá)到 burst 限制之前都可以立刻被處理并返回,超出了 burst 限制之后,才會返回 503。

          現(xiàn)在可以回答一下剛開始提出的兩個問題:

          • limit_req 與 limit_conn 配置同時生效時,哪個優(yōu)先級高?limit_req 在 limit_conn 處理之前,因此是 limit_req 會生效
          • nodelay 添加與否,有什么不同?不添加 nodelay,請求會等待,直到能夠處理請求;添加 nodelay,在不超出 burst 的限制的情況下會立刻處理并返回,超出限制則會返回 503。

          access 階段

          經(jīng)過 preaccess 階段對用戶的限流之后,就到了 access 階段。

          access 模塊

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

          • 生效階段:NGX_HTTP_ACCESS_PHASE 階段
          • 模塊:http_access_module
          • 默認(rèn)編譯進(jìn) Nginx,通過 --without-http_access_module 禁用
          • 生效范圍進(jìn)入 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 模塊提供了兩條指令 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; 
          }
          

          對于用戶訪問來說,這些指令是順序執(zhí)行的,當(dāng)滿足了一條之后,就不會再向下執(zhí)行。這個模塊比較簡單,我們這里不做實(shí)戰(zhàn)演練了。

          auth_basic 模塊

          auth_basic 模塊是用作用戶認(rèn)證的,當(dāng)開啟了這個模塊之后,我們通過瀏覽器訪問網(wǎng)站時,就會返回一個 401 Unauthorized,當(dāng)然這個 401 用戶不會看見,瀏覽器會彈出一個對話框要求輸入用戶名和密碼。這個模塊使用的是 RFC2617 中的定義。

          指令語法

          • 基于 HTTP Basic Authutication 協(xié)議進(jìn)行用戶密碼的認(rèn)證
          • 默認(rèn)編譯進(jìn) Nginx--without-http_auth_basic_moduledisable 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
          

          實(shí)戰(zhàn)

          • 在 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 模塊

          • 功能:向上游的服務(wù)轉(zhuǎn)發(fā)請求,若上游服務(wù)返回的響應(yīng)碼是 2xx,則繼續(xù)執(zhí)行,若上游服務(wù)返回的響應(yīng)碼是 2xx,則繼續(xù)執(zhí)行,若上游服務(wù)返回的是 401 或者 403,則將響應(yīng)返回給客戶端
          • 原理:收到請求后,生成子請求,通過反向代理技術(shù)把請求傳遞給上游服務(wù)
          • 默認(rèn)未編譯進(jìn) Nginx,需要通過 --with-http_auth_request_module 編譯進(jìn)去

          指令語法

          Syntax: auth_request uri | off;
          Default: auth_request off; 
          Context: http, server, location
          
          Syntax: auth_request_set $variable value;
          Default: —
          Context: http, server, location
          

          實(shí)戰(zhàn)

          • 在上一個配置文件中添加以下內(nèi)容
          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;
              }
          }
          
          • 這個配置文件中,/ 路徑下會將請求轉(zhuǎn)發(fā)到另外一個服務(wù)中去,可以用 nginx 再搭建一個服務(wù)
          • 如果這個服務(wù)返回 2xx,那么鑒權(quán)成功,如果返回 401 或 403 則鑒權(quán)失敗

          限制所有 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 階段的模塊都要執(zhí)行,都通過了才會放行;值是 any 的話,表示有任意一個模塊得到執(zhí)行即可。

          下面有幾個問題可以加深一下理解:

          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 之前呢?依然可以,因為各個模塊執(zhí)行順序和指令的順序無關(guān)。
          5. 如果改為 allow all,有機(jī)會輸入密碼嗎?沒有機(jī)會,因為 allow all 是 access 模塊,先于 auth_basic 模塊執(zhí)行。

          precontent 階段

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

          現(xiàn)在我們已經(jīng)來到了 precontent 階段,這個階段只有 try_files 這一個指令。

          try_files 模塊

          指令語法

          Syntax: try_files file ... uri;
                  try_files file ...=code;
          Default: —
          Context: server, location
          
          • 模塊:ngx_http_try_files_module 模塊
          • 依次試圖訪問多個 URL 對應(yīng)的文件(由 root 或者 alias 指令指定),當(dāng)文件存在時,直接返回文件內(nèi)容,如果所有文件都不存在,則按照最后一個 URL 結(jié)果或者 code 返回

          實(shí)戰(zhàn)

          下面我們實(shí)際看一個例子:

          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;
              }
          }
          

          結(jié)果如下:

          • 訪問 /first 實(shí)際上到了 lasturl,然后返回 200
          • 訪問 /second 則返回了 404

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

          ?  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 模塊可以實(shí)時拷貝流量,這對于需要同時訪問多個環(huán)境的請求是非常有用的。

          指令語法

          • 模塊:ngx_http_mirror_module 模塊,默認(rèn)編譯進(jìn) Nginx通過 --without-http_mirror_module 移除模塊
          • 功能:處理請求時,生成子請求訪問其他服務(wù),對子請求的返回值不做處理
          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
          

          實(shí)戰(zhàn)

          • 配置文件如下所示,需要再開啟另外一個 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 映射為文件路徑,以返回靜態(tài)文件內(nèi)容
          • 差別:root 會將完整 URL 映射進(jìn)文件路徑中,alias 只會將 location 后的 URL 映射到文件路徑

          實(shí)戰(zhàn)

          下面來看一個問題:

          現(xiàn)在有一個文件路徑:

          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 會得到什么響應(yīng)呢?

          /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>
          

          訪問這四個路徑分別得到的結(jié)果是:

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

          這是為什么呢?是因為,root 在映射 URL 時,會把 location 中的路徑也加進(jìn)去,也就是:

          • static.ziyang.com/root/ 實(shí)際訪問的是 html/root/
          • static.ziyang.com/root/1.txt 實(shí)際是 html/first/1.txt/root/1.txt
          • static.ziyang.com/alias/ 實(shí)際上是正確訪問到了 html 文件夾,由于后面有 / 的存在,因此實(shí)際訪問的是 html/index.html
          • static.ziyang.com/alias/1.txt 實(shí)際訪問的是 html/first/1.txt,文件存在

          三個相關(guān)變量

          還是上面的配置文件:

          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 中的軟鏈接替換成真實(shí)路徑

          為了驗證這三個變量,在 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

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

          靜態(tài)文件返回時的 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
          

          在生產(chǎn)環(huán)境中,經(jīng)常可能會有找不到文件的情況,錯誤日志中就會打印出來:

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

          如果不想記錄日志,可以關(guān)掉。

          重定向跳轉(zhuǎn)的域名

          現(xiàn)在有另外一個問題,當(dāng)我們訪問目錄時最后沒有帶 /,static 模塊會返回 301 重定向,那么這個規(guī)則是怎么定義的呢,看下面三個指令:

          # 該指令決定重定向時的域名,可以決定返回哪個域名
          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
          # 該指令決定是否填域名,默認(rèn)是打開的,也就是返回絕對路徑
          Syntax: absolute_redirect on | off;
          Default: absolute_redirect on; 
          Context: http, server, location
          

          這三個指令的實(shí)際用法來實(shí)戰(zhàn)演示一下,先來看配置文件:

          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 默認(rèn)是打開的,我們把它關(guān)閉了,看下是怎么返回的:

          ?  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 打開(默認(rèn)是打開的,因此注釋掉就行了),看下返回什么:

          • 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 這兩個指令打開了,如果關(guān)閉掉這兩個指令,看下返回什么:

          • 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
          

          這兩個指令都設(shè)置為 off 之后,會發(fā)現(xiàn)返回的不再是主域名加端口號,而是我們請求的域名和端口號,如果在請求頭中加上 Host,那么就會用 Host 請求頭中的域名。

          index 模塊

          • 模塊:ngx_http_index_module
          • 功能:指定 / 結(jié)尾的目錄訪問時,返回 index 文件內(nèi)容
          • 語法:Syntax: index file ...; Default: index index.html; Context: http, server, location
          • 先于 autoindex 模塊執(zhí)行

          這個模塊,當(dāng)我們訪問以 / 結(jié)尾的目錄時,會去找 root 或 alias 指令的文件夾下的 index.html,如果有這個文件,就會把文件內(nèi)容返回,也可以指定其他文件。

          autoindex 模塊

          • 模塊:ngx_http_autoindex_module,默認(rèn)編譯進(jìn) Nginx,使用 --without-http_autoindex_module 取消
          • 功能:當(dāng) URL 以 / 結(jié)尾時,嘗試以 html/xml/json/jsonp 等格式返回 root/alias 中指向目錄的目錄結(jié)構(gòu)
          • 語法:# 開啟或關(guān)閉 Syntax: autoindex on | off; Default: autoindex off; Context: http, server, location # 當(dāng)以 HTML 格式輸出時,控制是否轉(zhuǎn)換為 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

          實(shí)戰(zhàn)

          • 配置文件如下:
          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 模塊是默認(rèn)編譯進(jìn) Nginx 的,且默認(rèn)指令是 index index.html,因此,會去找是否有 index.html 這個文件。

          • 打開瀏覽器,訪問 autoindex.ziyang.com:8080,html 目錄下默認(rèn)是有 index.html 文件的,因此顯示結(jié)果為:

          • 打開 index b.html 指令注釋。由于 html 文件夾下并不存在 b.html 這個文件,所以請求會走到 autoindex 模塊,顯示目錄:

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

          concat模塊

          下面介紹一個可以提升小文件性能的模塊,這個模塊是由阿里巴巴開發(fā)的,在淘寶網(wǎng)中有廣泛應(yīng)用。

          • 模塊:ngx_http_concat_module
          • 模塊開發(fā)者:Tengine(https://github.com/alibaba/nginx-http-concat) --add-module=../nginx-http-concat/
          • 功能:合并多個小文件請求,可以明顯提升 HTTP 請求的性能
          • 指令:#在 URI 后面加上 ??,通過 ”,“ 分割文件,如果還有參數(shù),則在最后通過 ? 添加參數(shù) 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

          打開淘寶主頁,會發(fā)現(xiàn)小文件都是通過這個模塊來提高性能的:

          這里就不做實(shí)戰(zhàn)了,感興趣的同學(xué)可以自己去編譯一下這個模塊,做一下實(shí)驗,我把配置文件放在這里:

          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 請求相關(guān)信息記錄到日志
          • 模塊:ngx_http_log_module,無法禁用

          access 日志格式

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

          默認(rèn)的 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 時每記錄一條日志都需要打開、關(guān)閉日志文件
          • if 通過變量值控制請求日志是否記錄
          • 日志緩存功能:批量將內(nèi)存中的日志寫入磁盤寫入磁盤的條件:所有待寫入磁盤的日志大小超出緩存大小;達(dá)到 flush 指定的過期時間;worker 進(jìn)程執(zhí)行 reopen 命令,或者正在關(guān)閉。
          • 日志壓縮功能:批量壓縮內(nèi)存中的日志,再寫入磁盤buffer 大小默認(rèn)為 64KB壓縮級別默認(rèn)為 1(1最快壓縮率最低,9最慢壓縮率最高)打開日志壓縮時,默認(rèn)打開日志緩存功能

          對日志文件名包含變量時的優(yōu)化

          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:緩存內(nèi)的最大文件句柄數(shù),超出后用 LRU 算法淘汰
          • inactive:文件訪問完后在這段時間內(nèi)不會被關(guān)閉。默認(rèn) 10 秒
          • min_uses:在 inactive 時間內(nèi)使用次數(shù)超過 min_uses 才會繼續(xù)存在內(nèi)存中。默認(rèn) 1
          • valid:超出 valid 時間后,將對緩存的日志文件檢查是否存在。默認(rèn) 60 秒
          • off:關(guān)閉緩存功能

          日志模塊沒有實(shí)戰(zhàn)。


          到了這里,我們已經(jīng)將 Nginx 處理 HTTP 請求的 11 個階段全部梳理了一遍,每個階段基本都有對應(yīng)的模塊。相信對于這樣一個全流程的解析,大家都能夠看懂 Nginx 的配置了,在此之上,還能夠按照需求靈活配置出自己想要的配置,這樣就真正的掌握了 11 個階段。


          主站蜘蛛池模板: 国产vr一区二区在线观看| 好吊视频一区二区三区| 麻豆AV无码精品一区二区| 老湿机一区午夜精品免费福利| 亚洲熟妇无码一区二区三区导航| 无码人妻精品一区二区三区久久 | 91秒拍国产福利一区| 国产一区二区好的精华液| 亚欧免费视频一区二区三区| 国产精品视频一区二区猎奇| 日韩电影一区二区三区| 亚洲午夜在线一区| 亚洲精品伦理熟女国产一区二区| 国产成人无码精品一区在线观看| 亚洲一区二区影视| 国产精品一区在线播放| 国产精品电影一区二区三区 | 亚洲视频一区网站| 国产精品视频一区二区噜噜 | 免费看一区二区三区四区| 国产精品日韩欧美一区二区三区 | 国产福利一区二区| 久久精品无码一区二区WWW| 日韩精品一区二区三区老鸭窝 | 精品无码综合一区| 国产亚洲一区二区三区在线| 亚洲av无码一区二区三区网站| 亚洲国产老鸭窝一区二区三区| 免费精品一区二区三区第35| 国产午夜精品一区二区三区嫩草| 日韩熟女精品一区二区三区| 国产av一区二区精品久久凹凸| 国语对白一区二区三区| 91在线一区二区三区| 99久久精品日本一区二区免费| 97精品一区二区视频在线观看| 午夜福利无码一区二区| 国模精品视频一区二区三区| 国产中文字幕一区| 在线观看午夜亚洲一区| 一区二区在线视频|