、原因
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 請求的示意圖,雖然簡單,但是卻很好的說明了整個過程。
以上這七個步驟從整體上介紹了一下處理流程,下面還會再說一下實(shí)際的處理過程。
下面介紹一下詳細(xì)的 11 個階段,每個階段都可能對應(yīng)著一個甚至多個 HTTP 模塊,通過這樣一個模塊對比,我們也能夠很好的理解這些模塊具體是怎么樣發(fā)揮作用的。
接下來是確認(rèn)用戶訪問權(quán)限的三個模塊:
最后的三個階段處理響應(yīng)和日志:
以上的這些階段都是嚴(yán)格按照順序進(jìn)行處理的,當(dāng)然,每個階段中各個 HTTP 模塊的處理順序也很重要,如果某個模塊不把請求向下傳遞,后面的模塊是接收不到請求的。而且每個階段中的模塊也不一定所有都要執(zhí)行一遍,下面就接著講一下各個階段模塊之間的請求順序。
如下圖所示,每一個模塊處理之間是有序的,那么這個順序怎么才能得到呢?其實(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 階段,是 11 個階段的第 1 個階段,這個階段剛剛獲取到了請求的頭部,還沒有進(jìn)行任何處理,我們可以拿到一些原始的信息。例如,拿到用戶的真實(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:
針對這個問題,Nginx 是基于變量來使用。
例如 binary_remote_addr、remote_addr 這樣的變量,其值就是真實(shí)的 IP,這樣做連接限制也就是 limit_conn 模塊才有意義,這也說明了,limit_conn 模塊只能在 preaccess 階段,而不能在 postread 階段生效。
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
上面關(guān)于 real_ip_recursive 指令可能不太容易理解,我們來實(shí)戰(zhàn)練習(xí)一下,先來看 real_ip_recursive 默認(rèn)關(guān)閉的情況:
關(guān)于如何編譯 Nginx,詳見:https://iziyang.github.io/2020/03/10/1-nginx/
# 下載 nginx 源碼,在源碼目錄下執(zhí)行
./configure --prefix=自己指定的目錄 --with-http_realip_module
make
make install
#屏蔽默認(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
? 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 打開的情況:
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 設(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 階段分為兩個,一個是 server_rewrite 階段,一個是 rewrite,這兩個階段都涉及到一個 rewrite 模塊,而在 rewrite 模塊中,有一個 return 指令,遇到該指令就不會再向下執(zhí)行,直接返回響應(yīng)。
return 指令的語法如下:
Syntax: return code [text];
return code URL;
return URL;
Default: —
Context: server, location, if
返回狀態(tài)碼包括以下幾種:
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!";
}
}
這兩個問題我們通過實(shí)戰(zhàn)驗證一下。
? 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 指令的注釋呢?
? 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í)行哪一個。
? 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>
針對上面兩個問題也就有了答案:
rewrite 指令用于修改用戶傳入 Nginx 的 URL。來看下 rewrite 的指令規(guī)則:
Syntax: rewrite regex replacement [flag];
Default: —
Context: server, location, if
它的功能主要有下面幾點(diǎn):
現(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;
}
}
那么我們的問題是:
帶著這三個問題,我們來實(shí)際演示一下。
準(zhǔn)備工作
last flag
首先訪問 rewrite.ziyang.com/first/3.txt,結(jié)果如下:
? ~ curl rewrite.ziyang.com/first/3.txt
second!
為什么結(jié)果是 second! 呢?應(yīng)該是 third! 呀,可能有人會有這樣的疑問。實(shí)際的匹配步驟如下:
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í)際的匹配步驟如下:
因此,這個過程實(shí)際請求的 URL 是 rewrite.ziyang.com/third/3.txt,這樣自然結(jié)果就是 test3 了。你還可以試試訪問 rewrite.ziyang.com/third/2.txt 看看會返回什么。
redirect 和 permanent flag
配置文件中還有 4 個 location,你可以分別試著訪問一下,結(jié)果是這樣的:
主要是一個指令 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
它的規(guī)則是:
那么 if 指令的條件表達(dá)式包含哪些內(nèi)容呢?它的規(guī)則如下:
下面是一些例子:
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;
}
當(dāng)經(jīng)過 rewrite 模塊,匹配到 URL 之后,就會進(jìn)入 find_config 階段,開始尋找 URL 對應(yīng)的 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)閉。
location 的匹配規(guī)則是僅匹配 URI,忽略參數(shù),有下面三種大的情況:
對于這些規(guī)則剛看上去肯定是很懵的,完全不知道在說什么,下面來實(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 時,會有幾個部分都匹配上:
訪問 /Test1/ 時,也會有幾個部分匹配上:
那么究竟會匹配哪一個呢?Nginx 其實(shí)是遵循一套規(guī)則的,如下圖所示:
全部的前綴字符串是放置在一棵二叉樹中的,Nginx 會分為兩部分進(jìn)行匹配:
下面看下實(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!
這里面重點(diǎn)解釋一下 /Test1/Test3 的匹配過程:
下面就來到了 preaccess 階段。我們經(jīng)常會遇到一個問題,就是如何限制每個客戶端的并發(fā)連接數(shù)?如何限制訪問頻率?這些就是在 preaccess 階段處理完成的,顧名思義,preaccess 就是在連接之前。先來看下 limit_conn 模塊。
這里面涉及到的模塊是 ngx_http_limit_conn_module,它的基本特性如下:
這里面有一點(diǎn)需要注意,就是 limit_conn key 的設(shè)計,所謂的 key 指的就是對哪個變量進(jìn)行限制,通常我們?nèi)〉亩际怯脩舻恼鎸?shí) 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
下面又到了實(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;
}
}
在這個配置文件中,做了兩條限制,一個是 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。
在本節(jié)開頭我們就提出了兩個問題:
第一個問題限制并發(fā)連接數(shù)的問題已經(jīng)解決了,下面來看第二個問題。
這里面生效的模塊是 ngx_http_limit_req_module,它的基本特性如下:
leaky bucket 叫漏桶算法,其他用來限制請求速率的還有令牌環(huán)算法等,這里面不展開講。
漏桶算法的原理是,先定義一個桶的大小,所有進(jìn)入桶內(nèi)的請求都會以恒定的速率被處理,如果請求太多超出了桶的容量,那么就會立刻返回錯誤。用一張圖解釋一下。
這張圖里面,水龍頭在不停地滴水,就像用戶發(fā)來的請求,所有的水滴都會以恒定的速率流出去,也就是被處理。漏桶算法對于突發(fā)流量有很好的限制作用,會將所有的請求平滑的處理掉。
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 默認(rèn)為 0nodelay,如果設(shè)置了這個參數(shù),那么對于漏桶中的請求也會立刻返回錯誤
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
在實(shí)際驗證之前呢,需要注意兩個問題:
添加配置文件,這個配置文件與上一節(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)在可以回答一下剛開始提出的兩個問題:
經(jīng)過 preaccess 階段對用戶的限流之后,就到了 access 階段。
這里面涉及到的模塊是 ngx_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;
}
對于用戶訪問來說,這些指令是順序執(zhí)行的,當(dāng)滿足了一條之后,就不會再向下執(zhí)行。這個模塊比較簡單,我們這里不做實(shí)戰(zhàn)演練了。
auth_basic 模塊是用作用戶認(rèn)證的,當(dāng)開啟了這個模塊之后,我們通過瀏覽器訪問網(wǎng)站時,就會返回一個 401 Unauthorized,當(dāng)然這個 401 用戶不會看見,瀏覽器會彈出一個對話框要求輸入用戶名和密碼。這個模塊使用的是 RFC2617 中的定義。
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 階段的模塊都要執(zhí)行,都通過了才會放行;值是 any 的話,表示有任意一個模塊得到執(zhí)行即可。
下面有幾個問題可以加深一下理解:
講到了這里,我們再來回顧一下 Nginx 處理 HTTP 請求的 11 個階段:
現(xiàn)在我們已經(jīng)來到了 precontent 階段,這個階段只有 try_files 這一個指令。
Syntax: try_files file ... uri;
try_files file ...=code;
Default: —
Context: server, location
下面我們實(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é)果如下:
這兩個結(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 模塊可以實(shí)時拷貝流量,這對于需要同時訪問多個環(huán)境的請求是非常有用的。
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
下面來看一個問題:
現(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 在映射 URL 時,會把 location 中的路徑也加進(jìn)去,也就是:
還是上面的配置文件:
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
可以看出來,三個路徑分別是:
還有其他的一些配置指令,例如:
靜態(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)掉。
現(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)是打開的,因此注釋掉就行了),看下返回什么:
? 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)閉掉這兩個指令,看下返回什么:
? 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 請求頭中的域名。
這個模塊,當(dāng)我們訪問以 / 結(jié)尾的目錄時,會去找 root 或 alias 指令的文件夾下的 index.html,如果有這個文件,就會把文件內(nèi)容返回,也可以指定其他文件。
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_exact_size on; 這條指令決定的。
下面介紹一個可以提升小文件性能的模塊,這個模塊是由阿里巴巴開發(fā)的,在淘寶網(wǎng)中有廣泛應(yīng)用。
打開淘寶主頁,會發(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;
}
}
下面終于來到了 11 個階段的最后一個階段,記錄請求訪問日志的 log 模塊。
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
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
日志模塊沒有實(shí)戰(zhàn)。
到了這里,我們已經(jīng)將 Nginx 處理 HTTP 請求的 11 個階段全部梳理了一遍,每個階段基本都有對應(yīng)的模塊。相信對于這樣一個全流程的解析,大家都能夠看懂 Nginx 的配置了,在此之上,還能夠按照需求靈活配置出自己想要的配置,這樣就真正的掌握了 11 個階段。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。