《深入理解NGINX 模块开发与架构解析》之摘抄学习
1.基于Nginx框架開發(fā)程序有5個(gè)優(yōu)勢(shì):
? ? (1).Nginx將網(wǎng)絡(luò)、磁盤及定時(shí)器等異步事件的驅(qū)動(dòng)都做了非常好的封裝,基于它開發(fā)將可以忽略這些事件處理的細(xì)節(jié);
? ? (2).Nginx封裝了許多平臺(tái)無關(guān)的接口、容器,適用于跨平臺(tái)開發(fā)。
? ? (3) 優(yōu)秀的模塊化設(shè)計(jì),使得開發(fā)者可以輕易地復(fù)用各種已有的模塊,其中既包括基本的讀取配置、記錄日志等模塊,也包括處理請(qǐng)求的諸如HTTP.mail等高級(jí)功能模塊;
? ? (4)?Nginx是作為服務(wù)器來設(shè)計(jì)其框架的,因此,它在服務(wù)器進(jìn)程的管理上相當(dāng)出色,基于它開發(fā)服務(wù)器程序可以輕松地實(shí)現(xiàn)程序的動(dòng)態(tài)升級(jí),子進(jìn)程的監(jiān)控、管理,配置項(xiàng)的動(dòng)態(tài)修改生效等;
? ? (5).Nginx充分考慮到各操作系統(tǒng)所擅長的“絕活”,能夠使用特殊的系統(tǒng)調(diào)用更高效地完成任務(wù)時(shí),絕不會(huì)去使用低效的通用接口。尤其對(duì)于Linux操作系統(tǒng),Nginx不遺余力地做了大量優(yōu)化。
2.由于默認(rèn)的Linux內(nèi)核參數(shù)考慮的是最通用的場(chǎng)景,這明顯不符合用于支持高并發(fā)訪問的Web服務(wù)器的定義,所以需要修改Linux內(nèi)核參數(shù),使得Nginx可以擁有更高的性能。
? ?最常用的配置:
fs.file-max = 999999 //這個(gè)參數(shù)表示進(jìn)程(比如一個(gè)worker進(jìn)程)可以同時(shí)打開的最大句柄數(shù) net.ipv4.tcp_tw_reuse = 1 //這個(gè)參數(shù)設(shè)置為1,表示允許將TIME-WAIT狀態(tài)的socket重新用于新的TCP連接,這對(duì)于服務(wù)器來說很有意義,因?yàn)榉?wù)器上總會(huì)有大量TIME-WAIT狀態(tài)的連接。 net.ipv4.tcp_keepalive_time = 600 //這個(gè)參數(shù)表示當(dāng)前keepalive啟用時(shí),TCP發(fā)送keepalive消息的頻度。默認(rèn)是2小時(shí),若將其設(shè)置得小一些,可以更快地清理無效的連接 net.ipv4.tcp_fin_timeout = 30 //這個(gè)參數(shù)表示當(dāng)前當(dāng)服務(wù)器主動(dòng)關(guān)閉連接時(shí),socket保持在FIN-WAIT-2狀態(tài)的最大時(shí)間 net.ipv4.tcp_max_tw_buckets = 5000 //這個(gè)參數(shù)表示操作系統(tǒng)允許TIME_WAIT套接字?jǐn)?shù)量的最大值,如果超過這個(gè)數(shù)字,TIME_WAIT套接字將立刻被清除并打印警告信息。 net.ipv4.ip_local_port_range = 1024 61000 //這個(gè)參數(shù)定義了在UDP和TCP連接中本地(不包括連接的遠(yuǎn)端)端口的取值范圍。 net.ipv4.tcp_rmem = 4096 32768 262142 //這個(gè)參數(shù)定義了TCP接收緩存(用于TCP接受滑動(dòng)窗口)的最小值、默認(rèn)值、最大值。 net.ipv4.tcp_wmem = 4096 32768 262142 //這個(gè)參數(shù)定義了TCP發(fā)送緩存(用于TCP發(fā)送滑動(dòng)窗口)的最小值、默認(rèn)值、最大值。 net.core.netdev_max_backlog = 8096 //當(dāng)網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時(shí),會(huì)有一個(gè)隊(duì)列保存這些數(shù)據(jù)包。這個(gè)參數(shù)表示該隊(duì)列的最大值。 net.core.rmem_default = 262144 //這個(gè)參數(shù)表示內(nèi)核套接字接收緩存區(qū)默認(rèn)的大小。 net.core.wmem_default = 262144 //這個(gè)參數(shù)表示內(nèi)核套接字發(fā)送緩存區(qū)默認(rèn)的大小。 net.core.rmem_max = 2097152 //這個(gè)參數(shù)表示內(nèi)核套接字接收緩存區(qū)的最大大小。 net.core.wmem_max = 2097152 //這個(gè)參數(shù)表示內(nèi)核套接字發(fā)送緩存區(qū)的最大大小。 net.ipv4.tcp_syncookies = 1 //該參數(shù)與性能無關(guān),用于解決TCP的SYN攻擊。 net.ipv4.tcp_max_syn_backlog = 1024 //這個(gè)參數(shù)表示TCP三次握手建立階段SYN請(qǐng)求隊(duì)列的最大長度,默認(rèn)為1024,將其設(shè)置得大一些可以使出現(xiàn)Nginx繁忙來不及accept新連接的情況時(shí),Linux不至于丟失客戶端發(fā)起的連接請(qǐng)求。3.configure腳本的內(nèi)容如下:
#!bin/sh# Copyright (C) Igor Sysoev # Copyright (C) Nginx, Inc.#auto/options腳本處理configure命令的參數(shù)。例如,如果參數(shù)是--help,那么顯示支持的所有參數(shù)格式。options腳本會(huì)定義后續(xù)工作將要用到的變量,然后根據(jù)本次參數(shù)以及默認(rèn)值設(shè)置這些變量 . auto/options#auto/init腳本初始化后續(xù)將產(chǎn)生的文件路徑。例如,Makefile、ngx_modules.c等文件默認(rèn)情況下會(huì)在<nginx-source>/objs/ . auto/init#auto/sources腳本將分析Nginx的源碼結(jié)構(gòu),這樣才能構(gòu)造后續(xù)的Makefile文件 . auto/sources# 編譯過程中所有目錄文件生成的路徑由--builddir=DIR參數(shù)指定,默認(rèn)情況下為<nginx-source>/objs,此時(shí)這個(gè)目錄將會(huì)被創(chuàng)建 test -d $NGX_OBJS || mkdir $NGX_OBJS# 開始準(zhǔn)備建立ngx_auto_headers.h、autoconf.err等必要的編譯文件 echo > $NGX_AUTO_HEADERS_H echo > $NGX_AUTOCONF_ERR# 向objs/ngx_auto_config.h寫入命令行帶的參數(shù) echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H# 判斷DEBUG標(biāo)志,如果有,那么在objs/ngx_auto_config.h文件中寫入DEBUG宏 if [ $NGX_DEBUG = YES ]; thenhave=NGX_DEBUG . auto/have fi# 現(xiàn)在開始檢查操作系統(tǒng)參數(shù)是否支持后續(xù)編譯 if test -z "$NGX_PLATFORM"; thenecho "checking for OS"NGX_SYSTEM=`uname -s 2>/dev/null`NGX_RELEASE=`uname -r 2>/dev/null`NGX_MACHINE=`uname -m 2>/dev/null`#屏幕上輸出OS名稱、內(nèi)核版本、32位/64位內(nèi)核echo " + $NGX_SYSTEM $NGX_RELEASE $NGX_MACHINE"NGX_PLATFORM="$NGX_SYSTEM:$NGX_RELEASE:$NGX_MACHINE";case "$NGX_SYSTEM" inMINGW32_*)NGX_PLATFORM=win32;;esacelseecho "building for $NGX_PLATFORM"NGX_SYSTEM=$NGX_PLATFORMfi#檢查并設(shè)置編譯器,如GCC是否安裝、GCC版本是否支持后續(xù)編譯nginx. auto/cc/conf# 對(duì)非Windows操作系統(tǒng)定義一些必要的頭文件,并檢查其是否存在,一次決定configure后續(xù)步驟是否可以成功if [ "$NGX_PLATFORM" != win32 ]; then. auto/headersfi# 對(duì)于當(dāng)前操作系統(tǒng),定義一些特定的操作系統(tǒng)相關(guān)的方法并檢查當(dāng)前環(huán)境是否支持。例如,對(duì)于Linux,在這里使用sched_sestaffinity設(shè)置進(jìn)程優(yōu)先級(jí),使用Linux特有的sendfile系統(tǒng)調(diào)用來加速向網(wǎng)絡(luò)中發(fā)送文件塊. auto/os/conf# 定義類UNIX操作系統(tǒng)中通用的頭文件和系統(tǒng)調(diào)用等,并檢查當(dāng)前環(huán)境是否支持if [ "$NGX_PLATFORM" != win32 ]; then. auto/unixfi#最核心的構(gòu)造運(yùn)行期modules的腳本。它將會(huì)生成ngx_modules.c文件,這個(gè)文件會(huì)被編譯進(jìn)Nginx中,其中它所做的唯一的事情就是定義了ngx_modules數(shù)組。ngx_modules指明Nginx運(yùn)行期間有哪些模塊會(huì)參與到請(qǐng)求的處理中,包括HTTP請(qǐng)求可能會(huì)使用哪些模塊處理,因此,它對(duì)數(shù)組元素的順序非常敏感,也就是說,絕大部分模塊在ngx_modules數(shù)組中的順序其實(shí)是固定的。例如,一個(gè)請(qǐng)求必須先執(zhí)行ngx_http_gzip_filter_module模塊重新修改HTTP響應(yīng)中的頭部后,才能使用ngx_http_header_filter模塊按照headers_in結(jié)構(gòu)體里的成員構(gòu)造出以TCP流形式發(fā)送給客戶端的HTTP響應(yīng)頭部。注意,我們?cè)?-add-module=參數(shù)里加入的第三方模塊也在此步驟寫入到ngx_modules.c文件中了. auto/modules# conf腳本用來檢查Nginx在鏈接期間需要鏈接的第三方靜態(tài)庫、動(dòng)態(tài)庫或者目標(biāo)文件是否存在. auto/lib/conf# 處理Nginx安裝后的路徑case ".$NGX_PREFIX" in.)NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx}have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define;;.!)NGX_PREFIX=;;*)have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define;;esac# 處理Nginx安裝后conf文件的路徑if [ ".$NGX_CONF_PREFIX" != "." ]; thenhave=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/definefi# 處理Nginx安裝后,二進(jìn)制文件、pid、lock等其他文件的路徑可參見configure參數(shù)中路徑類選項(xiàng)的說明have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/definehave=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/definehave=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/definehave=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/definehave=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/definehave=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/definehave=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/definehave=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\"" . auto/definehave=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\"" . auto/definehave=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\"" . auto/definehave=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\"" . auto/define# 創(chuàng)建編譯時(shí)使用的objs/Makefile文件. auto/make# 為objs/Makefile加入需要連接的第三方靜態(tài)庫、動(dòng)態(tài)庫或者目標(biāo)文件. auto/lib/make# 為objs/Makefile加入install功能,當(dāng)執(zhí)行make install時(shí)將編譯生成的必要文件復(fù)制到安裝路徑,建立必要的目錄. auto/install# 在ngx_auto_config.h文件中加入NGX_SUPPERSS_WARN宏、NGX_SMP宏. auto/stubs# 在ngx_auto_config.h文件中指定NGX_USER和NGX_GROUP宏,如果執(zhí)行configure時(shí)沒有參數(shù)指定,默認(rèn)兩者皆為nobody(也就是默認(rèn)以nobody用戶運(yùn)行進(jìn)程)have=NGX_USER value="\"$NGX_USER\"" . auto/definehave=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define# 顯示configure執(zhí)行的結(jié)果,如果失敗,則給出原因. auto/summary4.ngx_moduels.c文件就是用來定義ngx_moduels數(shù)組的。它指明了每個(gè)模塊在Nginx中的優(yōu)先級(jí),當(dāng)一個(gè)請(qǐng)求同時(shí)符合多個(gè)模塊的處理規(guī)則時(shí),將按照它們?cè)趎gx_moduels數(shù)組中的順序選擇最靠前的模塊優(yōu)先處理。對(duì)于HTTP過濾模塊而言,在ngx_modules數(shù)組中越是靠后的模塊反而會(huì)首先處理HTTP響應(yīng)。
5.日志文件回滾
? ?使用-s reopen參數(shù)可以重新打開日志文件,這樣可以先把當(dāng)前日志文件改名或轉(zhuǎn)移到其他目錄中進(jìn)行備份,再重新打開時(shí)就會(huì)生成新的日志文件。這個(gè)功能使得日志文件不至于過大。
6.平滑升級(jí)Nginx??
? ?當(dāng)Nginx服務(wù)升級(jí)到新的版本時(shí),必須要將舊的二進(jìn)制文件Nginx替換掉,通常情況下這是需要重啟服務(wù)的,但Nginx支持不重啟服務(wù)來完成新版本的平滑升級(jí)。
? ?升級(jí)時(shí)包括以下步驟:
? ? 1) 通知正在運(yùn)行的舊版本Nginx準(zhǔn)備升級(jí)。通過向master進(jìn)程發(fā)送USR2信號(hào)可達(dá)到目的。例如:
kill -s SIGUSR2 <nginx master pid>? ? ? 這時(shí),運(yùn)行中的Nginx會(huì)將pid文件重命名,如將/usr/local/nginx/los/nginx.pid重命名為/usr/local/nginx/logs/nginx.pid.oldbin,這樣新的Nginx才有可能啟動(dòng)成功。
? ? ?2) 啟動(dòng)新版本的Nginx,可以使用以上介紹過的任意一種啟動(dòng)方法,這時(shí)通過ps命令可以發(fā)現(xiàn)新舊版本的Nginx在同時(shí)運(yùn)行。
? ? ?3) 通過kill命令向舊版本的master進(jìn)程發(fā)送SIGQUIT信號(hào),以“優(yōu)雅”的方式關(guān)閉舊版本的Nginx。隨后將只有新版本的Nginx服務(wù)運(yùn)行,此時(shí)平滑升級(jí)完畢。
7.部署后Nginx進(jìn)程間的關(guān)系
? ?
8.系統(tǒng)調(diào)用gettimeofday的執(zhí)行頻率
? ?默認(rèn)情況下,每次內(nèi)核的事件調(diào)用(如epoll、select、poll、kqueue等)返回時(shí),都會(huì)執(zhí)行一次gettimeofdata,實(shí)現(xiàn)用內(nèi)核的時(shí)鐘來更新Nginx中的緩存時(shí)鐘。
9.server_name后可以跟多個(gè)主機(jī)名稱,如server_name www.testweb.com、download.testweb.com;。
? 在開始處理一個(gè)HTTP請(qǐng)求時(shí),Nginx會(huì)取出header頭中的Host,與每個(gè)server中的server_name進(jìn)行匹配,以此決定到底由哪一個(gè)server塊來處理這個(gè)請(qǐng)求。有可能一個(gè)Host與多個(gè)server塊中的server_name都匹配,這是就會(huì)根據(jù)匹配優(yōu)先級(jí)來選擇實(shí)際處理的server塊。server_name與Host的匹配優(yōu)先級(jí)如下:
? ? 1) 首先選擇所有字符串完全匹配的server_name,如www.testweb.com.
? ? 2)其次選擇通配符在前面的server_name,如 *.testweb.com。
? ? 3)再次選擇通配符在后面的server_name,如www.testweb.*。
? ? 4)最后選擇使用正則表達(dá)式才匹配的server_name,如~^\.testweb\.com$。
10.作為靜態(tài)Web服務(wù)器與反向代理服務(wù)器的Nginx
? ??
11.Nginx作為反向代理服務(wù)器時(shí)轉(zhuǎn)發(fā)請(qǐng)求的流程
? ??
? ?當(dāng)客戶端發(fā)來HTTP請(qǐng)求時(shí),Nginx并不會(huì)立刻轉(zhuǎn)發(fā)到上游服務(wù)器,而是先把用戶的請(qǐng)求(包括HTTP包體)完整地接收到Nginx所在服務(wù)器的硬盤或者內(nèi)存中,然后再向上游服務(wù)器發(fā)起連接,把緩存的客戶端請(qǐng)求轉(zhuǎn)發(fā)到上游服務(wù)器。而Squid等代理服務(wù)器則采用一邊接收客戶端請(qǐng)求,一邊轉(zhuǎn)發(fā)到上游服務(wù)器的方式。
? ?Nginx的這種工作方式有什么優(yōu)缺點(diǎn)呢?很明顯,缺點(diǎn)是延長了一個(gè)請(qǐng)求的處理時(shí)間,并增加了用于緩存請(qǐng)求內(nèi)容的內(nèi)存內(nèi)核磁盤空間。而優(yōu)點(diǎn)則是降低了上游服務(wù)器的負(fù)載,盡量把壓力放在Nginx服務(wù)器上。
12.Nginx HTTP模塊調(diào)用的簡(jiǎn)化流程
? ??
13.在Linux平臺(tái)下,Nginx對(duì)ngx_int_t和ngx_uint_t的定義如下:
typedef intptr_t ngx_int_t; typedef uintptr_t ngx_uint_t;14.ngx_str_t的定義如下:
typedef struct {size_t len;u_char *data; } ngx_str_t;? ?任何視圖將ngx_str_t的data成員當(dāng)做字符串來使用的情況,都可能導(dǎo)致內(nèi)存越界!
? ?Nginx使用ngx_str_t可以有效地降低內(nèi)存使用量。例如,用戶請(qǐng)求“GET /test?a=1 http/1.1\r\n”存儲(chǔ)到內(nèi)存地址0x1d0b0110上,這時(shí)只需要把r->method_name設(shè)置為{len = 3,data = 0x1d0b0110}就可以表示方法名"GET",而不需要單獨(dú)為method_name再分配內(nèi)存冗余的存儲(chǔ)字符串。
15.ngx_list_t是Nginx封裝的鏈表容器,它在Nginx中使用得很頻繁,例如HTTP的頭部就是用ngx_list_t來存儲(chǔ)的。先看一下ngx_list_t相關(guān)成員的定義:
typedef struct ngx_list_part_s ngx_list_part_t;struct ngx_list_part_s {void *elts; //指向數(shù)組的起始地址。ngx_uint_t nelts; //表示數(shù)組中已經(jīng)使用了多少個(gè)元素,當(dāng)然,nelts必須小于ngx_list_t結(jié)構(gòu)體中的nalloc.ngx_list_part_t *next; //下一個(gè)鏈表元素ngx_list_part_t的地址。 };typedef struct {ngx_list_part_t *last; //指向鏈表的最后一個(gè)數(shù)組元素ngx_list_part_t part; //鏈表的首個(gè)數(shù)組元素size_t size; //鏈表中的每個(gè)ngx_list_part_t元素都是一個(gè)數(shù)組。因?yàn)閿?shù)組存儲(chǔ)的是某種類型的數(shù)據(jù)結(jié)構(gòu),且ngx_list_t是非常靈活的數(shù)據(jù)結(jié)構(gòu),所以它不會(huì)限制存儲(chǔ)什么樣的數(shù)據(jù),只是通過size限制每一個(gè)數(shù)組元素的占用的空間大小,也就是用戶要存儲(chǔ)的一個(gè)數(shù)據(jù)所占用的字節(jié)數(shù)必須小于或等于size。ngx_uint_t nalloc; //鏈表的數(shù)組元素一旦分配后是不可更改的。nalloc表示每個(gè)ngx_list_part_t數(shù)組的容量,即最多可存儲(chǔ)多少個(gè)數(shù)據(jù)。ngx_pool_t *pool; //鏈表中管理內(nèi)存分配的內(nèi)存池對(duì)象。用戶要存放的數(shù)據(jù)占用的內(nèi)存都是由pool分配的。 } ngx_list_t;? ngx_list_t的內(nèi)存分布情況如下:
? ?
? ?上圖中是由3個(gè)ngx_list_part_t數(shù)組元素組成的ngx_list_t鏈表中可能擁有的一種內(nèi)存分布結(jié)構(gòu)。這里,pool內(nèi)存池為其分配了連續(xù)的內(nèi)存,最前端內(nèi)存存儲(chǔ)的是ngx_list_t結(jié)構(gòu)中的成員,緊接著是第一個(gè)ngx_list_part_t結(jié)構(gòu)占用的內(nèi)存,然后是ngx_list_part_t結(jié)構(gòu)指向的數(shù)組,它們一共占用size*nalloc字節(jié),表示數(shù)組中擁有nalloc個(gè)大小為size的元素。其后面是第2個(gè)ngx_list_part_t結(jié)構(gòu)以及它所指向的數(shù)組,依次類推。
16.ngx_table_elt_t數(shù)據(jù)結(jié)構(gòu)如下所示:
typedef struct {ngx_uint_t hash; //表明ngx_table_elt_t也可以是某個(gè)散列表數(shù)據(jù)結(jié)構(gòu)(ngx_hash_t類型)中的成員ngx_str_t key;ngx_str_t value;u_char *lowcase_key; } ngx_table_elt_t;? 顯而易見,ngx_table_elt_t是為HTTP頭部"量身定制"的。
17.緩沖區(qū)ngx_buf_t是Nginx處理大數(shù)據(jù)的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),它既應(yīng)用于內(nèi)存數(shù)據(jù)也應(yīng)用于磁盤數(shù)據(jù)。下面主要介紹ngx_buf_t結(jié)構(gòu)體本身:
typedef struct ngx_buf_s ngx_buf_t; typedef void * ngx_buf_tag_t; struct ngx_buf_s {/* pos通常是用來告訴使用者本次應(yīng)該從pos這個(gè)位置開始處理內(nèi)存中的數(shù)據(jù),這樣設(shè)置是因?yàn)橥粋€(gè)ngx_buf_t可能被多次反復(fù)處理,當(dāng)然,pos的含義是由使用它的模塊定義的*/u_char *pos;/* last通常表示有效的內(nèi)容到此為止,注意,pos與last之間的內(nèi)存是希望nginx處理的內(nèi)容*/u_char *last;/*處理文件時(shí),file_pos與file_last的含義與處理內(nèi)存時(shí)的pos與last相同,file_pos表示將要處理的文件位置,file_last表示截止的文件位置*/off_t file_pos;off_t file_last;//如果ngx_buf_t緩沖區(qū)用于內(nèi)存,那么start指向這段內(nèi)存的起始地址u_char *start;//與start成員對(duì)應(yīng),指向緩沖區(qū)內(nèi)存的末尾u_char *end;/* 表示當(dāng)前緩沖區(qū)的類型,例如由哪個(gè)模塊使用就指向這個(gè)模塊ngx_module_t變量的地址*/ngx_buf_tag_t tag;//引用的文件ngx_file_t *file;/* 當(dāng)前緩沖區(qū)的影子緩沖區(qū),該成員很少用到,僅僅在12.8節(jié)描述的使用緩沖區(qū)轉(zhuǎn)發(fā)上游服務(wù)器的響應(yīng)時(shí)才使用了shadow成員,這是因?yàn)镹ginx太節(jié)約內(nèi)存了,分配一塊內(nèi)存并使用ngx_buf_t表示接收到的上游服務(wù)器響應(yīng)后,在向下游客戶端轉(zhuǎn)發(fā)時(shí)可能會(huì)把這塊內(nèi)存存儲(chǔ)到文件中,也可能直接向下游發(fā)送,此時(shí)Nginx絕不會(huì)重新復(fù)制一份內(nèi)存用于新的目的,而是再次建立一個(gè)ngx_buf_t結(jié)構(gòu)體指向原內(nèi)存,這樣多個(gè)ngx_buf_t結(jié)構(gòu)體指向了同一塊內(nèi)存,它們之間的關(guān)系就通過shadow成員來引用。這種設(shè)計(jì)過于復(fù)雜,通常不建議使用*/ngx_buf_t *shadow;//臨時(shí)內(nèi)存標(biāo)志位,為1時(shí)表示數(shù)據(jù)在內(nèi)存中且這段內(nèi)存可以修改unsigned temporary:1;//標(biāo)志位,為1時(shí)表示數(shù)據(jù)在內(nèi)存中且這段內(nèi)存不可以被修改unsigned memory:1;//標(biāo)志位,為1時(shí)表示這段內(nèi)存使用mmap系統(tǒng)調(diào)用映射過來的,不可以被修改unsigned mmap:1;//標(biāo)志位,為1時(shí)表示可回收unsigned recycled:1;//標(biāo)志位,為1時(shí)表示這段緩沖區(qū)處理的是文件而不是內(nèi)存unsigned in_file:1;//標(biāo)志位,為1時(shí)表示需要執(zhí)行flush操作unsigned flush:1;/*標(biāo)志位,對(duì)于操作這塊緩沖區(qū)時(shí)是否使用同步方式,需謹(jǐn)慎考慮,這可能會(huì)阻塞Nginx進(jìn)程,Nginx中所有操作幾乎都是異步的,這是它支持高并發(fā)的關(guān)鍵。有些框架代碼在sync為1時(shí)可能會(huì)有阻塞的方式進(jìn)行I/O操作,它的意義視使用它的Nginx模塊而定*/unsigned sync:1;/*標(biāo)志位,表示是否是最后一塊緩沖區(qū),因?yàn)閚gx_buf_t可以由ngx_chain_t鏈表串聯(lián)起來,因?yàn)?#xff0c;當(dāng)last_buf為1時(shí),表示當(dāng)前是最后一塊待處理的緩沖區(qū)*/unsigned last_buf:1;//標(biāo)志位,表示是否是ngx_chain-t中的最后一塊緩沖區(qū)unsigned last_in_chain:1;/* 標(biāo)志位,表示是否是最后一個(gè)影子緩沖區(qū),與shadow域配合使用。通常不建議使用它*/unsigned last_shadow:1;//標(biāo)志位,表示當(dāng)前緩沖區(qū)是否屬于臨時(shí)文件unsigned temp_file:1; };18.ngx_chain_t是與ngx_buf_t配合使用的鏈表數(shù)據(jù)結(jié)構(gòu),來看一下定義:
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s {ngx_buf_t *buf; //指向當(dāng)前的ngx_buf_t緩沖區(qū)ngx_chain-t *next; //用來指向下一個(gè)ngx_chain_t,如果這是最后一個(gè)ngx_chain_t,則需要把next置為NULL。 };? 在向用戶發(fā)送HTTP包體時(shí),就要傳入ngx_chain_t鏈表對(duì)象,注意,如果是最后一個(gè)ngx_chain_t,那么必須將next置為NULL,否則永遠(yuǎn)不會(huì)發(fā)送成功,而且這個(gè)請(qǐng)求將一直不會(huì)結(jié)束(Nginx框架的要求).
19.ngx_module_t是一個(gè)Nginx模塊的數(shù)據(jù)結(jié)構(gòu),如下所示:
typedef struct ngx_module_s ngx_module_t; struct ngx_module_s {/* 下面的ctx_index、index、spare0、spare1、spare2、spare3、version變量不需要在定義時(shí)賦值,可以用Nginx準(zhǔn)備好的宏NGX_MODULE_V1來定義,它已經(jīng)定義好了這7個(gè)值 #define NGX_MODULE_V1 0,0,0,0,0,0,1對(duì)于一類模塊(由下面的type成員決定類別)而言,ctx_index表示當(dāng)前模塊在這類模塊中的序號(hào)。這個(gè)成員常常是由管理這類模塊的一個(gè)Nginx核心模塊設(shè)置的,對(duì)于所有的HTTP模塊而言,ctx_index是由核心模塊ngx_http_module設(shè)置的。ctx_index非常重要,Nginx的模塊化設(shè)計(jì)非常依賴于各個(gè)模塊的順序,它們既用于表達(dá)優(yōu)先級(jí),也用于表明每個(gè)模塊的位置,借以幫助Nginx框架快速獲得某個(gè)模塊的數(shù)據(jù)*/ngx_uint_t ctx_index;/*index表示當(dāng)前模塊在ngx_modules數(shù)組中的序號(hào),注意,ctx_index表示的是當(dāng)前模塊在一類模塊中的序號(hào),而index表示當(dāng)前模塊在所有模塊中的序號(hào),它同樣關(guān)鍵。Nginx啟動(dòng)時(shí)會(huì)根據(jù)ngx_modules數(shù)組設(shè)置各模塊的index值,例如:ngx_max_module = 0;for (i=0; ngx_modules[i]; i++) {ngx_modules[i]->index = ngx_max_module++;}*/ngx_uint_t index;//spare系列的保留變量,暫未使用ngx_uint_t spare0;ngx_uint_t spare1;ngx_uint_t spare2;ngx_uint_t spare3;//模塊的版本,便于將來的擴(kuò)展。目前只有一種,默認(rèn)為1ngx_uint_t version;/*ctx用于指向一類模塊的上下文結(jié)構(gòu)體,為什么需要ctx呢?因?yàn)榍懊嬲f過,Nginx模塊有許多種類,不同類模塊之間的功能差別很大。例如,事件類型的模塊主要處理I/O事件相關(guān)的功能,HTTP類型的模塊主要處理HTTP應(yīng)用層的功能。這樣,每個(gè)模塊都有了自己的特性,而ctx將會(huì)指向特定類型模塊的公共接口。例如,在HTTP模塊中,ctx需要指向ngx_http_module_t結(jié)構(gòu)體*/void *ctx;//commands將處理nginx.conf中的配置項(xiàng)ngx_command_t *commands;/*type表示該模型的類型,它與ctx指針是緊密相關(guān)的。在官方Nginx中,它的取值范圍是以下5種:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE。*/ngx_uint_t type;/*在Nginx的啟動(dòng)、停止過程中,以下7個(gè)函數(shù)指針表示有7個(gè)執(zhí)行點(diǎn)會(huì)分別調(diào)用者7種方法。對(duì)于任一個(gè)方法而言,如果不需要Nginx在某個(gè)時(shí)刻執(zhí)行它,那么簡(jiǎn)單地把它設(shè)為NULL空指針即可*//*雖然從字面上理解應(yīng)當(dāng)在master進(jìn)程啟動(dòng)時(shí)回調(diào)init_master,但到目前為止,框架代碼從來不會(huì)調(diào)用它,因此,可將init_master設(shè)為NULL */ngx_int_t (*init_master)(ngx_lot_t *log);/* init_module回調(diào)方法在初始化所有模塊時(shí)被調(diào)用。在master/worker模式下,這個(gè)階段將在啟動(dòng)worker子進(jìn)程前完成*/ngx_int_t (*init_module)(ngx_cycle_t *cycle);/*init_process回調(diào)方法在正常服務(wù)前被調(diào)用。在master/worker模式下,多個(gè)worker子進(jìn)程已經(jīng)產(chǎn)生,在每個(gè)worker進(jìn)程的初始化過程會(huì)調(diào)用所有模塊的init_process函數(shù) */ngx_int_t (*init_process)(ngx_cycle_t *cycle);/*由于Nginx暫不支持多線程模式,所以init_thread在框架代碼中沒有被調(diào)用過,設(shè)為NULL*/ngx_int_t (*init_thread)(ngx_cycle_t *cycle);//同上,exit_thread也不支持,設(shè)為NULL.void (*exit_process)(ngx_cycle_t *cycle);//exit_master回調(diào)方法將在master進(jìn)程退出前被調(diào)用void (*exit_master)(ngx_cycle_t *cycle);/*保留字段,目前沒有使用*/uintptr_t spare_hook0;uintptr_t spare_hook1;uintptr_t spare_hook2;uintptr_t spare_hook3;uintptr_t spare_hook4;uintptr_t spare_hook5;uintptr_t spare_hook6;uintptr_t spare_hook7; };20.HTTP框架在讀取、重載配置文件時(shí)定義了由ngx_http_module_t接口描述的8個(gè)階段,HTTP框架在啟動(dòng)過程中會(huì)在每一個(gè)階段中調(diào)用ngx_http_module_t中相應(yīng)的方法。
typedef struct {//解析配置文件前調(diào)用ngx_int_t (*preconfiguration)(ngx_conf_t *cf);//完成配置文件的解析后調(diào)用ngx_int_t (*postconfiguration)(ngx_conf_t *cf);/*當(dāng)需要?jiǎng)?chuàng)建數(shù)據(jù)結(jié)構(gòu)用于存儲(chǔ)main級(jí)別(直屬于http{...}塊的配置項(xiàng))的全局配置項(xiàng)時(shí),可以通過create_main_conf回調(diào)方法創(chuàng)建存儲(chǔ)全局配置項(xiàng)的結(jié)構(gòu)體 */void *(*create_main_conf)(ngx_conf_t *cf);//常用于初始化main級(jí)別配置項(xiàng)char *(*init_main_conf)(ngx_conf_t *cf, void *conf);/* 當(dāng)需要?jiǎng)?chuàng)建數(shù)據(jù)結(jié)構(gòu)用于存儲(chǔ)srv級(jí)別(直屬于虛擬主機(jī)server{...}塊的配置項(xiàng))的配置項(xiàng)時(shí),可以通過實(shí)現(xiàn)create_srv_conf回調(diào)方法創(chuàng)建存儲(chǔ)srv級(jí)別配置項(xiàng)的結(jié)構(gòu)體 */void *(*create_srv_conf)(ngx_conf_t *cf);//merge_srv_conf回調(diào)方法主要用于合并main級(jí)別和srv級(jí)別下的同名配置項(xiàng)char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);/*當(dāng)需要?jiǎng)?chuàng)建數(shù)據(jù)結(jié)構(gòu)用于存儲(chǔ)loc級(jí)別(直屬于location{...}塊的配置項(xiàng))的配置項(xiàng)時(shí),可以實(shí)現(xiàn)create_loc_conf回調(diào)方法*/void *(*create_loc_conf)(ngx_conf_t *cf);//merge_loc_conf回調(diào)方法主要用于合并srv級(jí)別和loc級(jí)別下的同名配置項(xiàng)char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_t;21.每一個(gè)ngx_command_t結(jié)構(gòu)體定義了自己感興趣的一個(gè)配置項(xiàng):
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s {//配置項(xiàng)名稱,如"gzip"ngx_str_t name;/*配置項(xiàng)類型,type將指定配置項(xiàng)可以出現(xiàn)的位置。例如,出現(xiàn)在server{}或location{}中,以及它可以攜帶的參數(shù)個(gè)數(shù) */ngx_uint_t type;//出現(xiàn)了name中指定的配置項(xiàng)后,將會(huì)調(diào)用set方法處理配置項(xiàng)的參數(shù)char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//在配置文件中的偏移量ngx_uint_t conf;/*通常用于使用預(yù)設(shè)的解析方法解析配置項(xiàng),這是配置模塊的一個(gè)優(yōu)秀設(shè)計(jì)。*/ngx_uint_t offset;//配置項(xiàng)讀取后的處理方法,必須是ngx_conf_post_t結(jié)構(gòu)的指針void *post; };? ?ngx_null_command只是一個(gè)空的ngx_command_t,如下所示:
?#define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL }
22.? ?
typedef enum {//在接收到完整的HTTP頭部后處理的HTTP階段NGX_HTTP_POST_READ_PHASE = 0,/*在還沒有查詢到URI匹配的location前,這時(shí)rewrite重寫URL也作為一個(gè)獨(dú)立的HTTP階段*/NGX_HTTP_SERVER_REWRITE_PHASE,/*根據(jù)URI尋找匹配的location,這個(gè)階段通常由ngx_http_core_module模塊實(shí)現(xiàn),不建議其他HTTP模塊重新定義這一階段的行為*/NGX_HTTP_FIND_CONFIG_PHASE,/*在NGX_HTTP_FIND_CONFIG_PHASE階段之后重寫URL的意義與NGX_HTTP_SERVER_REWRITE_PHASE階段顯然是不同的,因?yàn)檫@兩者會(huì)導(dǎo)致查找到不同的location塊(location是與URI進(jìn)行匹配的) */NGX_HTTP_REWRITE_PHASE,/* 這一階段是用于在rewrite重寫URL后重新跳到NGX_HTTP_FIND_CONFIG_PHASE階段,找到與心得URI匹配的location。所以,這一階段是無法由第三方HTTP模塊處理的,而僅由ngx_http_core_module模塊使用*/NGX_HTTP_POST_REWRITE_PHASE,//處理NGX_HTTP_ACCESS_PHASE階段前,HTTP模塊可以介入的處理階段NGX_HTTP_PREACCESS_PHASE,/*這個(gè)階段用于讓HTTP模塊判斷是否允許這個(gè)請(qǐng)求訪問Nginx服務(wù)器*/NGX_HTTP_ACCESS_PHASE,/*當(dāng)NGX_HTTP_ACCESS_PHASE階段中HTTP模塊的handler處理方法返回不允許訪問的錯(cuò)誤碼時(shí)(實(shí)際是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),這個(gè)階段將負(fù)責(zé)構(gòu)造拒絕服務(wù)的用戶響應(yīng)。所以,這個(gè)階段實(shí)際上用于給NGX_HTTP_ACCESS_PHASE階段收尾*/NGX_HTTP_POST_ACCESS_PHASE,/*這個(gè)階段完全是為了try_files配置項(xiàng)而設(shè)計(jì)的。當(dāng)HTTP請(qǐng)求訪問靜態(tài)文件資源時(shí),try_files配置項(xiàng)可以使這個(gè)請(qǐng)求順序地訪問多個(gè)靜態(tài)文件資源,如果某一次訪問失敗,則繼續(xù)訪問try_files中指定的下一個(gè)靜態(tài)資源。另外,這個(gè)功能完全是在NGX_HTTP_TRY_FILES_PHASE階段中實(shí)現(xiàn)的 */NGX_HTTP_TRY_FILES_PHASE,//用于處理HTTP請(qǐng)求內(nèi)容的階段,這是大部分HTTP模塊最喜歡介入的階段NGX_HTTP_CONTENT_PHASE,/* 處理完請(qǐng)求后記錄日志的階段,例如,ngx_http_log_module模塊就在這個(gè)階段中加入了一個(gè)handler處理方法,使得每個(gè)HTTP請(qǐng)求處理完畢后會(huì)記錄access_log日志 */NGX_HTTP_LOG_PHASE } ngx_http_phases;23.處理方法的返回值,其中包括了HTTP框架已經(jīng)在/src/http/ngx_http_request.h文件中定義好的宏,如下所示:
#define NGX_HTTP_OK 200 #define NGX_HTTP_CREATED 201 #define NGX_HTTP_ACCEPTED 202 #define NGX_HTTP_NO_CONTENT 204 #define NGX_HTTP_PARTIAL_CONTENT 206#define NGX_HTTP_SPECIAL_RESPONSE 300 #define NGX_HTTP_MOVED_PERMANENTLY 301 #define NGX_HTTP_MOVED_TEMPORARILY 302 #define NGX_HTTP_SEE_OTHER 303 #define NGX_HTTP_NOT_MODIFIED 304 #define NGX_HTTP_TEMPORARY_REDIRECT 307#define NGX_HTTP_BAD_REQUEST 400 #define NGX_HTTP_UNAUTHORIZED 401 #define NGX_HTTP_FORBIDDEN 403 #define NGX_HTTP_NOT_FOUND 404 #define NGX_HTTP_NOT_ALLOWED 405 #define NGX_HTTP_REQUEST_TIME_OUT 408 #define NGX_HTTP_CONFLICT 409 #define NGX_HTTP_LENGTH_REQUIRED 411 #define NGX_HTTP_PRECONDITION_FAILED 412 #define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE 414 #define NGX_HTTP_REQUEST_URI_TOO_LARGE 414 #define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE 415 #define NGX_HTTP_RANGE_NOT_SATISFIABLE 416/* The special code to close connection without any response */ #define NGX_HTTP_CLOSE 444 #define NGX_HTTP_NGINX_CODES 494 #define NGX_HTTP_REQUEST_HEADER_TOO_LARGE 494 #define NGX_HTTPS_CERT_ERROR 495 #define NGX_HTTPS_NO_CERT 496#define NGX_HTTP_TO_HTTPS 497 #define NGX_HTTP_CLIENT_CLOSED_REQUEST 499#define NGX_HTTP_INTERNAL_SERVER_ERROR 500 #define NGX_HTTP_NOT_IMPLEMENTED 501 #define NGX_HTTP_BAD_GATEWAY 502 #define NGX_HTTP_SERVICE_UNAVAILABLE 503 #define NGX_HTTP_GATEWAY_TIME_OUT 504 #define NGX_HTTP_INSUFFICIENT_STORAGE 50724.請(qǐng)求的所有信息(如方法、URI、協(xié)議版本號(hào)和頭部等)都可以在傳入的ngx_http_request_t類型參數(shù)r中取得。
typedef struct ngx_http_request_s ngx_http_request_t; struct ngx_http_request_s {...ngx_uint_t method;ngx_uint_t http_version;ngx_str_t request_line;ngx_str_t uri;ngx_str_t args;ngx_str_t exten;ngx_str_t unparsed_uri; //表示沒有進(jìn)行URL解碼的原始請(qǐng)求。ngx_str_t method_name;ngx_str_t http_protocol;u_char *uri_start;u_char *uri_end;u_char *uri_ext;u_char *args_start;u_char *request_start;u_char *request_end;u_char *method_end;u_char *schema_start;u_char *schema_end;... };25.ngx_http_headers_in_t類型的headers_in則存儲(chǔ)已經(jīng)解析過的HTTP頭部。
typedef struct {/* 所有解析過的HTTP頭部都在headers鏈表中,可以使用遍歷鏈表的方法來獲取所有的HTTP頭部。注意:這里headers鏈表的每一個(gè)元素都是ngx_table_elt_t成員*/ngx_list_t headers;/*以下每個(gè)ngx_table_elt_t成員都是RFC1616規(guī)范中定義的HTTP頭部,它們實(shí)際都指向headers鏈表中的響應(yīng)成員。注意,當(dāng)它們?yōu)镹ULL空指針時(shí),表示沒有解析到響應(yīng)的HTTP頭部*/ngx_table_elt_t *host;ngx_table_elt_t *connection;ngx_table_elt_t *if_modified_since;ngx_table_elt_t *if_unmodified_since;ngx_table_elt_t *user_agent;ngx_table_elt_t *referer;ngx_table_elt_t *content_length;ngx_table_elt_t *content_type;ngx_table_elt_t *range;ngx_table_elt_t *if_range;ngx_table_elt_t *transfer_encoding;ngx_table_elt_t *expect;#if (NGX_HTTP_GZIP)ngx_table_elt_t *accept_encoding;ngx_table_elt_t *via; #endifngx_table_elt_t *authorization;ngx_table_elt_t *keep_alive; #if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO)ngx_table_elt_t *x_forwarded_for; #endif#if (NGX_HTTP_REALIP)ngx_table_elt_t *x_real_ip; #endif#if (NGX-HTTP_HEADERS)ngx_table_elt_t *accept;ngx_table_elt_t *accept_language; #endif#if (NGX_HTTP_DAV)ngx_table_elt_t *depth;ngx_table_elt_t *destination;ngx_table_elt_t *overwrite;ngx_table_elt_t *date; #endif/* user和passwd是只有ngx_http_auth_basic_module才會(huì)用到的成員,這里可以忽略*/ngx_str_t user;ngx_str_t passwd;/* cookies是以ngx_array_t數(shù)組存儲(chǔ)的。*/ngx_array_t cookies;//server名稱ngx_str_t server;//根據(jù)ngx_table_elt_t *content_length計(jì)算出的HTTP包體代銷off_t content_length_n;time_t keep_alive_n;/* HTTP連接類型,它的取值范圍是0、NGX_http_CONNECTION_CLOSE或者NGX_HTTP_CONNECTION_KEEP_ALIVE */unsigned connection_type:2;/*以下7個(gè)標(biāo)志位是HTTP框架根據(jù)瀏覽器傳來的"useragent"頭部,它們可用來判斷瀏覽器的類型,值為1時(shí)表示是相應(yīng)的瀏覽器發(fā)來的請(qǐng)求,值為0時(shí)則相反 */unsigned msie:1;unsigned msie6:1;unsigned opera:1;unsigned gecko:1;unsigned chrome:1;unsigned safari:1;unsigned konqueror:1; } ngx_http_headers_in_t;26.HTTP包體的長度有可能非常大,如果視圖一次性調(diào)用并讀取完所有的包體,那么多半會(huì)阻塞Nginx進(jìn)程,HTTP框架提供了一種方法來異步地接受包體:
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler);? ngx_http_read_client_request_body是一個(gè)異步方法,調(diào)用它只是說明要求Nginx開始接收請(qǐng)求的包體,并不表示是否已經(jīng)接收完,當(dāng)接收完所有的包體內(nèi)容后,post_handler指向的回調(diào)方法會(huì)被調(diào)用。因此,即使在調(diào)用了ngx_http-read_client_request_body方法后它已經(jīng)返回,也無法確定這時(shí)是否已經(jīng)調(diào)用過post_handler指向的方法。換句話說,ngx_http_read_client_request_body返回時(shí)既有可能已經(jīng)接收完請(qǐng)求中所有的包體(假設(shè)包體的長度很小),也有可能還沒開始接受包體。
27.HTTP框架提供的發(fā)送HTTP頭部的方法如下所示:
ngx_int_t ngx_http_send_header(ngx_http_request_t *r);28.headers_out的結(jié)構(gòu)類型ngx_http_headers_out_t:
typedef struct {//待發(fā)送的HTTP頭部鏈表,與headers_in中的headers成員類似ngx_list_t headers;/*響應(yīng)中的狀態(tài)值,如200表示成功。*/ngx_str_t status;//響應(yīng)的狀態(tài)行,如"HTTP/1.1 201 CREATED"ngx_str_t status_line;/*以下成員(包括ngx_table_elt_t)都是RFC1616規(guī)范中定義的HTTP頭部,設(shè)置后,ngx_http_header_filter_module過濾模塊可以把它們加到待發(fā)送的網(wǎng)絡(luò)包中*/ngx_table_elt_t *server;ngx_table_elt_t *date;ngx_table_elt_t *content_length;ngx_table_elt_t *content_encoding;ngx_table_elt_t *location;ngx_table_elt_t *refresh;ngx_table_elt_t *last_modified;ngx_table_elt_t *content_range;ngx_table_elt_t *accept_ranges;ngx_table_elt_t *www_authenticate;ngx_table_elt_t *expires;ngx_table_elt_t *etag;ngx_str_t *override_charset;/* 可以調(diào)用ngx_http_set_content_type(r)方法幫助我們?cè)O(shè)置Content-Type頭部,這個(gè)方法會(huì)根據(jù)URI中的文件擴(kuò)展名餅對(duì)應(yīng)著mime.type來設(shè)置Content-Type值*/size_t content_type_len; ngx_str_t content_type;ngx_str_t charset;u_char *content_type_lowcase;ngx_uint_t content_type_hash;ngx_array_t cache_control;/*在這里指定過content_length_n后,不用再次到ngx_table_elt_t *content_length中設(shè)置響應(yīng)長度*/off_t content_length_n;time_t date_time;time_t last_modified_time; } ngx_http_headers_out_t;? ?ngx_http_send_header方法會(huì)首先調(diào)用所有的HTTP過濾模塊共同處理headers_out中定義的HTTP響應(yīng)頭部,全部處理完畢后才會(huì)序列化為TCP字符流發(fā)送到客戶端。
28.注意:在向用戶發(fā)送響應(yīng)包體時(shí),必須牢記Nginx是全異步的服務(wù)器,也就是說,不可以在進(jìn)程的棧里分配內(nèi)存并將其作為包體發(fā)送。當(dāng)ngx_http_output_filter方法返回時(shí),可能由于TCP連接上的緩沖區(qū)還不可寫,所以導(dǎo)致ngx_buf_t緩沖區(qū)指向的內(nèi)存還沒有發(fā)送,可這時(shí)方法返回已把控制權(quán)交給Nginx了,又會(huì)導(dǎo)致棧里的內(nèi)存被釋放,最后就會(huì)造成內(nèi)存越界錯(cuò)誤。因此,在發(fā)送響應(yīng)包體時(shí),盡量將ngx_buf_t中的pos指針指向從內(nèi)存池里分配的內(nèi)存。
29.經(jīng)典的"Hello World"示例
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) {//必須是GET或者HEAD方法,否則返回405 Not Allowedif (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {return NGX_HTTP_NOT_ALLOWED;}//丟棄請(qǐng)求中的包體ngx_int_t rc = ngx_http_discard_request_body(r);if (rc != NGX_OK) {return rc;}/* 設(shè)置返回的Content-Type.注意,ngx_str_t有一個(gè)很方便的初始化宏ngx_string,它可以把ngx_str_t的data和len成員都設(shè)置好 */ngx_str_t type = ngx_string("text/plain");//返回的包體內(nèi)容ngx_str_t type = ngx_string("Hello World!");//設(shè)置返回狀態(tài)碼r->headers_out.status = NGX_HTTP_OK;//響應(yīng)包是有包體內(nèi)容的,需要設(shè)置Content-Lenght的長度r->headers_out.content_length_n = response.len;//設(shè)置Content-Typer->headers_out.content_type = type;//發(fā)送HTTP頭部rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {return rc;}//構(gòu)造ngx_buf_t結(jié)構(gòu)體準(zhǔn)備發(fā)送包體ngx_buf_t *b;b = ngx_create_temp_buf(r->pool, response.len);if (b == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}//將Hello World復(fù)制到ngx_buf_t指向的內(nèi)存中ngx_memcpy(b->pos, response.data, response.len);//注意,一定要設(shè)置好last指針b->last = b->pos + response.len;//聲明這是最后一塊緩沖區(qū)b->last_buf = 1;//構(gòu)造發(fā)送時(shí)的ngx_chain_t結(jié)構(gòu)體ngx_chain_t out;//賦值ngx_buf_tout.buf = b;//設(shè)置next為NULLout.next = NULL;/*最后一步為發(fā)送包體,發(fā)送結(jié)束后HTTP框架會(huì)調(diào)用ngx_http_finalize_request方法結(jié)束請(qǐng)求 */return ngx_http_output_filter(r, &out); }30.ngx_file_t的結(jié)構(gòu)如下:
typedef struct ngx_file_s ngx_file_t; struct ngx_file_s {//文件句柄描述符ngx_fd_t fd;//文件名稱ngx_str_t name;//文件大小等資源信息,實(shí)際就是Linux系統(tǒng)定義的stat結(jié)構(gòu)ngx_file_info_t info;/*該偏移量告訴Nginx現(xiàn)在處理到文件何處了,一般不用設(shè)置它,Nginx框架會(huì)根據(jù)當(dāng)前發(fā)送狀態(tài)設(shè)置它*/off_t offset;//當(dāng)前文件系統(tǒng)偏移量,一般不用設(shè)置它off_t sys_offset;//日志對(duì)象,相關(guān)的日志會(huì)輸出到log指定的日志文件中ngx_log_t *log;//目前未使用unsigned valid_info:1;//與配置文件中的directio配置項(xiàng)相對(duì)應(yīng),在發(fā)送大文件時(shí)可以設(shè)為1unsigned directio:1; };? Nginx不只對(duì)stat數(shù)據(jù)結(jié)構(gòu)做了封裝,對(duì)于由操作系統(tǒng)中獲取文件信息的stat方法,Nginx也使用一個(gè)宏進(jìn)行了簡(jiǎn)單的封裝,如下:
#define ngx_file_info(file, sb) stat((const char *) file, sb)? ?之后必須要設(shè)置Content-Length頭部:
r->headers_out.content_length_n = b->file->info.st_size;? ?還需要設(shè)置ngx_buf_t緩沖區(qū)的file_pod和file_last:
b->file_pos = 0; b->file_last = b->file->info.st_size;? ?這里告訴Nginx從文件的file_pos偏移量開始發(fā)送文件,一直到達(dá)file_last偏移量處截止。
31. HTTP框架定義了3個(gè)級(jí)別的配置main、srv、loc,分別表示直接出現(xiàn)在http{}、server{}、location{}塊內(nèi)的配置項(xiàng)。當(dāng)nginx.conf中出現(xiàn)http{}時(shí),HTTP框架會(huì)接管配置文件中http{}塊內(nèi)的配置項(xiàng)解析。當(dāng)遇到http{...}配置塊時(shí),HTTP框架會(huì)調(diào)用所有HTTP模塊可能實(shí)現(xiàn)的create_main_conf、create_srv_conf、create_loc_conf方法生成存儲(chǔ)main級(jí)別配置參數(shù)的結(jié)構(gòu)體;在遇到server{...}塊時(shí)會(huì)再次調(diào)用所有HTTP模塊的create_srv_conf、create_loc_conf回調(diào)方法生成存儲(chǔ)srv級(jí)別配置參數(shù)的結(jié)構(gòu)體;在遇到location{...}時(shí)則會(huì)再次調(diào)用create_loc_conf回調(diào)方法生成存儲(chǔ)loc級(jí)別配置參數(shù)的結(jié)構(gòu)體。因此,實(shí)現(xiàn)這3個(gè)回調(diào)方法的意義是不同的。
32.設(shè)定配置項(xiàng)的解析方式
? ? ngx_command_t結(jié)構(gòu)詳解
struct ngx_command_s {ngx_str_t name;ngx_uint_t type;char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);ngx_uint_t conf;ngx_uint_t offset;void *post; };? ? (1) ngx_str_t name
? ? ? ? 其中,name是配置項(xiàng)名稱。
? ? (2) ngx_uint_t type
? ? ? ? 其中,type決定這個(gè)配置項(xiàng)可以在哪些塊(如http、server、location、if、upstream塊等)中出現(xiàn),以及可以攜帶的參數(shù)類型和個(gè)數(shù)等。
? ? ? ? ? ngx_command_t結(jié)構(gòu)體中type成員的取值及其意義
| type類型 | type取值 | 意義 |
| 處理配置項(xiàng)時(shí)獲取當(dāng)前配置塊的方式 | NGX_DIRECT_CONF | 一般由NGX_CORE_MODULE類型的核心模塊使用,僅與下面的NGX_MAIN_CONF同時(shí)設(shè)置,表示模塊需要解析不屬于任何{}內(nèi)的全局配置項(xiàng)。它實(shí)際上會(huì)指定set方法里的第3個(gè)參數(shù)conf的值,使之指向每個(gè)模塊解析全局配置項(xiàng)的配置結(jié)構(gòu)體(表下面解釋) |
| NGX_ANY_CONF | 目前未使用,設(shè)置與否均無意義 | |
| 配置項(xiàng)可以在哪些{}配置塊中出現(xiàn) | NGX_MAIN_CONF | 配置項(xiàng)可以出現(xiàn)在全局配置中,即不屬于任何{}配置塊 |
| NGX_EVENT_CONF | 配置項(xiàng)可以出現(xiàn)在events{}塊內(nèi) | |
| NGX_MAIL_MAIN_CONF | 配置項(xiàng)可以出現(xiàn)在mail{}塊或者imap{}塊內(nèi) | |
| NGX_MAIL_SRV_CONF | 配置項(xiàng)可以出現(xiàn)在server{}塊內(nèi),然而該server{}塊必須屬于mail{}塊或者imap{}塊 | |
| NGX_HTTP_MAIN_CONF | 配置項(xiàng)可以出現(xiàn)在http{}塊內(nèi) | |
| NGX_HTTP_SRV_CONF | 配置項(xiàng)可以出現(xiàn)在server{}塊內(nèi),然而該server塊必須屬于http{}內(nèi) | |
| NGX_HTTP_LOC_CONF | 配置項(xiàng)可以出現(xiàn)在location{}塊內(nèi),然而該location塊必須屬于http{}內(nèi) | |
| NGX_HTTP_UPS_CONF | 配置項(xiàng)可以出現(xiàn)在upstream{}塊內(nèi),然而該upstream塊必須屬于http{}塊 | |
| NGX_HTTP_SIF_CONF | 配置項(xiàng)可以出現(xiàn)在server塊內(nèi)的if{}塊中。目前僅有rewrite模塊會(huì)使用,該if塊必須屬于http{}塊 | |
| NGX_HTTP_LIF_CONF | 配置項(xiàng)可以出現(xiàn)在location塊內(nèi)的if{}塊中。目前僅有rewrite模塊會(huì)使用,該if塊必須屬于http{}塊 | |
| NGX_HTTP_LMT_CONF | 配置項(xiàng)可以出現(xiàn)在limit_except{}塊內(nèi),然而該limit_except塊必須屬于http{}塊 | |
| 限制配置項(xiàng)的參數(shù)個(gè)數(shù) | NGX_CONF_NOARGS | 配置項(xiàng)不攜帶任何參數(shù) |
| NGX_CONF_TAKE1 | 配置項(xiàng)必須攜帶1個(gè)參數(shù) | |
| NGX_CONF_TAKE2 | 配置項(xiàng)必須攜帶2個(gè)參數(shù) | |
| NGX_CONF_TAKE3 | 配置項(xiàng)必須攜帶3個(gè)參數(shù) | |
| NGX_CONF_TAKE4 | 配置項(xiàng)必須攜帶4個(gè)參數(shù) | |
| NGX_CONF_TAKE5 | 配置項(xiàng)必須攜帶5個(gè)參數(shù) | |
| NGX_CONF_TAKE6 | 配置項(xiàng)必須攜帶6個(gè)參數(shù) | |
| NGX_CONF_TAKE7 | 配置項(xiàng)必須攜帶7個(gè)參數(shù) | |
| NGX_CONF_TAKE12 | 配置項(xiàng)可以攜帶1個(gè)參數(shù)或2個(gè)參數(shù) | |
| NGX_CONF_TAKE13 | 配置項(xiàng)可以攜帶1個(gè)參數(shù)或3個(gè)參數(shù) | |
| NGX_CONF_TAKE23 | 配置項(xiàng)可以攜帶2個(gè)參數(shù)或3個(gè)參數(shù) | |
| NGX_CONF_TAKE123 | 配置項(xiàng)可以攜帶1~3個(gè)參數(shù) | |
| NGX_CONF_TAKE1234 | 配置項(xiàng)可以攜帶1~4個(gè)參數(shù) | |
| 限制配置項(xiàng)后的參數(shù)出現(xiàn)的形式 | NGX_CONF_ARGS_NUMBERS | 目前未使用,無意義 |
| NGX_CONF_BLOCK | 配置項(xiàng)定義了一種新的{}塊。例如,http、server、location等配置,它們的ttype都必須定義為NGX_CONF_BLOCK | |
| NGX_CONF_ANY | 不驗(yàn)證配置項(xiàng)攜帶的參數(shù)個(gè)數(shù) | |
| NGX_CONF_FLAG | 配置項(xiàng)攜帶的參數(shù)只能是1個(gè),并且參數(shù)的值只能是on或者off | |
| NGX_CONF_1MORE | 配置項(xiàng)攜帶的參數(shù)個(gè)數(shù)必須超過1個(gè) | |
| NGX_CONF_2MORE | 配置項(xiàng)攜帶的參數(shù)個(gè)數(shù)必須超過2個(gè) | |
| NGX_CONF_MULTI | 表示當(dāng)前配置項(xiàng)可以出現(xiàn)在任意塊中(包括不屬于任何塊的全局配置),它僅用于配合其他配置項(xiàng)使用。type中未加入NGX_CONF_MULTI時(shí),如果一個(gè)配置項(xiàng)出現(xiàn)在type成員未標(biāo)明的配置塊中,那么Nginx會(huì)認(rèn)為該配置項(xiàng)非法,最后將導(dǎo)致Nginx啟動(dòng)失敗。但如果type中加入了NGX_CONF_MULTI,則認(rèn)為該配置項(xiàng)一定是合法的,然而又會(huì)有兩種不同的結(jié)果: a.如果配置項(xiàng)出現(xiàn)在type指示的塊中,則會(huì)調(diào)用set方法解析配置項(xiàng);b.如果配置項(xiàng)沒有出現(xiàn)在type指示的塊中,則不對(duì)該配置項(xiàng)做任何處理。因此,NGX_CONF_MULTI會(huì)使得配置項(xiàng)出現(xiàn)在未知塊中時(shí)不會(huì)出錯(cuò)。目前,還沒有官方模塊使用過NGX_CONF_MULTI. |
解釋:每個(gè)進(jìn)程總都有一個(gè)唯一的ngx_cycle_t核心結(jié)構(gòu)體,它有一個(gè)成員conf_ctx維護(hù)著所有模塊的配置結(jié)構(gòu)體,其類型是void ****。conf_ctx意義為首先指向一個(gè)成員皆為指針的數(shù)組,其中每個(gè)成員指針又指向另外一個(gè)成員皆為指針的數(shù)組,第2個(gè)子數(shù)組中的成員指針才會(huì)指向各模塊生成的配置結(jié)構(gòu)體。這正是為了事件模塊、http模塊、mail模塊而設(shè)計(jì)的,這有利于不同于NGX_CORE_MODULE類型的特定模塊解析配置項(xiàng)。然而,NGX_CORE_MODULE類型的核心模塊解析配置項(xiàng)時(shí),配置項(xiàng)一定是全局的,不會(huì)從屬于任何{}配置塊的,它不需要上述這種雙數(shù)組設(shè)計(jì)。解析標(biāo)識(shí)為NGX_DIRECT_CONF類型的配置項(xiàng)時(shí),會(huì)把void ****類型的conf_ctx強(qiáng)制轉(zhuǎn)換為void **,也就是說,此時(shí),在conf_ctx指向的指針數(shù)組中,每個(gè)成員指針不再指向其他數(shù)組,直接指向核心模塊生成的配置結(jié)構(gòu)體。因此,NGX_DIRECT_CONF僅由NGX_CORE_MODULE類型的核心模塊使用,而且配置項(xiàng)只應(yīng)該出現(xiàn)在全局配置中。
? ?c. char*(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
? ? 預(yù)設(shè)的14個(gè)配置項(xiàng)解析方法
| 預(yù)設(shè)方法名 | 行為 |
| ngx_conf_set_flag_slot | 如果ngxin.conf文件中某個(gè)配置項(xiàng)的參數(shù)是on或者off(即希望配置項(xiàng)表達(dá)打開或者關(guān)閉某個(gè)功能的意思),而且在Nginx模塊的代碼中使用ngx_flag_t變量來保存這個(gè)配置項(xiàng)的參數(shù),就可以將set回調(diào)方法設(shè)為ngx_conf_set_flag_slot。當(dāng)nginx.conf文件中參數(shù)是on時(shí),代碼中的ngx_flag_t類型變量將設(shè)為1,參數(shù)為off時(shí)則設(shè)為0 |
| ngx_conf_set_str_slot | 如果配置項(xiàng)后只有1個(gè)參數(shù),同時(shí)在代碼中我們希望用ngx_str_t類型的變量來保存這個(gè)配置項(xiàng)的參數(shù),則可以使用ngx_conf_set_str_slot方法 |
| ngx_conf_set_str_array_slot | 如果這個(gè)配置項(xiàng)會(huì)出現(xiàn)多次,每個(gè)配置項(xiàng)后面都跟著1個(gè)參數(shù),而在程序中我們希望僅用一個(gè)ngx_array_t動(dòng)態(tài)數(shù)組來存儲(chǔ)所有的參數(shù),且數(shù)組中的每個(gè)參數(shù)都以ngx_str_t來存儲(chǔ),那么預(yù)設(shè)的ngx_conf_set_str_array_slot方法可以幫我們做到 |
| ngx_conf_set_keyval_slot | 與ngx_conf_set_str_array_slot類似,也使用用一個(gè)ngx_array_t數(shù)組來存儲(chǔ)所有同名配置項(xiàng)的參數(shù)。只是每個(gè)配置項(xiàng)的參數(shù)不再只有1個(gè),而必須是兩個(gè),且以“配置項(xiàng)名 關(guān)鍵字 值;”的形式出現(xiàn)在nginx.conf文件中,同時(shí),ngx_conf_set_keyval_slot將把這些配置項(xiàng)轉(zhuǎn)化為數(shù)組,其中每個(gè)元素都存儲(chǔ)著key/value鍵值對(duì)。 |
| ngx_conf_set_num_slot | 配置項(xiàng)后必須攜帶1個(gè)參數(shù),且只能是數(shù)字。存儲(chǔ)這個(gè)參數(shù)的變量必須是整形 |
| ngx_conf_set_size_slot | 配置項(xiàng)后必須攜帶1個(gè)參數(shù),表示空間大小,可以是一個(gè)數(shù)字,這時(shí)表示字節(jié)數(shù)(Byte)。如果數(shù)字后跟著k或者K,就表示Kilobyt,1KB=1024B;如果數(shù)字后跟著m或者M(jìn),就表示Megabyte,1MB=1024KB。ngx_conf_set_size_slot解析后將把配置項(xiàng)后的參數(shù)轉(zhuǎn)化成以字節(jié)數(shù)為單位的數(shù)字 |
| ngx_conf_set_off_slot | 配置項(xiàng)后必須攜帶1個(gè)參數(shù),表示空間上的偏移量。它與設(shè)置的參數(shù)非常類似,其參數(shù)是一個(gè)數(shù)字時(shí)表示Byte,也可以在后面加單位,但與ngx_conf_set_size_slot不同的是,數(shù)字后面的單位不僅可以是k或者K、m或者M(jìn),還可以是g或者G,這時(shí)表示Gigabyte,1GB=1024MB,ngx_conf_set_off_slot解析后將把配置項(xiàng)后的參數(shù)轉(zhuǎn)化成以字節(jié)數(shù)為單位的數(shù)字 |
| ngx_conf_set_msec_slot | 配置項(xiàng)后必須攜帶1個(gè)參數(shù),表示時(shí)間。這個(gè)參數(shù)可以在數(shù)字后面加單位,如果單位為s或者沒有任何單位,那么這個(gè)數(shù)字表示秒;如果單位為m,則表示分鐘,1m=60s;如果單位為h,則表示小時(shí),1h=60m;如果單位為d,則表示天,1d=24h;如果單位為w,則表示周,1w=7d;如果單位為M,則表示月,1M=30d;如果單位為y,則表示年,1y=265d。ngx_conf_set_msec_slot解析后將把配置項(xiàng)后的參數(shù)轉(zhuǎn)化成以毫秒為單位的數(shù)字 |
| ngx_conf_set_sec_slot | 與ngx_conf_set_msec_slot非常類似,唯一的區(qū)別是ngx_conf_set_msec_slot解析后將把配置項(xiàng)后的參數(shù)轉(zhuǎn)化成以毫秒為單位的數(shù)字,而ngx_conf_set_sec_slot解析后會(huì)把配置項(xiàng)后的參數(shù)轉(zhuǎn)化成以秒為單位的數(shù)字 |
| ngx_conf_set_bufs_slot | 配置項(xiàng)后必須攜帶一兩個(gè)參數(shù),第1個(gè)參數(shù)是數(shù)字,第2個(gè)參數(shù)表示空間大小。例如:"gzip_buffers 4 8k;"(通常用來表示有多少個(gè)ngx_buf_t緩沖區(qū)),其中第1個(gè)參數(shù)不可以攜帶任何單位,第2個(gè)參數(shù)不帶任何單位時(shí)表示Byte,如果yik或者K作為單位,則表示Kilobyte,如果以m或者M(jìn)作為單位,則表示Megabyte。ngx_conf_set_bufs_slot解析后會(huì)把配置項(xiàng)后的兩個(gè)參數(shù)轉(zhuǎn)化成ngx_bufs_t結(jié)構(gòu)體下的兩個(gè)成員,這個(gè)配置項(xiàng)對(duì)應(yīng)于Nginx最喜歡用的多緩沖區(qū)的解決方案(如接受連接對(duì)端發(fā)來的TCP流) |
| ngx_conf_set_enum_slot | 配置項(xiàng)后必須攜帶1個(gè)參數(shù),其取值范圍必須是我們?cè)O(shè)定好的字符串之一(就像C語言中的枚舉一樣),首先,我們要用ngx_conf_enum_t結(jié)構(gòu)定義配置項(xiàng)的取值范圍,并設(shè)定每個(gè)值對(duì)應(yīng)的序列號(hào),然后,ngx_conf_set_enum_slot將會(huì)把配置項(xiàng)參數(shù)轉(zhuǎn)化為對(duì)應(yīng)的序列號(hào)。 |
| ngx_conf_set_bitmask_slot | 與ngx_conf_set_bitmask_slot類似,配置項(xiàng)后必須攜帶1個(gè)參數(shù),其取值范圍必須是設(shè)定好的字符串只注意。首先,我們要用ngx_conf_bitmask_t結(jié)構(gòu)定義配置項(xiàng)的取值范圍,并設(shè)定每個(gè)值對(duì)應(yīng)的比特位。注意,每個(gè)值所對(duì)應(yīng)的比特位都要不同,然后ngx_conf_set_bitmask_slot將會(huì)把配置項(xiàng)參數(shù)轉(zhuǎn)化為對(duì)應(yīng)的比特位 |
| ngx_conf_set_access_slot | 這個(gè)方法用于設(shè)置目錄或者文件的讀寫權(quán)限。配置項(xiàng)后可以攜帶1~3個(gè)參數(shù),可以是如下形式:user:rw group:rw all:rw。注意,它的意義與Linux上文件或者目錄的權(quán)限意義視一致的,但是user/group/all后面的權(quán)限只可以設(shè)為rw(讀/寫)或者r(只讀),不可以有其他任何形式,如w或者rx等。ngx_conf_set_access_slot將會(huì)把這些參數(shù)轉(zhuǎn)化為一個(gè)整形。 |
| ngx_conf_set_path_slot | 這個(gè)方法用于設(shè)置路徑,配置項(xiàng)后必須攜帶1個(gè)參數(shù),表示1個(gè)有意義的路徑。ngx_conf_set_path_slot將會(huì)把參數(shù)轉(zhuǎn)化為ngx_path_t結(jié)構(gòu)。 |
? ? d.ngx_uint_t conf
? ? ? ?conf用于指示配置項(xiàng)所處內(nèi)存的相對(duì)偏移位置,僅在type中沒有設(shè)置NGX_DIRECT_CONF和NGX_MAIN_CONF時(shí)才會(huì)生效。對(duì)于HTTP模塊,conf是必須要設(shè)置的,它的取值范圍如下:
| conf在HTTP模塊中的取值 | 意義 |
| NGX_HTTP_MAIN_CONF_OFFSET | 使用create_main_conf方法產(chǎn)生的結(jié)構(gòu)體來存儲(chǔ)解析出的配置項(xiàng)參數(shù) |
| NGX_HTTP_SRV_CONF_OFFSET | 使用create_srv_conf方法產(chǎn)生的結(jié)構(gòu)體來存儲(chǔ)解析出的配置項(xiàng)參數(shù) |
| NGX_HTTP_LOC_CONF_OFFSET | 使用create_loc_conf方法產(chǎn)生的結(jié)構(gòu)體來存儲(chǔ)解析出的配置項(xiàng)參數(shù) |
? ? e.ngx_uint_t offset
? ? ? offset表示當(dāng)前配置項(xiàng)在整個(gè)存儲(chǔ)配置項(xiàng)啊的結(jié)構(gòu)體中的偏移位置(以字節(jié)(Byte)為單位)。
? ? f.void *post
? ? ?一般置為NULL。
33.解析HTTP配置的流程
? ??
? ?(1) 主循環(huán)是指Nginx進(jìn)程的主循環(huán),主循環(huán)只有調(diào)用配置文件解析器才能解析nginx.conf文件(這里的"主循環(huán)"是指解析全部配置文件的循環(huán)代碼)。
? ?(2) 當(dāng)發(fā)現(xiàn)配置文件中含有http{}關(guān)鍵字時(shí),HTTP框架開始啟動(dòng),這一過程見ngx_http_block方法。
? ?(3) HTTP框架會(huì)初始化所有HTTP模塊的序列號(hào),并創(chuàng)建3個(gè)數(shù)組用于存儲(chǔ)所有HTTP模塊的create_main_conf、create_srv_conf、create_loc_conf方法返回的指針地址,并把這3個(gè)數(shù)組的地址保存到ngx_http_conf_ctx_t結(jié)構(gòu)中。
? ?(4) 調(diào)用每個(gè)HTTP模塊(當(dāng)然也包括例子中的mytest模塊)的create_main_conf、create_srv_conf、create_loc_conf(如果實(shí)現(xiàn)的話)方法。
? ?(5) 把各HTTP模塊上述3個(gè)方法的地址依次保存到ngx_http_conf_ctx_t結(jié)構(gòu)體的3個(gè)數(shù)組中。
? ?(6) 調(diào)用每個(gè)HTTP模塊的preconfiguration方法(如果實(shí)現(xiàn)的話).
? ?(7) 注意,如果preconfiguration返回失敗,那么Nginx進(jìn)程將會(huì)停止。
? ?(8) HTTP框架開始循環(huán)解析nginx.conf文件中http{...}里面的所有配置項(xiàng),注意,這個(gè)過程到第19步才會(huì)返回。
? ?(9) 配置文件解析器在檢測(cè)到1個(gè)配置項(xiàng)后,會(huì)遍歷所有的HTTP模塊,檢查它們的ngx_command_t數(shù)組中的name項(xiàng)是否與配置項(xiàng)名相同。
? ? (10) 如果找到有1個(gè)HTTP模塊對(duì)這個(gè)配置項(xiàng)感興趣,就調(diào)用ngx_command_t結(jié)構(gòu)中的set方法來處理。
? ? (11) set方法返回是否處理成功。如果處理失敗,那么Nginx進(jìn)程會(huì)停止。
? ? (12) 配置文件解析器繼續(xù)監(jiān)測(cè)配置項(xiàng)。如果發(fā)現(xiàn)server{...}配置項(xiàng),就會(huì)調(diào)用ngx_http_core_module模塊來處理。因?yàn)閚gx_http_core_module模塊明確表示希望處理server{}塊下的配置項(xiàng)。注意,這次調(diào)用到第18步才會(huì)返回。
? ? (13) ngx_http_core_module模塊在解析server{...}之前,也會(huì)如第3步一樣建立ngx_http_conf_ctx_t結(jié)構(gòu),并建立數(shù)組保存所有HTTP模塊返回的指針地址。然后,它會(huì)調(diào)用每個(gè)HTTP模塊的create_srv_conf、create_loc_conf方法(如果實(shí)現(xiàn)的話).
? ? (14) 將上一步各HTTP模塊返回的指針地址保存到ngx_http_conf_ctx_t對(duì)應(yīng)的數(shù)組中。
? ? (15) 開始調(diào)用配置文件解析器來處理server{...}里面的配置項(xiàng),注意,這個(gè)過程在第17步返回。
? ? (16) 繼續(xù)重復(fù)第9步的過程,遍歷nginx.conf中當(dāng)前server{...}內(nèi)的所有配置項(xiàng)。
? ? (17)配置文件解析器繼續(xù)解析配置項(xiàng),發(fā)現(xiàn)當(dāng)前server塊已經(jīng)遍歷到尾部,說明server塊內(nèi)的配置項(xiàng)處理完畢,返回ngx_http_core_module模塊。
? ? (18) http core模塊也處理完server配置項(xiàng)了,返回至配置文件解析器繼續(xù)解析后面的配置項(xiàng)。
? ? (19) 配置文件解析器繼續(xù)解析配置項(xiàng),這時(shí)發(fā)現(xiàn)處理到了http{...}的尾部,返回給HTTP框架繼續(xù)處理。
? ? (20) 在第3步和第13步,以及我們沒有列出來的某些步驟中(如發(fā)現(xiàn)其他server塊或者location塊),都創(chuàng)建了ngx_http_conf_ctx_t結(jié)構(gòu)體,這時(shí)將開始調(diào)用merge_srv_conf、merge_loc_conf等方法合并這些不同塊(http、server、location)中每個(gè)HTTP模塊分配的數(shù)據(jù)結(jié)構(gòu)。
? ? (21) HTTP框架處理完畢http配置項(xiàng)(也就是ngx_command_t結(jié)構(gòu)中的set回調(diào)方法處理完畢),返回給配置文件解析器繼續(xù)處理其他http{...}外的配置項(xiàng)。
? ? (22) 配置文件解析器處理完所有配置項(xiàng)后會(huì)告訴Nginx主循環(huán)配置項(xiàng)解析完畢,這時(shí)Nginx才會(huì)啟動(dòng)Web服務(wù)器。
34.http塊與server塊下的ngx_http_conf_ctx_t所指向的內(nèi)存間的關(guān)系
? ?
35.合并配置項(xiàng)過程的活動(dòng)圖,主要包含四大部分內(nèi)容:
? ?(1) 如果HTTP模塊實(shí)現(xiàn)了merge_srv_conf方法,就將http{...}塊下create_srv_conf生成的結(jié)構(gòu)體與遍歷每一個(gè)server{...}配置塊下的結(jié)構(gòu)體做merge_srv_conf操作;
? ?(2) 如果HTTP模塊實(shí)現(xiàn)了merge_loc_conf方法,就將http{...}塊下create_loc_conf生成的結(jié)構(gòu)體與嵌套的每一個(gè)server{...}配置塊下生成的結(jié)構(gòu)體做merge_loc_conf操作;
? ?(3) 如果HTTP模塊實(shí)現(xiàn)了merge_loc_conf方法,就將server{...}塊下create_loc_conf生成的結(jié)構(gòu)體與嵌套的每一個(gè)location{...}配置塊下create_loc_conf生成的數(shù)據(jù)結(jié)構(gòu)做merge_loc_conf操作;
? ?(4) 如果HTTP模塊實(shí)現(xiàn)了merge_loc_conf方法,就將location{...}塊下create_loc_conf生成的結(jié)構(gòu)體與繼續(xù)嵌套的每一個(gè)location{...}配置塊下create_loc_conf生成的數(shù)據(jù)結(jié)構(gòu)做merge_loc_conf操作。
? ?
? 上圖中包括4重循環(huán),第1層(最外層)遍歷所有的HTTP模塊,第2層遍歷所有的server{...}配置塊,第3層是遍歷某個(gè)server{}塊中嵌套的所有l(wèi)ocation{...}塊,第4層遍歷某個(gè)location{}塊中繼續(xù)嵌套的所有l(wèi)ocation塊(實(shí)際上,它會(huì)一直遞歸下去以解析可能被層層嵌套的location塊).
36.請(qǐng)求的上下文
? ? 在Nginx中,上下文有很多種含義。HTTP框架定義的這個(gè)上下文是針對(duì)于HTTP請(qǐng)求的,而且一個(gè)HTTP請(qǐng)求對(duì)應(yīng)于每一個(gè)HTTP模塊都可以有一個(gè)獨(dú)立的上下文結(jié)構(gòu)體(并不是一個(gè)請(qǐng)求的上下文由所有HTTP模塊共用)。
37.ngx_http_get_module_ctx和ngx_http_set_ctx這兩個(gè)宏可以完成HTTP上下文的設(shè)置和使用。
#define ngx_http_get_module_ctx(r, module) (r)->ctx[module.ctx_index] #define ngx_http_set_ctx(r, c, module) r->ctx[module.ctx_index] = c;? ?當(dāng)請(qǐng)求第1次進(jìn)入mytest模塊處理時(shí),創(chuàng)建ngx_http_mytest_ctx_t結(jié)構(gòu)體,并設(shè)置到這個(gè)請(qǐng)求的上下文中。
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) {//首先調(diào)用ngx_http_get_module_ctx宏來獲取上下文結(jié)構(gòu)體ngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);//如果之前沒有設(shè)置過上下文,那么應(yīng)當(dāng)返回NULL。if (myctx == NULL) {/*必須在當(dāng)前請(qǐng)求的內(nèi)存池r->pool中分配上下文結(jié)構(gòu)體,這樣請(qǐng)求結(jié)束時(shí)結(jié)構(gòu)體占用的內(nèi)存才會(huì)釋放*/myctx = ngx_palloc(r->pool, sizeof(ngx_http_mytest_ctx_t));if (myctx == NULL) {return NGX_ERROR;}//將剛分配的結(jié)構(gòu)體設(shè)置到當(dāng)前請(qǐng)求的上下文中ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);}//之后可以任意使用myctx這個(gè)上下文結(jié)構(gòu)體... }38.模塊在處理任何一個(gè)請(qǐng)求時(shí)都有ngx_http_request_t結(jié)構(gòu)的對(duì)象r,而請(qǐng)求r中又有一個(gè)ngx_http_upstream_t類型的成員upstream.
typedef struct ngx_http_request_s ngx_http_request_t; struct ngx_http_request_s {...ngx_http_upstream_t *upstream;... };39.啟動(dòng)upstream的流程圖
? ?
40.upstream執(zhí)行的一般流程
??
41.ngx_http_upstream_t結(jié)構(gòu)體
typedef struct ngx_http_upstream_s ngx_http_upstream_t; struct ngx_http_upstream_s {.../* request_bufs決定發(fā)送什么樣的請(qǐng)求給上游服務(wù)器,在實(shí)現(xiàn)create_request方法是需要設(shè)置它 */ngx_chain_t *request_bufs;//upstream訪問時(shí)的所有限制性參數(shù)ngx_http_upstream_conf_t *conf;//通過resolved可以直接指定上游服務(wù)器地址ngx_http_upstream_resolved_t *resolved;/* buffer成員存儲(chǔ)接收自上游服務(wù)器發(fā)來的響應(yīng)內(nèi)容,由于它會(huì)被復(fù)用,所以具有下例多種意義:* a) 在使用process_header方法解析上游響應(yīng)的包頭時(shí),buffer中將會(huì)保存完整的響應(yīng)包頭;* b) 當(dāng)下面的buffering成員為1,而且此時(shí)upstream是向下游轉(zhuǎn)發(fā)上游的包體時(shí),buffer沒有意義;* c) 當(dāng)buffering標(biāo)志位位0時(shí),buffer緩沖區(qū)會(huì)被用于反復(fù)地接收上游的包體,進(jìn)而向下游轉(zhuǎn)發(fā);* d) 當(dāng)upstream并不用于轉(zhuǎn)發(fā)上游包體時(shí),buffer會(huì)被用于反復(fù)接收上游的包體,HTTP模塊實(shí)現(xiàn)的input_filter方法需要關(guān)注它*/ngx_buf_t buffer;//構(gòu)造發(fā)往上游服務(wù)器的請(qǐng)求內(nèi)容ngx_int_t (*create_request)(ngx_http_request_t *r);/* 銷毀upstream請(qǐng)求時(shí)調(diào)用 */void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);//5個(gè)可選的回調(diào)方法ngx_int_t (*input_filter_init)(void *data);ngx_int_t (*input_filter)(void *data, ssize_t bytes);ngx_int_t (*reinit_request)(ngx_http_request_t *r);void (*abort_request)(ngx_http_request_t *r);ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix);//是否基于SSL協(xié)議訪問上游服務(wù)器unsigned ssl:1;/* 在向客戶端轉(zhuǎn)發(fā)上游服務(wù)器的包體時(shí)才有用。當(dāng)buffering為1時(shí),表示使用多個(gè)緩沖區(qū)以及磁盤文件來轉(zhuǎn)發(fā)上有的響應(yīng)包體。當(dāng)Nginx與上游間的網(wǎng)速遠(yuǎn)大于Nginx與下游客戶端見的網(wǎng)速時(shí),讓Nginx開辟更多的內(nèi)存甚至使用磁盤文件來緩存上游的響應(yīng)包體,這是有意義的,它可以減輕上游服務(wù)器的并發(fā)壓力。當(dāng)buffering為0時(shí),表示只使用上面的這一個(gè)buffer緩沖區(qū)來向下游轉(zhuǎn)發(fā)響應(yīng)包體*/unsigned buffering:1;... };42.設(shè)置upstream的限制性參數(shù)
? ? ngx_http_upstream_t中的conf成員,它用于設(shè)置upstream模塊處理請(qǐng)求時(shí)的參數(shù),包括連接、發(fā)送、接收的超時(shí)時(shí)間等。
typedef struct {...//連接上游服務(wù)器的超時(shí)時(shí)間,單位為毫秒ngx_msec_t connect_timeout;//發(fā)送TCP包到上游服務(wù)器的超時(shí)時(shí)間,單位為毫秒ngx_msec_t send_timeout;//接收TCP包到上游服務(wù)器的超時(shí)時(shí)間,單位為毫秒ngx_msec_t read_timeout;... } ngx_http_upstream_conf_t;43.ngx_http_upstream_t結(jié)構(gòu)中的resolved成員可以直接設(shè)置上游服務(wù)器的地址,首先介紹一下resolved的類型:
typedef struct {...//地址個(gè)數(shù)ngx_uint_t naddrs;//上游服務(wù)器的地址struct sockaddr *sockaddr;socklen_t socklen;... } ngx_http_upstream_resolved_t;44.直接執(zhí)行ngx_http_upstream_init方法即可啟動(dòng)upstream機(jī)制。
45.create_request回調(diào)方法
? ??
? ? 如上圖,步驟分別如下:
? ? 1) 在Nginx主循環(huán)(之類的主循環(huán)是指ngx_worker_process_cycle方法)中,會(huì)定期地調(diào)用事件模塊,以檢查是否有網(wǎng)絡(luò)事件發(fā)生。
? ? 2)事件模塊在接收到HTTP請(qǐng)求后會(huì)調(diào)用HTTP框架來處理。假設(shè)接收、解析完HTTP頭部后發(fā)現(xiàn)應(yīng)該由mytest模塊處理,這時(shí)會(huì)調(diào)用mytest模塊的ngx_http_mytest_handler來處理。
? ? 3)設(shè)置回調(diào)函數(shù)和第三方地址;
? ? 4)調(diào)用ngx_http_upstream_init方法啟動(dòng)upstream;
? ? 5)upstream模塊會(huì)去檢查文件緩存,如果緩存中已經(jīng)有合適的響應(yīng)包,則會(huì)直接返回緩存(當(dāng)然必須是在使用反向代理文件緩存的前提下)。
? ? 6)回調(diào)mytest模塊已經(jīng)實(shí)現(xiàn)的create_request回調(diào)方法;
? ? 7)mytest模塊通過設(shè)置r->upstream->request_bufs已經(jīng)決定好發(fā)送什么樣的請(qǐng)求到上有服務(wù)器。
? ? 8)upstream模塊會(huì)檢查已經(jīng)介紹過的resolved成員,如果有resolved成員的話,就根據(jù)它設(shè)置好上游服務(wù)器的地址r->upstream->peer成員。
? ? 9)用無阻塞的TCP套接字建立連接;
? ? 10)無論連接是否建立成功,負(fù)責(zé)建立連接的connect方法都會(huì)立刻返回。
? ? 11)ngx_http_upstream_init返回;
? ? 12)mytest模塊的ngx_http_mytest_handler方法返回NGX_DONE。
? ? 13)當(dāng)事件模塊處理完這批網(wǎng)絡(luò)事件后,將控制權(quán)交還給Nginx主循環(huán).
46.reinit_request可能會(huì)被多次回調(diào)。它被調(diào)用的原因只有一個(gè),就是在第一次試圖向上游服務(wù)器建立連接時(shí),如果連接由于各種異常原因失敗,那么會(huì)根據(jù)upstream中conf參數(shù)的策略要求再次重連上游服務(wù)器,而這時(shí)就會(huì)調(diào)用reinit_request方法了。
? ??
? ? ? 上圖中的流程的步驟描述如下:
? ? ? 1) Nginx主循環(huán)中會(huì)定期地調(diào)用事件模塊,檢查是否有網(wǎng)絡(luò)事件發(fā)生。
? ? ? 2) 事件模塊在確定與上游服務(wù)器的TCP連接建立成功后,會(huì)回調(diào)upstream模塊的相關(guān)方法處理。
? ? ? 3) upstream模塊這時(shí)會(huì)把r->upstream->request_sent標(biāo)志位置為1,表示連接已經(jīng)建立成功了,現(xiàn)在開始向上游服務(wù)器發(fā)送請(qǐng)求內(nèi)容。
? ? ? 4) 發(fā)送請(qǐng)求到上游服務(wù)器。
? ? ? 5) 發(fā)送方法當(dāng)然是無阻塞的(使用了無阻塞的套接字),會(huì)立刻返回。
? ? ? 6) upstream模塊處理第2步中的TCP連接建立成功事件。
? ? ? 7) 事件模塊處理完本輪網(wǎng)絡(luò)事件后,將控制權(quán)交還給Nginx主循環(huán)。
? ? ? 8) Nginx主循環(huán)重復(fù)第1步,調(diào)用事件模塊檢查網(wǎng)絡(luò)事件。
? ? ? 9) 這時(shí),如果發(fā)現(xiàn)與上游服務(wù)器建立的TCP連接已經(jīng)異常斷開,那么事件模塊會(huì)通知upstream模塊處理它。
? ? ? 10) 在符合重試次數(shù)的前提下,upstream模塊會(huì)毫不猶豫地再次用無阻塞的套接字試圖建立連接。
? ? ? 11) 無論連接是否建立成功都立刻返回。
? ? ? 12) 這時(shí)檢查r->upstream->request_sent標(biāo)志位,會(huì)發(fā)現(xiàn)它已經(jīng)被置為1了。
? ? ? 13) 如果mytest模塊沒有實(shí)現(xiàn)reinit_request方法,那么是不會(huì)調(diào)用它的。而如果reinit_request不為NULL空指針,就會(huì)回調(diào)它。
? ? ? ?14) mytest模塊在reinit_request中處理完自己的事情。
? ? ? ?15) 處理完第9步中的TCP連接斷開事件,將控制權(quán)交還給事件模塊。
? ? ? ?16) 事件模塊處理完本輪網(wǎng)絡(luò)事件后,交還控制權(quán)給Nginx主循環(huán)。
47.finalize_request回調(diào)方法
? ? 當(dāng)調(diào)用ngx_http_upstream_init啟動(dòng)upstream機(jī)制后,在各種原因(無論成功還是失敗)導(dǎo)致該請(qǐng)求被銷毀前都會(huì)調(diào)用finalize_request方法。
? ? 在finalize_request方法中可以不做任何事情,但必須實(shí)現(xiàn)finalize_request方法,否則Nginx會(huì)出現(xiàn)空指針調(diào)用的嚴(yán)重錯(cuò)誤。
48.process_header回調(diào)方法
? ? process_header是用于解析上游服務(wù)器返回的基于TCP的響應(yīng)頭部的,因此,process_header可能會(huì)被多次調(diào)用,它的調(diào)用次數(shù)與process_header的返回值有關(guān)。如果process_header返回NGX_AGAIN,這意味著還沒有接收到完整的響應(yīng)頭部,如果再次接收到上游服務(wù)器發(fā)來的TCP流,還會(huì)把它當(dāng)做頭部,仍然調(diào)用process_header處理。如果process_header返回NGX_OK(或者其他非NGX_AGAIN的值),那么在這次連接的后續(xù)處理中將不會(huì)再次調(diào)用process_header。
? ?
? ? 上圖中的步驟解釋:
? ?1) Nginx主循環(huán)中會(huì)定期地調(diào)用事件模塊,檢查是否有網(wǎng)絡(luò)事件發(fā)生。
? ?2) 事件模塊接收到上游服務(wù)器發(fā)來的響應(yīng)時(shí),會(huì)回調(diào)upstream模塊處理。
? ?3) upstream模塊這時(shí)可以從套接字緩沖區(qū)中讀取到來自上游的TCP流。
? ?4) 讀取的響應(yīng)會(huì)存放到r->upstream->buffer指向的內(nèi)存中。注意:在未解析完響應(yīng)頭部前,若多次接收到字符流,所有接收自上游的響應(yīng)頭回完整地存放到r->upstream->buffer緩沖區(qū)中。因此,在解析上游響應(yīng)包頭時(shí),如果buffer緩沖區(qū)全滿卻還沒有解析到完整的響應(yīng)頭部(也就是說,process_header一直在返回NGX_AGAIN),那么請(qǐng)求就會(huì)出錯(cuò)。
? ?5) 調(diào)用mytest模塊實(shí)現(xiàn)的process_header方法。
? ?6) process_header方法實(shí)際上就是在解析r->upstream->buffer緩沖區(qū),試圖從中取到完整的響應(yīng)頭部(當(dāng)然,如果上游服務(wù)器與Nginx通過HTTP通信,就是接收到完整的HTTP頭部)。
? ?7) 如果process_header返回NGX_AGAIN,那么表示還沒有解析到完整的響應(yīng)頭部,下次還會(huì)調(diào)用process_header處理接收到的上游響應(yīng)。
? ?8) 調(diào)用無阻塞的讀取套接字接口。
? ?9) 這時(shí)有可能返回套接字緩沖區(qū)已經(jīng)為空。
? ?10) 當(dāng)?shù)?步中的讀取上游響應(yīng)時(shí)間處理完畢后,控制權(quán)交還給事件模塊。
? ?11) 事件模塊處理完本輪網(wǎng)絡(luò)事件后,交還控制權(quán)給Nginx主循環(huán)。
49.舉例說明upstream機(jī)制。實(shí)現(xiàn)的功能很簡(jiǎn)單:"即以訪問mytest模塊的URL參數(shù)作為搜索引擎的關(guān)鍵字,用upstream方式訪問google,查詢URL里的參數(shù),然后把google的結(jié)果返回給用戶。"
? ?每一個(gè)HTTP請(qǐng)求都會(huì)有獨(dú)立的ngx_http_upstream_conf_t結(jié)構(gòu)體,在mytest模塊的例子中,所有的請(qǐng)求都將共享同一個(gè)ngx_http_upstream_conf_t結(jié)構(gòu)體,因此,這里把它放到ngx_http_mytest_conf_t配置結(jié)構(gòu)體中,如下所示:
typedef struct {ngx_http_upstream_conf_t upstream; } ngx_http_mytest_conf_t;? ?(1) 在啟動(dòng)upstream前,先將ngx_http_mytest_conf_t下的upstream成員賦給r->upstream->conf成員。
static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf) {ngx_http_mytest_conf_t *mycf;mycf = (ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_mytest_conf_t));if (mycf == NULL) {return NULL;}/* 以下簡(jiǎn)單的硬編碼ngx_http_upstream_conf_t結(jié)構(gòu)中的各成員,如超時(shí)事件,都設(shè)為1分鐘,這也是HTTP反向代理模塊的默認(rèn)值*/mycf->upstream.connect_timeout = 60000;mycf->upstream.send_timeout = 60000;mycf->upstream.read_timeout = 60000;mycf->upstream.store_access = 0600;/* 實(shí)際上,buffering已經(jīng)決定了將以固定大小的內(nèi)存作為緩沖區(qū)來轉(zhuǎn)發(fā)上游的響應(yīng)包體,這塊固定緩沖區(qū)的大小就是buffer_size。如果buffering為1,就會(huì)使用更多的內(nèi)存緩存來不及發(fā)往下游的響應(yīng)。例如,最多使用bufs.num個(gè)緩沖區(qū)且每個(gè)緩沖區(qū)大小為bufs.size。另外,還會(huì)使用臨時(shí)文件,臨時(shí)文件的最大長度為max_temp_file_size */mycf->upstream.buffering = 0;mycf->upstream.bufs.num = 8;mycf->upstream.bufs.size = ngx_pagesize;mycf->upstream.buffer_size = ngx_pagesize;mycf->upstream.busy_buffers_size = 2*ngx_pagesize;mycf->upstream.temp_file_write_size = 2 * ngx_pagesize;mycf->upstream.max_temp_file_size = 1024 * 1024 * 1024;/* upstream模塊要求hide_headers成員必須要初始化(upstream在解析完上游服務(wù)器返回的包頭時(shí),會(huì)調(diào)用ngx_http_upstream_process_headers方法按照hide_headers成員將本應(yīng)轉(zhuǎn)發(fā)給下游的一些HTTP頭部隱藏),這里將它賦為NGX_CONF_UNSET_PTR,這是為了在merge合并配置項(xiàng)方法中使用upstream模塊提供的ngx_http_upstream_hide_headers_hash方法初始化hide_headers成員 */mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR;mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR:retrurn mycf; }static char *ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_mytest_conf_t *prev = (ngx_http_mytest_conf_t *)parent;ngx_http_mytest_conf_t *conf = (ngx_http_mytest_conf_t *)child;ngx_hash_init_t hash;hash.max_size = 100;hash.bucket_size = 1024;hash.name = "proxy_headers_hash";if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, ngx_http_proxy_hide_headers, &hash) != NGX_OK) {return NGX_CONF_ERROR;}return NGX_CONF_OK; }? 本例必須要使用上下文才能正確地解析upstream上游服務(wù)器的響應(yīng)包,因?yàn)閡pstream模塊每次接收到一段TCP流時(shí)都會(huì)回調(diào)mytest模塊實(shí)現(xiàn)的process_header方法解析,這樣就需要有一個(gè)上下文保存解析狀態(tài)。
? ? ? (2) 在create_request方法中構(gòu)造請(qǐng)求
? ? ? ? ?下面方法用于創(chuàng)建發(fā)送給上游服務(wù)器的HTTP請(qǐng)求,upstream模塊將會(huì)回調(diào)它,實(shí)現(xiàn)如下:
static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r) {/* 在發(fā)往google上游服務(wù)器的請(qǐng)求很簡(jiǎn)單,就是模仿正常的搜索請(qǐng)求,以/search?q=...的URL來發(fā)起搜索請(qǐng)求。backendQueryLine中的%V等轉(zhuǎn)化格式的用法*/static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n");ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2;/* 必須在內(nèi)存池中申請(qǐng)內(nèi)存,這有以下兩點(diǎn)好處:一個(gè)好處是,在網(wǎng)絡(luò)情況不佳的情況下,向上游服務(wù)器發(fā)送請(qǐng)求時(shí),可能需要epoll多次調(diào)度send才能發(fā)送完成,這時(shí)必須保證這段內(nèi)存不會(huì)被釋放;另一個(gè)好處是,在請(qǐng)求結(jié)束時(shí),這段內(nèi)存會(huì)被自動(dòng)釋放,減低內(nèi)存泄露的可能性*/ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen);if (b == NULL) {return NGX_ERROR;}//last要指向請(qǐng)求的末尾b->last = b->pos + queryLineLen;//作用相當(dāng)于snprintfngx_snprintf(b->pos, queryLineLen, (char *)backendQueryLine.data, &r->args);/* r->upstream->request_bufs是一個(gè)ngx_chain_t結(jié)構(gòu),它包含著要發(fā)送給上游服務(wù)器的請(qǐng)求 */r->upstream->request_bufs = ngx_alloc_chain_link(r->pool);if (r->upstream->request_bufs == NULL) return NGX_ERROR;//request_bufs在這里只包含1個(gè)ngx_buf_t緩沖區(qū)r->upstream->request_bufs->buf = b;r->upstream->request_bufs->next = NULL;r->upstream->request_sent = 0;r->upstream->header_sent = 0;// header_hash不可以為0r->header_hash = 1;return NGX_OK; }? ? ? ? ? (3) 在process_header方法中解析包頭
? ? ? ? ? ? ?process_header負(fù)責(zé)解析上游服務(wù)器發(fā)來的基于TCP的包頭,在本例中,就是解析HTTP響應(yīng)行和HTTP頭部,因此,這里使用mytest_process_status_line方法解析HTTP響應(yīng)行,使用mytest_upstream_process_header方法解析http響應(yīng)頭部。
static ngx_int_t mytest_process_status_line(ngx_http_request_t *r) {size_t len;ngx_int_t rc;ngx_http_upstream_t *u;//上下文中才會(huì)保存多次解析HTTP響應(yīng)行的狀態(tài),下面首先取出請(qǐng)求的上下文ngx_http_mytest_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);if (ctx == NULL) {return NGX_ERROR;}u = r->upstream;/* HTTP框架提供的ngx_http_parse_status_line方法可以解析HTTP響應(yīng)行,它的輸入就是收到的字符流和上下文中的ngx_http_status_t結(jié)構(gòu) */rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);//返回NGX_AGAIN時(shí),表示還沒有解析出完整的HTTP響應(yīng)行,需要接受更多的字符流再進(jìn)行解析if (rc == NGX_AGAIN) {return rc;}//返回NGX_ERROR時(shí),表示沒有接收到合法的HTTP響應(yīng)行if (rc == NGX_ERROR) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"upstream sent no valid HTTP/1.0 header");r->http_version = NGX_HTTP_VERSION_9;u->state->status = NGX_HTTP_OK;return NGX_OK;}/* 以下表示在解析到完整的HTTP響應(yīng)行時(shí),會(huì)做一些簡(jiǎn)單的賦值操作,將解析出的信息設(shè)置到r->upstream->headers_in結(jié)構(gòu)體中。當(dāng)upstream解析完所有的包頭時(shí),會(huì)把headers_in中的成員設(shè)置到 將要向下游發(fā)送的r->headers_out結(jié)構(gòu)體中,也就是說,現(xiàn)在用戶向headers_in中設(shè)置的信息,最終都會(huì)發(fā)往下游客戶端。為什么不直接設(shè)置r->headers_out而要多此一舉呢?因?yàn)閡pstream希望能夠按照ngx_http_upstream_conf_t配置結(jié)構(gòu)體中的hide_headers等成員對(duì)發(fā)往下游的響應(yīng)頭部做統(tǒng)一處理 */if (u->state) {u->state->status = ctx->status.code;}u->headers_in.status_n = ctx->status.code;len = ctx->status.end - ctx->status.start;u->headers_in.status_line.len = len;u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);if (u->headers_in.status_line.data == NULL) {return NGX_ERROR;}ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);/* 下一步將開始解析HTTP頭部。設(shè)置process_header回調(diào)方法為mytest_upstream_process_header,之后再收到的新字符流將由mytest_upstream_process_header解析 */u->process_header = mytest_upstream_process_header;/* 如果本次收到的字符流除了HTTP響應(yīng)行外,還有多余的字符,那么將由mytest_upstream_process_header方式解析 */return mytest_upstream_process_header(r); }? mytest_upstream_process_header方法可以解析HTTP響應(yīng)頭部,而這里只是簡(jiǎn)單地把上游服務(wù)器發(fā)送的HTTP頭部添加到了請(qǐng)求r->upstream->headers_in.headers鏈表中。如果有需要特殊處理的HTTP頭部,那么應(yīng)該在mytest_upstream_process_header方法中進(jìn)行。
static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r) {ngx_int_t rc;ngx_table_elt_t *h;ngx_http_upstream_header_t *hh;ngx_http_upstream_main_conf_t *umcf;/* 這里將upstream模塊配置項(xiàng)ngx_http_upstream_main_conf_t取出來,目的只有一個(gè),就是對(duì)將要轉(zhuǎn)發(fā)給下游客戶端的HTTP響應(yīng)頭部進(jìn)行統(tǒng)一處理。該結(jié)構(gòu)體中存儲(chǔ)了需要進(jìn)行統(tǒng)一處理的HTTP頭部名稱和回調(diào)方法 */umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);//循環(huán)地解析所有的HTTP頭部for (;;) {/* HTTP框架提供了基礎(chǔ)性的ngx_http_parse_haeder_line方法,它用于解析HTTP頭部 */rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);//返回NGX_OK時(shí),表示解析出一行HTTP頭部if (rc == NGX_OK) {// 向headers_in.headers這個(gè)ngx_list_t鏈表中添加HTTP頭部h = ngx_list_push(&r->upstream->headers_in.headers);if (h == NULL) {return NGX_ERROR;}//下面開始構(gòu)造剛剛添加到headers鏈表中的HTTP頭部h->hash = r->header_hash;h->key.len = r->header_name_end - r->header_name_start;h->value.len = r->header_end - r->header_start;//必須在內(nèi)存池中分配存放HTTP頭部的內(nèi)存空間h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len);if (h->key.data == NULL) {return NGX_ERROR;}h->value.data = h->key.data + h->key.len + 1;h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;ngx_memcpy(h->key.data, r->header_name_start, h->key.len);h->key.data[h->key.len] = '\0';ngx_memcpy(h->value.data, r->header_start, h->value.len);h->value.data[h->value.len] = '\0';if (h->key.len == r->lowcase_index) {ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);} else {ngx_strlow(h->lowcase_key, h->key.data, h->key.len);}//upstream模塊會(huì)對(duì)一些HTTP頭部做特殊處理hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len);if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {return NGX_ERROR;}continue;}/* 返回NGX_HTTP_PARSE_HEADER_DONE時(shí),表示響應(yīng)中所有的HTTP頭部解析完畢,接下來在接收到的都將是HTTP包體 */if (rc == NGX_HTTP_PARSE_HEADER_DONE) {/* 如果之前解析HTTP頭部時(shí)沒有發(fā)現(xiàn)server和date頭部,那么下面會(huì)根據(jù)HTTP協(xié)議規(guī)范添加這兩個(gè)頭部 */if (r->upstream->headers_in.server == NULL) {h = ngx_list_push(&r->upstream->headers_in.headers);if (h == NULL) {return NGX_ERROR;}h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');ngx_str_set(&h->key, "Date");ngx_str_null(&h->value);h->lowcase_key = (u_char *) "date";}return NGX_OK;}/* 如果返回NGX_AGAIN, 則表示狀態(tài)機(jī)還沒有解析到完整的HTTP頭部,此時(shí)要求upstream模塊繼續(xù)接收新的字符流,然后交由process_header回調(diào)方法解析 */if (rc == NGX_AGAIN) {return NGX_AGAIN;}//其他返回值都是非法的ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header");return NGX_HTTP_UPSTREAM_INVALID_HEADER;} }? ?當(dāng)mytest_upstream_process_header返回NGX_OK后,upstream模塊開始把上游的包體(如果有的話)直接轉(zhuǎn)發(fā)到下游客戶端。
? ? ? ? ? (4) 在finalize_request方法中釋放資源
? ? ? ? ? ? ? ? 當(dāng)請(qǐng)求結(jié)束時(shí),將會(huì)回調(diào)finalize_request方法,如果我們希望此時(shí)釋放資源,如打開的句柄等,那么可以把這樣的代碼添加到finalize_request方法中。本例中定義了mytest_upstream_finalize_request方法,由于我們沒有任何需要釋放的資源,所以該方法沒有完成任何實(shí)際工作,只是因?yàn)閡pstream模塊要求必須實(shí)現(xiàn)finalize_request回調(diào)方法。
? ? ? ?? ?(5) 在ngx_http_mytest_handler方法中啟動(dòng)upstream
? ? ? ? ? ? ? ? ?在開始介入處理客戶端請(qǐng)求的ngx_http_mytest_handler方法中啟動(dòng)upstream機(jī)制,而何時(shí)會(huì)結(jié)束,則視Nginx與上游的google服務(wù)器間的通信而定。
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_r *r) {//首先建立HTTP上下文結(jié)構(gòu)體ngx_http_mytest_ctx_tngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);if (myctx == NULL) {myctx = ngx_palloc(r->pool, sizeof(ngx_http_mytest_ctx_t));if (myctx == NULL) {return NGX_ERROR;}//得到配置結(jié)構(gòu)體ngx_http_mytest_conf_tngx_http_mytest_conf_t *mycf = (ngx_http_mytest_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);ngx_http_upstream_t *u = r->upstream;//這里用配置文件中的結(jié)構(gòu)體來賦給r->upstream->conf成員u->conf = &mycf->upstream;//決定轉(zhuǎn)發(fā)包體時(shí)使用的緩沖區(qū)u->buffering = mycf->upstream.buffering;//以下代碼開始初始化resolved結(jié)構(gòu)體,用來保存上游服務(wù)器的地址u->resolved = (ngx_http_upstream_resolved_resolved_t *)ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));if (u->resolved == NULL) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc resolved error. %s.", strerror(errno));return NGX_ERROR;}//這里的上游服務(wù)器就是www.google.comstatic struct sockaddr_in backendSockAddr;struct hostent *pHost = gethostbyname((char *) "www.google.com");if (pHost == NULL) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gethostbyname fail. %s", strerror(errno));return NGX_ERROR;}//訪問上游服務(wù)器的80端口backendSockAddr.sin_family = AF_INET;backendSockAddr.sin_port = htons((in_port_t) 80);char *pDmsIP = inet_ntoa(*(struct in_addr *) (pHost->h_addr_list[0]));backendSockAddr.sin_addr.s_addr = inet_addr(pDmsIP);myctx->backendServer.data = (u_char *)pDmsIP;myctx->backendServer.len = strlen(pDmsIP);//將地址設(shè)置到resolved成員中u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;u->resolved->socklen = sizeof(struct sockaddr_in);u->resolved->naddrs = 1;// 設(shè)置3個(gè)必須實(shí)現(xiàn)的回調(diào)方法u->create_request = mytest_upstream_create_request;u->process_header = mytest_process_status_line;u->finalize_request = mytest_upstream_finalize_request;//這里必須將count成員加1r->main->count++;//啟動(dòng)upstreamngx_http_upstream_init(r);//必須返回NGX_DONEreturn NGX_DONE;}? ? ? ? ? ?到此為止,高性能地訪問第三方服務(wù)的upstream例子就介紹完了。在本例中,可以完全異步地訪問第三方服務(wù),并發(fā)訪問數(shù)也只會(huì)受制于物理內(nèi)存的大小,完全可以輕松達(dá)到幾十萬的并發(fā)TCP連接。
50.使用subrequest的方式只需要完成以下4步操作即可:
? ? 1) 在nginx.conf文件中配置好子請(qǐng)求的處理方式;
? ? 2) 啟動(dòng)subrequest子請(qǐng)求;
? ? 3) 實(shí)現(xiàn)子請(qǐng)求執(zhí)行結(jié)束時(shí)的回調(diào)方法;
? ? 4) 實(shí)現(xiàn)父請(qǐng)求被激活時(shí)的回調(diào)方法。
51.ngx_http_subrequest的定義:
ngx_int_t ngx_http_subrequest(ngx_http_request_t *r,ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,ngx_http_post_subrequest_t *ps, ngx_uint_t flags);? ? ?1) ngx_http_request_t *r
? ? ? ? ? ngx_http_request_t *r 是當(dāng)前的請(qǐng)求,也就是父請(qǐng)求。
? ? ? 2) ngx_str_t *uri
? ? ? ? ? ngx_str_t *uri是子請(qǐng)求的URI,它對(duì)究竟選用nginx.conf配置文件中的哪個(gè)模塊來處理子請(qǐng)求起決定性作用。
? ? ? 3) ngx_str_t *args
? ? ? ? ? ngx_str_t *args是子請(qǐng)求的URI參數(shù),如果沒有參數(shù),可以傳送NULL空指針。
? ? ? ?4) ngx_http_request_t **psr
? ? ? ? ? psr是輸出參數(shù)而不是輸入?yún)?shù),它將把ngx_http_subrequest生成的子請(qǐng)求傳出來。
? ? ? ? 5) ngx_http_post_subrequest_t *ps
? ? ? ? ? 這里傳入創(chuàng)建的ngx_http_post_subrequest_t結(jié)構(gòu)體地址,它指出子請(qǐng)求結(jié)束時(shí)必須回調(diào)的處理方法。
? ? ? ? ?6) ngx_uint_t flags
? ? ? ? ? ?flag的取值范圍包括: (1) 0.在沒有特殊需求的情況下都應(yīng)該填寫它; (2) NGX_HTTP_SUBREQUEST_IN_MEMORY。這個(gè)宏會(huì)將子請(qǐng)求的subrequest_in_memory標(biāo)志位置為1,這意味著如果子請(qǐng)求使用upstream訪問上游服務(wù)器,那么上游服務(wù)器的響應(yīng)都將會(huì)在內(nèi)存中處理;(3) NGX_HTTP_SUBREQUEST_WAITED。這個(gè)宏會(huì)將子請(qǐng)求的waited標(biāo)志位置為1,當(dāng)子請(qǐng)求提前結(jié)束時(shí),有個(gè)done標(biāo)志位置為1,但目前HTTP框架并沒有針對(duì)這兩個(gè)標(biāo)志位做任何實(shí)質(zhì)性處理。注意,flag是按比特位操作的,這樣可以同時(shí)包含上述3個(gè)值。
? ? ? ? ?7) 返回值
? ? ? ? ? ? 返回NGX_OK表示成功建立子請(qǐng)求;返回NGX_ERROR表示建立子請(qǐng)求失敗。
52.如何啟動(dòng)subrequest
? ? ?處理父請(qǐng)求的過程中會(huì)創(chuàng)建子請(qǐng)求,在父請(qǐng)求的處理方法返回NGX_DONE后,HTTP框架會(huì)開始執(zhí)行子請(qǐng)求
? ? ??
? ? ? ?上圖中的步驟如下:
? ? ? ?1) Nginx主循環(huán)中會(huì)定期地調(diào)用事件模塊,檢查是否有網(wǎng)絡(luò)事件發(fā)生;
? ? ? ?2) 事件模塊發(fā)現(xiàn)這個(gè)請(qǐng)求的回調(diào)方法屬于HTTP框架,交由HTTP框架來處理請(qǐng)求。
? ? ? ?3) 根據(jù)解析完的URI來決定使用哪個(gè)location下的模塊來處理這個(gè)請(qǐng)求。
? ? ? ?4) 調(diào)用mytest模塊的ngx_http_mytest_handler方法處理這個(gè)請(qǐng)求。
? ? ? ?5) 設(shè)置subrequest子請(qǐng)求的URI及回調(diào)方法。
? ? ? ?6) 調(diào)用ngx_http_subrequest方法創(chuàng)建子請(qǐng)求。
? ? ? ?7) 創(chuàng)建的子請(qǐng)求會(huì)添加到原始請(qǐng)求的posted_requests鏈表中,這樣保證第10步時(shí)會(huì)在父請(qǐng)求返回NGX_DONE的情況下開始執(zhí)行子請(qǐng)求。
? ? ? ?8) ngx_http_subrequest方法執(zhí)行完畢,子請(qǐng)求創(chuàng)建成功。
? ? ? ?9) ngx_http_mytest_handler方法執(zhí)行完畢,返回NGX_DONE,這樣父請(qǐng)求不會(huì)被銷毀,將等待以后的再次激活。
? ? ? ?10) HTTP框架執(zhí)行完當(dāng)前請(qǐng)求(父請(qǐng)求)后,檢查posted_requests鏈表中是否還有子請(qǐng)求,如果存在子請(qǐng)求,則調(diào)用子請(qǐng)求的write_event_handler方法。
? ? ? ? 11) 根據(jù)子請(qǐng)求的URI(第5步中建立),檢查nginx.conf文件中所有的location配置,確定應(yīng)由哪個(gè)模塊來執(zhí)行子請(qǐng)求。在本章的例子中,子請(qǐng)求是交由反向代理模塊執(zhí)行的。
? ? ? ? 12) 調(diào)用反向代理模塊的入口方法ngx_http_proxy_handler來處理子請(qǐng)求。
? ? ? ? ?13) 由于反向代理模塊使用了upstream機(jī)制,所以它也要通過許多次的異步調(diào)用才能完整地處理完子請(qǐng)求,這時(shí)它的入口方法會(huì)返回NGX_DONE.
? ? ? ? ?14) 再次檢查是否還有子請(qǐng)求,這時(shí)會(huì)發(fā)現(xiàn)已經(jīng)沒有子請(qǐng)求需要執(zhí)行了。當(dāng)然,子請(qǐng)求可以繼續(xù)建立新的子請(qǐng)求,只是這里的反向代理模塊不會(huì)這樣做。
? ? ? ? ?15) 當(dāng)?shù)?步中的網(wǎng)絡(luò)讀取事件處理完畢后,交還控制權(quán)給事件模塊。
? ? ? ? ?16) 當(dāng)本輪網(wǎng)絡(luò)事件處理完畢后,交還控制權(quán)給Nginx主循環(huán)。
53.如何轉(zhuǎn)發(fā)多個(gè)子請(qǐng)求的響應(yīng)包體
? ? ?每個(gè)請(qǐng)求的ngx_http_request_t結(jié)構(gòu)體中都有一個(gè)postponed成員:
struct ngx_http_request_s {...ngx_http_postponed_request_t *postponed;... } 它實(shí)際上是一個(gè)鏈表: typedef struct ngx_http_postponed_request_s ngx_http_postponed_request_t; struct ngx_http_postponed_request_s {ngx_http_request_t *request;ngx_chain_t *out;ngx_http_postponed_request_t *next; };? ? ? ? 多個(gè)ngx_http_postponed_request_t之間使用next指針連接成一個(gè)單向鏈表。ngx_http_postponed_request_t中的out成員是ngx_chain_t結(jié)構(gòu),它指向的是來自上游的、將要轉(zhuǎn)發(fā)給下游的響應(yīng)包體。
? ? ? ? 每當(dāng)使用ngx_http_output_filter方法(反向代理模塊也使用該方法轉(zhuǎn)發(fā)響應(yīng))向下游的客戶端發(fā)送響應(yīng)包體時(shí),都會(huì)調(diào)用到ngx_http_postpone_filter_module過濾模塊處理這段要發(fā)送的包體。
//這里的參數(shù)in就是將要發(fā)送給客戶端的一段包體 static ngx_int_t ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in) {ngx_connection_t *c;ngx_http_postponed_request_t *pr;//c是Nginx與下游客戶端間的連接,c->data保存的是原始請(qǐng)求c = r->connection;//如果當(dāng)前請(qǐng)求r是一個(gè)子請(qǐng)求(因?yàn)閏->data指向原始請(qǐng)求)if (r != c->data) {/* 如果待發(fā)送的in包體不為空,則把in加到postponed鏈表中屬于當(dāng)前請(qǐng)求的ngx_http_postponed_request_t結(jié)構(gòu)體的out鏈表中,同時(shí)返回NGX_OK,這意味著本次不會(huì)把in包體發(fā)送客戶端*/if (in) {ngx_http_postpone_filter_add(r, in);return NGX_OK;}//如果當(dāng)前請(qǐng)求是子請(qǐng)求,而in包體又為空,那么直接返回即可return NGX_OK;}//如果postponed為空,表示請(qǐng)求r沒有子請(qǐng)求產(chǎn)生的響應(yīng)需要轉(zhuǎn)發(fā)if (r->postponed == NULL) {/* 直接調(diào)用下一個(gè)HTTP過濾模塊繼續(xù)處理in包體即可。如果沒有錯(cuò)誤的話,就會(huì)向下游客戶端發(fā)送響應(yīng) */if (in || c->buffered) {return ngx_http_next_filter(r->main, in);}return NGX_OK;}/* 至此,說明postponed鏈表中是有子請(qǐng)求產(chǎn)生的響應(yīng)需要轉(zhuǎn)發(fā)的,可以先把in包體加到待轉(zhuǎn)發(fā)響應(yīng)的末尾 */if (in) {ngx_http_postpone_filter_add(r, in);}//循環(huán)處理postponed鏈表中所有子請(qǐng)求待轉(zhuǎn)發(fā)的包體do {pr = r->postponed;/* 如果pr->request是子請(qǐng)求,則加入到原始請(qǐng)求的posted_requests隊(duì)列中,等待HTTP框架下次調(diào)用這個(gè)請(qǐng)求時(shí)再來處理 */if (pr->request) {r->postponed = pr->next;c->data = pr->request;return ngx_http_post_request(pr->request, NULL);}//調(diào)用下一個(gè)HTTP過濾模塊轉(zhuǎn)發(fā)out鏈表中保存的待轉(zhuǎn)發(fā)的包體if (pr->out == NULL) {} else {if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {return NGX_ERROR;}}//遍歷完postponed鏈表r->postponed = pr->next;} while (r->postponed);return NGX_OK; }? ? ? ? ?
54.子請(qǐng)求在結(jié)束前會(huì)回調(diào)在ngx_http_post_subrequest_t中實(shí)現(xiàn)的handler方法,在這個(gè)handler方法中,又設(shè)置了父請(qǐng)求被激活后的執(zhí)行方法mytest_post_handler,流程如下:
? ? ? ? ? ? ? ??
? ?上圖中的步驟如下:
? ? 1) Nginx主循環(huán)中會(huì)定期地調(diào)用事件模塊,檢查是否有網(wǎng)絡(luò)事件發(fā)生。
? ? 2) 如果事件模塊檢測(cè)到連接關(guān)閉事件,而這個(gè)請(qǐng)求的處理方法屬于upstream模塊,則交由upstream模塊來處理請(qǐng)求。
? ? 3) upstream模塊開始調(diào)用ngx_http_upstream_finalize_request方法來結(jié)束upstream機(jī)制下的請(qǐng)求。
? ? 4) 調(diào)用HTTP框架提供的ngx_http_finalize_request方法來結(jié)束子請(qǐng)求。
? ? 5) ngx_http_finalize_request方法會(huì)檢查當(dāng)前的請(qǐng)求是否是子請(qǐng)求,如果是子請(qǐng)求,則會(huì)回調(diào)post_subrequest成員中的handler方法,也就是會(huì)調(diào)用mytest_subrequest_post_handler方法。
? ? 6) 在實(shí)現(xiàn)的子請(qǐng)求回調(diào)方法中,解析子請(qǐng)求返回的響應(yīng)包。注意,這時(shí)需要通過write_event_handler設(shè)置父請(qǐng)求被激活后的回調(diào)方法(因此此時(shí)父請(qǐng)求的回調(diào)方法已經(jīng)被HTTP框架設(shè)置為什么事情也不做的ngx_http_request_empty_handler方法).
? ? 7) 子請(qǐng)求的回調(diào)方法執(zhí)行完畢后,交由HTTP框架的ngx_http_finalize_request方法繼續(xù)向下執(zhí)行。
? ? 8) ngx_http_finalize_request方法執(zhí)行完畢。
? ? 9) HTTP框架如果發(fā)現(xiàn)當(dāng)前請(qǐng)求后還有父請(qǐng)求需要執(zhí)行,則調(diào)用父請(qǐng)求的write_event_handler回調(diào)方法。
? ? 10) 這里可以根據(jù)第6步中解析子請(qǐng)求響應(yīng)后的結(jié)果來構(gòu)造響應(yīng)包。
? ? ?11) 調(diào)用無阻塞的ngx_http_send_header、ngx_http_output_filter發(fā)送方法,向客戶端發(fā)送響應(yīng)包。
? ? ?12) 無阻塞發(fā)送方法會(huì)立刻返回,即使目前未發(fā)送完,Nginx之后也會(huì)異步地發(fā)送完所有的響應(yīng)包,然后再結(jié)束請(qǐng)求。
? ? ? 13) 父請(qǐng)求的回調(diào)方法執(zhí)行完畢。
? ? ? 14) 當(dāng)?shù)?步中的上游服務(wù)器連接關(guān)閉時(shí)間處理完畢后,交還控制權(quán)給事件模塊。
? ? ? 15) 當(dāng)本輪網(wǎng)絡(luò)事件處理完畢后,交還控制權(quán)給Nginx主循環(huán)。
55.subrequest是分解復(fù)雜請(qǐng)求的設(shè)計(jì)方法,派生出的子請(qǐng)求使用某些HTTP模塊基于upstream訪問第三方服務(wù)是最常見的用法,通過subrequest可以使Nginx在保持高并發(fā)的前提下處理復(fù)雜的業(yè)務(wù)。
56.
57.默認(rèn)即編譯進(jìn)Nginx的HTTP過濾模塊
| 默認(rèn)即編譯進(jìn)Nginx的HTTP過濾模塊 | 功能 |
| ngx_http_not_modified_filter_module | 僅對(duì)HTTP頭部進(jìn)行處理。在返回200成功時(shí),根據(jù)請(qǐng)求中If-Modified-Since或者If-Unmodified-Since頭部取得瀏覽器緩存文件的時(shí)間,再分析返回用戶文件的最后修改時(shí)間,以此決定是否直接發(fā)送304 Not Modified響應(yīng)給用戶 |
| ngx_http_range_body_filter_module | 處理請(qǐng)求中的Range信息,根據(jù)Range中的要求返回文件的一部分給用戶 |
| ngx_http_copy_filter_module | 僅對(duì)HTTP包體做處理。將用戶發(fā)送的ngx_chain_t結(jié)構(gòu)的HTTP包體復(fù)制到新的ngx_chain-t結(jié)構(gòu)中(都是各種指針的復(fù)制,不包括實(shí)際HTTP響應(yīng)內(nèi)容),后續(xù)的HTTP過濾模塊處理的ngx_chain-t類型的成員都是ngx_http_copy_filter_module模塊處理后的變量 |
| ngx_http_headers_filter_module | 僅對(duì)HTTP頭部做處理。允許通過修改nginx.conf配置文件,在返回給用戶的響應(yīng)中添加任意的HTTP頭部 |
| ngx_http_userid_filter_module | 僅對(duì)HTTP頭部做處理。這就是執(zhí)行configure命令時(shí)提到的http_userid_module模塊,它基于cookie提供了簡(jiǎn)單的認(rèn)證管理功能 |
| ngx_http_charset_filter_module | 可以將文本類型返回給用戶的響應(yīng)包,按照nginx.conf中的配置重新進(jìn)行編碼,再返回給用戶 |
| ngx_http_ssi_filter_module | 支持SSI(Server Side Include,服務(wù)器端嵌入)功能,將文件內(nèi)容包含到網(wǎng)頁中并返回給用戶 |
| ngx_http_postpone_filter_module | 僅對(duì)HTTP包體做處理。它僅應(yīng)用于subrequest產(chǎn)生的子請(qǐng)求。它使得多個(gè)子請(qǐng)求同時(shí)向客戶端發(fā)送響應(yīng)時(shí)能夠有序,所謂的“有序”是指按照子請(qǐng)求的順序發(fā)送響應(yīng) |
| ngx_http_gzip_filter_module | 對(duì)特定的HTTP響應(yīng)包體(如網(wǎng)頁或者文本文件)進(jìn)行g(shù)zip壓縮,再把壓縮后的內(nèi)容返回給用戶 |
| ngx_http_range_header_filter_module | 支持range協(xié)議 |
| ngx_http_chunked_filter_module | 支持chunk編碼 |
| ngx_http_header_filter_module | 僅對(duì)HTTP頭部做處理。該過濾模塊將會(huì)把r->headers_out結(jié)構(gòu)體中的成員序列化為返回給用戶的HTTP響應(yīng)字符流,包括響應(yīng)行(如HTTP/1.1 200 OK)和響應(yīng)頭部,并通過調(diào)用ngx_http_write_filter_module過濾模塊中的過濾方法直接將HTTP包頭發(fā)送給客戶端 |
| ngx_http_write_filter_module | 僅對(duì)HTTP包體做處理。該模塊負(fù)責(zé)向客戶端發(fā)送HTTP響應(yīng) |
58.過濾模塊例子中,HTTP頭部處理方法的執(zhí)行活動(dòng)圖
?
59.過濾模塊例子中,HTTP包體處理方法的執(zhí)行活動(dòng)圖
? ?
60.參考另一篇:https://blog.csdn.net/zhangge3663/article/details/83180659
61.ngx_module_t接口及其對(duì)核心、事件、HTTP、mail等4類模塊ctx上下文成員的具體化
? ??
62.Nginx常用模塊及其之間的關(guān)系
? ??
63.傳統(tǒng)Web服務(wù)器和Nginx間的重要差別:前者是每個(gè)事件消費(fèi)者獨(dú)占一個(gè)進(jìn)程資源,后者的事件消費(fèi)者只是被事件分發(fā)者進(jìn)程短期調(diào)用而已。
64.在阻塞代碼段上按照下面4種方式來劃分階段:
? ?(1) 將阻塞進(jìn)程的方法按照相關(guān)的觸發(fā)事件分解為兩個(gè)階段
? ?(2) 將阻塞方法調(diào)用按照時(shí)間分解為多個(gè)階段的方法調(diào)用
? ?(3) 在“無所事事”且必須等待系統(tǒng)的響應(yīng),從而導(dǎo)致進(jìn)程空轉(zhuǎn)時(shí),使用定時(shí)器劃分階段
? ?(4) 如果阻塞方法完全無法繼續(xù)劃分,則必須使用獨(dú)立的進(jìn)程執(zhí)行這個(gè)阻塞方法
65.內(nèi)存池的設(shè)計(jì)
? ? 為了避免出現(xiàn)內(nèi)存碎片、減少向操作系統(tǒng)申請(qǐng)內(nèi)存的次數(shù)、降低各個(gè)模塊的開發(fā)復(fù)雜度,Nginx設(shè)計(jì)了簡(jiǎn)單的內(nèi)存池。這個(gè)內(nèi)存池沒有很復(fù)雜的功能:通常它不負(fù)責(zé)回收內(nèi)存池中已經(jīng)分配出的內(nèi)存。
? ? 通常每一個(gè)請(qǐng)求都有一個(gè)這種簡(jiǎn)易的獨(dú)立內(nèi)存池(Nginx為每一個(gè)TCP連接都分配一個(gè)內(nèi)存池,HTTP框架為每一個(gè)HTTP請(qǐng)求又分配了1個(gè)內(nèi)存池),而在請(qǐng)求結(jié)束時(shí)則會(huì)銷毀整個(gè)內(nèi)存池,把曾經(jīng)分配的內(nèi)存一次性歸還給操作系統(tǒng)。
66.Nginx核心的框架代碼一直圍繞著一個(gè)結(jié)構(gòu)體展開,它就是ngx_cycle_t。無論是master管理進(jìn)程、worker工作進(jìn)程還是cache manager(loader)進(jìn)程,每一個(gè)進(jìn)程都毫無例外地?fù)碛形ㄒ灰粋€(gè)ngx_cycle_t結(jié)構(gòu)體。
? ? 作為一個(gè)Web服務(wù)器,Nginx首先需要監(jiān)聽端口并處理其中的網(wǎng)絡(luò)事件。ngx_cycle_t對(duì)象中有一個(gè)動(dòng)態(tài)數(shù)組成員叫做listening,它的每一個(gè)數(shù)組元素都是ngx_listening_t結(jié)構(gòu)體,而每個(gè)ngx_listen_t結(jié)構(gòu)體又代表著Nginx服務(wù)器監(jiān)聽的一個(gè)端口。
typedef struct ngx_listening_s ngx_listening_t; struct ngx_listening_s {//socket套接字句柄ngx_socket_t fd;//監(jiān)聽sockaddr地址struct sockaddr *sockaddr;//sockaddr地址長度socklen_t socklen;/* 存儲(chǔ)IP地址的字符串a(chǎn)ddr_text最大長度,即它指定了addr_text所分配的內(nèi)存大小 */size_t addr_text_max_len;//以字符串形式存儲(chǔ)IP地址ngx_str_t addr_text;//套接字地址。例如,當(dāng)type是SOCK_STREAM時(shí),表示TCPint type;/* TCP實(shí)現(xiàn)監(jiān)聽時(shí)的backlog隊(duì)列,它表示允許正在通過三次握手建立TCP連接但還沒有任何進(jìn)程開始處理的連接最大個(gè)數(shù) */int backlog;//內(nèi)核中對(duì)于這個(gè)套接字的接收緩沖區(qū)大小int rcvbuf;//內(nèi)核中對(duì)于這個(gè)套接字的發(fā)送緩沖區(qū)大小int sndbuf;//當(dāng)新的TCP連接成功建立后的處理方法ngx_connection_handler_pt handler;/* 實(shí)際上框架并不使用servers指針,它更多的是作為一個(gè)保留指針,目前主要用于HTTP或者mail等模塊,用于保存當(dāng)前監(jiān)聽端口對(duì)應(yīng)著的所有主機(jī)名 */void *servers;//log和logp都是可用的日志對(duì)象的指針ngx_log_t log;ngx_lot_t *logp;//如果為新的TCP連接創(chuàng)建內(nèi)存池,則內(nèi)存池的初始大小應(yīng)該是pool_sizesize_t pool_size;/* TCP_DEFER_ACCEPT選項(xiàng)將在建立TCP連接成功且接收到用戶的請(qǐng)求數(shù)據(jù)后,才向?qū)ΡO(jiān)聽套接字感興趣的進(jìn)程發(fā)送事件通知,而連接建立成功后,如果post_accept_timeout秒后仍然沒有收到的用戶數(shù)據(jù),則內(nèi)核直接丟棄連接 */ngx_msec_t post_accept_timeout;/* 前一個(gè)ngx_listening_t 結(jié)構(gòu),多個(gè)ngx_listening_t結(jié)構(gòu)體之間由previous指針組成單鏈表 */ngx_listening_t *previous;//當(dāng)前監(jiān)聽句柄對(duì)應(yīng)著的ngx_connection_t結(jié)構(gòu)體ngx_connection_t *connection;/* 標(biāo)志位,為1則表示在當(dāng)前監(jiān)聽句柄有效,且執(zhí)行ngx_init_cycle時(shí)不關(guān)閉監(jiān)聽端口,為0時(shí)則正常關(guān)閉。該標(biāo)志位框架代碼會(huì)自動(dòng)設(shè)置 */unsigned open:1;/* 標(biāo)志位,為1表示使用已有的ngx_cycle_t來初始化新的ngx_cycle_t結(jié)構(gòu)體時(shí),不關(guān)閉原來打開的監(jiān)聽端口,這對(duì)運(yùn)行中升級(jí)程序很有用,remain為0時(shí),表示正常關(guān)閉曾經(jīng)打開的監(jiān)聽端口。該標(biāo)志位框架代碼會(huì)自動(dòng)設(shè)置,參見ngx_init_cycle方法 */unsigned remain:1;/* 標(biāo)志位,為1時(shí)表示跳過設(shè)置當(dāng)前ngx_listening_t結(jié)構(gòu)體中的套接字,為0時(shí)正常初始化套接字。該標(biāo)志位框架代碼會(huì)自動(dòng)設(shè)置 */unsigned ignore:1;//表示是否已經(jīng)綁定。實(shí)際上目前該標(biāo)志位沒有使用unsigned bound:1; /* 已經(jīng)綁定 *//* 表示當(dāng)前監(jiān)聽句柄是否來自前一個(gè)進(jìn)程(如升級(jí)Nginx程序),如果為1,則表示來自前一個(gè)進(jìn)程。一般會(huì)保留之前已經(jīng)設(shè)置好的套接字,不做改變 */unsigned inherited:1; /* 來自前一個(gè)進(jìn)程 *///目前未使用unsigned nonblocking_accept:1;//標(biāo)志位,為1時(shí)表示當(dāng)前結(jié)構(gòu)體對(duì)應(yīng)的套接字已經(jīng)監(jiān)聽unsigned listen:1;//表示套接字是否阻塞,目前該標(biāo)志位沒有意義unsigned nonblocking:1;//目前該標(biāo)志位沒有意義unsigned shared:1;//標(biāo)志位,為1時(shí)表示Nginx會(huì)將網(wǎng)絡(luò)地址轉(zhuǎn)變?yōu)樽址问降牡刂穟nsigned addr_ntop:1; };67.Nginx框架是圍繞著ngx_cycle_t結(jié)構(gòu)體來控制進(jìn)程運(yùn)行的。
typedef struct ngx_cycle_s ngx_cycle_t; struct ngx_cycle_s {/* 保存著所有模塊存儲(chǔ)配置項(xiàng)的結(jié)構(gòu)體的指針,它首先是一個(gè)數(shù)組,每個(gè)數(shù)組成員又是一個(gè)指針,這個(gè)指針指向另一個(gè)存儲(chǔ)著指針的數(shù)組,因此會(huì)看到void **** */void ****conf_ctx;//內(nèi)存池ngx_pool_t *pool;/* 日志模塊中提供了生成基本ngx_log_t日志對(duì)象的功能,這里的log實(shí)際上是在還沒有執(zhí)行ngx_init_cycle方法前,也就是還沒有解析配置前,如果有信息需要輸出到日志,就會(huì)暫時(shí)使用log對(duì)象,它會(huì)輸出到屏幕。在ngx_init_cycle方法執(zhí)行后,將會(huì)根據(jù)nginx.conf配置文件中的配置項(xiàng),構(gòu)造出正確的日志文件,此時(shí)會(huì)對(duì)log重新賦值 */ngx_lot_t *log;/*由nginx.conf配置文件讀取到日志文件路徑后,將開始初始化error_log日志文件,由于log對(duì)象還在用于輸出日志到屏幕,這時(shí)會(huì)用new_log對(duì)象暫時(shí)性地替代log日志,待初始化成功后,會(huì)用new_log的地址覆蓋上面的log指針 */ngx_lot_t new_log;//與下面的files成員配合使用,指出files數(shù)組里元素的總數(shù)ngx_uint_t files_n;/* 對(duì)于poll、rtsig這樣的事件模塊,會(huì)以有效文件句柄數(shù)來預(yù)先建立這些ngx_connection_t結(jié)構(gòu)體,以加速事件的收集、分發(fā)。這時(shí)files就會(huì)保存所有ngx_connection_t的指針組成的數(shù)組,files_n就是指針的總數(shù),而文件句柄的值用來訪問files數(shù)組成員 */ngx_connection_t **files;//可用連接池,與free_connection_n配合使用ngx_connection_t *free_connections;//可用連接池中連接的總數(shù)ngx_uint_t free_connection_n;/* 雙向鏈表容器,元素類型是ngx_connection_t結(jié)構(gòu)體,表示可重復(fù)使用連接隊(duì)列 */ngx_queue_t reusable_connections_queue;/*動(dòng)態(tài)數(shù)組,每個(gè)數(shù)組元素存儲(chǔ)著ngx_listening_t成員,表示監(jiān)聽端口及相關(guān)的參數(shù) */ngx_array_t listening;/*動(dòng)態(tài)數(shù)組容器,它保存著Nginx所有要操作的目錄。如果有目錄不存在,則會(huì)視圖創(chuàng)建,而創(chuàng)建目錄失敗將會(huì)導(dǎo)致Nginx啟動(dòng)失敗。例如,上傳文件的臨時(shí)目錄也在pathes中,如果沒有權(quán)限創(chuàng)建,則會(huì)導(dǎo)致Nginx無法啟動(dòng) */ngx_array_t pathes;/* 單鏈表容器,元素類型是ngx_open_file_t結(jié)構(gòu)體,它表示Nginx已經(jīng)打開的所有文件。事實(shí)上,Nginx框架不會(huì)向open_files鏈表中添加文件,而是由對(duì)此感興趣的模塊向其中添加文件路徑名,Nginx框架會(huì)有ngx_init_cycle方法中打開這些文件 */ngx_list_t open_files;/* 單鏈表容器,元素的類型是ngx_shm_zone_t結(jié)構(gòu)體,每個(gè)元素表示一塊共享內(nèi)存*/ngx_list_t shared_memory;//當(dāng)前進(jìn)程中所有連接對(duì)象的總數(shù),與下面的connections成員配合使用ngx_uint_t connection_n;//指向當(dāng)前進(jìn)程中的所有連接對(duì)象,與connection_n配合使用ngx_connection_t *connections;//指向當(dāng)前進(jìn)程中的所有讀事件對(duì)象,connection_n同時(shí)表示所有讀事件的總數(shù)ngx_event_t *read_events;//指向當(dāng)前進(jìn)程中的所有寫事件對(duì)象,connection_n同時(shí)表示所有寫事件的總數(shù)ngx_event_t *write_events;/* 舊的ngx_cycle_t對(duì)象用于引用上一個(gè)ngx_cycle_t對(duì)象中的成員。例如ngx_init_cycle方法,在啟動(dòng)初期,需要建立一個(gè)臨時(shí)的ngx_cycle_t對(duì)象保存一些變量,再調(diào)用ngx_init_cycle方法時(shí)就可以把舊的ngx_cycle_t對(duì)象傳進(jìn)去,而這時(shí)old_cycle對(duì)象就會(huì)保存這個(gè)前期的ngx_cycle_t對(duì)象 */ngx_cycle_t *old_cycle;//配置文件相對(duì)于安裝目錄的路徑名稱ngx_str_t conf_file;/* Nginx處理配置文件時(shí)需要特殊處理的在命令行攜帶的參數(shù),一般是-g選項(xiàng)攜帶的參數(shù) */ngx_str_t conf_param;//Nginx配置文件所在目錄的路徑ngx_str_t conf_prefix;//Nginx安裝目錄的路徑ngx_str_t prefix;//用于進(jìn)程間同步的文件鎖名稱ngx_str_t lock_file;//使用gethostname系統(tǒng)調(diào)用得到的主機(jī)名ngx_str_t hostname; };68.Nginx啟動(dòng)過程的流程圖
? ?
69.worker進(jìn)程正常工作、退出時(shí)的流程圖
? ??
70.master進(jìn)程不需要處理網(wǎng)絡(luò)事件,它不負(fù)責(zé)業(yè)務(wù)的執(zhí)行,只會(huì)通過管理worker等子進(jìn)程來實(shí)現(xiàn)重啟服務(wù)、平滑升級(jí)、更換日志文件、配置文件實(shí)時(shí)生效等功能。
71.epoll是目前Linux操作系統(tǒng)上最強(qiáng)大的事件管理機(jī)制。
72.首先,Nginx定義了一個(gè)核心模塊ngx_events_module,這樣在Nginx啟動(dòng)時(shí)會(huì)調(diào)用ngx_init_cycle方法解析配置項(xiàng),一旦在nginx.conf配置文件中找到ngx_events_module感興趣的“events{}”配置項(xiàng),ngx_events_module模塊就開始工作了。ngx_events_module模塊定義了事件類型的模塊,它的全部工作就是為所有的事件模塊解析"events{}"中的配置項(xiàng),同時(shí)管理這些事件模塊存儲(chǔ)配置項(xiàng)的結(jié)構(gòu)體。
? ? 其次,Nginx定義了一個(gè)非常重要的事件模塊ngx_event_core_module,這個(gè)模塊會(huì)決定使用哪種事件驅(qū)動(dòng)機(jī)制,以及如何管理事件。
? ? 最后,Nginx定義了一系列運(yùn)行在不同操作系統(tǒng)、不同內(nèi)核版本上的事件驅(qū)動(dòng)模塊,包括:ngx_epoll_module、ngx_kqueue_module、ngx_poll_module、ngx_select_module、ngx_devpoll_module、ngx_eventport_module、ngx_aio_module、ngx_rtsig_module和基于Windows的ngx_select_module模塊。在ngx_event_core_module模塊的初始化過程中,將會(huì)從以上模塊中選取一個(gè)作為Nginx進(jìn)程的事件驅(qū)動(dòng)模塊。
73.ngx_connection_t連接池示意圖
? ??
74.所有事件模塊配置項(xiàng)結(jié)構(gòu)體的指針是如何管理的。
??
75.ngx_event_core_module事件模塊啟動(dòng)時(shí)的工作流程
? ? ?
? 76.epoll在Linux內(nèi)核中申請(qǐng)了一個(gè)簡(jiǎn)易的文件系統(tǒng),把原來的一個(gè)select或者poll調(diào)用分成了3個(gè)部分:調(diào)用epoll_create建立1個(gè)epoll對(duì)象(在epoll文件系統(tǒng)中給這個(gè)句柄分配資源)、調(diào)用epoll_ctx向epoll對(duì)象中添加這100萬個(gè)連接的套接字、調(diào)用epoll_wait收集發(fā)生事件的連接。
77.ngx_event_accept方法建立新連接的流程
? ?
78.ngx_process_events_and_times方法中的事件框架處理流程
? ?
79.解析server{}塊內(nèi)配置項(xiàng)的流程
? ?
80.解析location{}配置塊的流程
? ??
81.HTTP框架的初始化流程
? ??
82.接收、解析HTTP請(qǐng)求行的流程圖
? ??
83.ngx_http_process_request_headers方法接收HTTP頭部的流程圖
? ??
84.ngx_http_process_request處理HTTP請(qǐng)求的流程圖
? ??
85.ngx_http_core_run_phases方法的執(zhí)行流程
? ??
86.ngx_http_request_handler方法的執(zhí)行流程
? ??
87.ngx_http_read_client_request_body方法的流程圖
? ??
88.upstream機(jī)制的場(chǎng)景示意圖
? ??
89.ngx_http_upstream_connect方法的流程圖
? ??
90.ngx_http_upstream_send_request方法的流程圖
? ? ?
91.郵件代理功能的示意序列圖
? ??
? ?從網(wǎng)絡(luò)通信的角度來看,Nginx實(shí)現(xiàn)郵件代理功能時(shí)會(huì)把一個(gè)請(qǐng)求分為以下4個(gè)階段。
? ?1) 接收并解析客戶端初始請(qǐng)求的階段;
? ?2) 向認(rèn)證服務(wù)器驗(yàn)證請(qǐng)求合法性,并獲取上游郵件服務(wù)器地址的階段;
? ?3) Nginx根據(jù)用戶信息多次與上游郵件服務(wù)器交互驗(yàn)證合法性的階段;
? ?4) Nginx在客戶端與上游郵件服務(wù)器間純粹透?jìng)鱐CP流的階段。
92.初始化郵件請(qǐng)求的流程
? ??
93.啟動(dòng)郵件認(rèn)證、向認(rèn)證服務(wù)器發(fā)起連接的流程
? ?
94.Nginx框架使用了3種傳遞消息傳遞方式:共享內(nèi)存、套接字、信號(hào)。Nginx各進(jìn)程間共享數(shù)據(jù)的主要方式就是使用共享內(nèi)存.
總結(jié)
以上是生活随笔為你收集整理的《深入理解NGINX 模块开发与架构解析》之摘抄学习的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pstack命令学习
- 下一篇: pread介绍