《深入理解NGINX 模块开发与架构解析》之摘抄学习
1.基于Nginx框架開發程序有5個優勢:
? ? (1).Nginx將網絡、磁盤及定時器等異步事件的驅動都做了非常好的封裝,基于它開發將可以忽略這些事件處理的細節;
? ? (2).Nginx封裝了許多平臺無關的接口、容器,適用于跨平臺開發。
? ? (3) 優秀的模塊化設計,使得開發者可以輕易地復用各種已有的模塊,其中既包括基本的讀取配置、記錄日志等模塊,也包括處理請求的諸如HTTP.mail等高級功能模塊;
? ? (4)?Nginx是作為服務器來設計其框架的,因此,它在服務器進程的管理上相當出色,基于它開發服務器程序可以輕松地實現程序的動態升級,子進程的監控、管理,配置項的動態修改生效等;
? ? (5).Nginx充分考慮到各操作系統所擅長的“絕活”,能夠使用特殊的系統調用更高效地完成任務時,絕不會去使用低效的通用接口。尤其對于Linux操作系統,Nginx不遺余力地做了大量優化。
2.由于默認的Linux內核參數考慮的是最通用的場景,這明顯不符合用于支持高并發訪問的Web服務器的定義,所以需要修改Linux內核參數,使得Nginx可以擁有更高的性能。
? ?最常用的配置:
fs.file-max = 999999 //這個參數表示進程(比如一個worker進程)可以同時打開的最大句柄數 net.ipv4.tcp_tw_reuse = 1 //這個參數設置為1,表示允許將TIME-WAIT狀態的socket重新用于新的TCP連接,這對于服務器來說很有意義,因為服務器上總會有大量TIME-WAIT狀態的連接。 net.ipv4.tcp_keepalive_time = 600 //這個參數表示當前keepalive啟用時,TCP發送keepalive消息的頻度。默認是2小時,若將其設置得小一些,可以更快地清理無效的連接 net.ipv4.tcp_fin_timeout = 30 //這個參數表示當前當服務器主動關閉連接時,socket保持在FIN-WAIT-2狀態的最大時間 net.ipv4.tcp_max_tw_buckets = 5000 //這個參數表示操作系統允許TIME_WAIT套接字數量的最大值,如果超過這個數字,TIME_WAIT套接字將立刻被清除并打印警告信息。 net.ipv4.ip_local_port_range = 1024 61000 //這個參數定義了在UDP和TCP連接中本地(不包括連接的遠端)端口的取值范圍。 net.ipv4.tcp_rmem = 4096 32768 262142 //這個參數定義了TCP接收緩存(用于TCP接受滑動窗口)的最小值、默認值、最大值。 net.ipv4.tcp_wmem = 4096 32768 262142 //這個參數定義了TCP發送緩存(用于TCP發送滑動窗口)的最小值、默認值、最大值。 net.core.netdev_max_backlog = 8096 //當網卡接收數據包的速度大于內核處理的速度時,會有一個隊列保存這些數據包。這個參數表示該隊列的最大值。 net.core.rmem_default = 262144 //這個參數表示內核套接字接收緩存區默認的大小。 net.core.wmem_default = 262144 //這個參數表示內核套接字發送緩存區默認的大小。 net.core.rmem_max = 2097152 //這個參數表示內核套接字接收緩存區的最大大小。 net.core.wmem_max = 2097152 //這個參數表示內核套接字發送緩存區的最大大小。 net.ipv4.tcp_syncookies = 1 //該參數與性能無關,用于解決TCP的SYN攻擊。 net.ipv4.tcp_max_syn_backlog = 1024 //這個參數表示TCP三次握手建立階段SYN請求隊列的最大長度,默認為1024,將其設置得大一些可以使出現Nginx繁忙來不及accept新連接的情況時,Linux不至于丟失客戶端發起的連接請求。3.configure腳本的內容如下:
#!bin/sh# Copyright (C) Igor Sysoev # Copyright (C) Nginx, Inc.#auto/options腳本處理configure命令的參數。例如,如果參數是--help,那么顯示支持的所有參數格式。options腳本會定義后續工作將要用到的變量,然后根據本次參數以及默認值設置這些變量 . auto/options#auto/init腳本初始化后續將產生的文件路徑。例如,Makefile、ngx_modules.c等文件默認情況下會在<nginx-source>/objs/ . auto/init#auto/sources腳本將分析Nginx的源碼結構,這樣才能構造后續的Makefile文件 . auto/sources# 編譯過程中所有目錄文件生成的路徑由--builddir=DIR參數指定,默認情況下為<nginx-source>/objs,此時這個目錄將會被創建 test -d $NGX_OBJS || mkdir $NGX_OBJS# 開始準備建立ngx_auto_headers.h、autoconf.err等必要的編譯文件 echo > $NGX_AUTO_HEADERS_H echo > $NGX_AUTOCONF_ERR# 向objs/ngx_auto_config.h寫入命令行帶的參數 echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H# 判斷DEBUG標志,如果有,那么在objs/ngx_auto_config.h文件中寫入DEBUG宏 if [ $NGX_DEBUG = YES ]; thenhave=NGX_DEBUG . auto/have fi# 現在開始檢查操作系統參數是否支持后續編譯 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名稱、內核版本、32位/64位內核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#檢查并設置編譯器,如GCC是否安裝、GCC版本是否支持后續編譯nginx. auto/cc/conf# 對非Windows操作系統定義一些必要的頭文件,并檢查其是否存在,一次決定configure后續步驟是否可以成功if [ "$NGX_PLATFORM" != win32 ]; then. auto/headersfi# 對于當前操作系統,定義一些特定的操作系統相關的方法并檢查當前環境是否支持。例如,對于Linux,在這里使用sched_sestaffinity設置進程優先級,使用Linux特有的sendfile系統調用來加速向網絡中發送文件塊. auto/os/conf# 定義類UNIX操作系統中通用的頭文件和系統調用等,并檢查當前環境是否支持if [ "$NGX_PLATFORM" != win32 ]; then. auto/unixfi#最核心的構造運行期modules的腳本。它將會生成ngx_modules.c文件,這個文件會被編譯進Nginx中,其中它所做的唯一的事情就是定義了ngx_modules數組。ngx_modules指明Nginx運行期間有哪些模塊會參與到請求的處理中,包括HTTP請求可能會使用哪些模塊處理,因此,它對數組元素的順序非常敏感,也就是說,絕大部分模塊在ngx_modules數組中的順序其實是固定的。例如,一個請求必須先執行ngx_http_gzip_filter_module模塊重新修改HTTP響應中的頭部后,才能使用ngx_http_header_filter模塊按照headers_in結構體里的成員構造出以TCP流形式發送給客戶端的HTTP響應頭部。注意,我們在--add-module=參數里加入的第三方模塊也在此步驟寫入到ngx_modules.c文件中了. auto/modules# conf腳本用來檢查Nginx在鏈接期間需要鏈接的第三方靜態庫、動態庫或者目標文件是否存在. 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安裝后,二進制文件、pid、lock等其他文件的路徑可參見configure參數中路徑類選項的說明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# 創建編譯時使用的objs/Makefile文件. auto/make# 為objs/Makefile加入需要連接的第三方靜態庫、動態庫或者目標文件. auto/lib/make# 為objs/Makefile加入install功能,當執行make install時將編譯生成的必要文件復制到安裝路徑,建立必要的目錄. auto/install# 在ngx_auto_config.h文件中加入NGX_SUPPERSS_WARN宏、NGX_SMP宏. auto/stubs# 在ngx_auto_config.h文件中指定NGX_USER和NGX_GROUP宏,如果執行configure時沒有參數指定,默認兩者皆為nobody(也就是默認以nobody用戶運行進程)have=NGX_USER value="\"$NGX_USER\"" . auto/definehave=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define# 顯示configure執行的結果,如果失敗,則給出原因. auto/summary4.ngx_moduels.c文件就是用來定義ngx_moduels數組的。它指明了每個模塊在Nginx中的優先級,當一個請求同時符合多個模塊的處理規則時,將按照它們在ngx_moduels數組中的順序選擇最靠前的模塊優先處理。對于HTTP過濾模塊而言,在ngx_modules數組中越是靠后的模塊反而會首先處理HTTP響應。
5.日志文件回滾
? ?使用-s reopen參數可以重新打開日志文件,這樣可以先把當前日志文件改名或轉移到其他目錄中進行備份,再重新打開時就會生成新的日志文件。這個功能使得日志文件不至于過大。
6.平滑升級Nginx??
? ?當Nginx服務升級到新的版本時,必須要將舊的二進制文件Nginx替換掉,通常情況下這是需要重啟服務的,但Nginx支持不重啟服務來完成新版本的平滑升級。
? ?升級時包括以下步驟:
? ? 1) 通知正在運行的舊版本Nginx準備升級。通過向master進程發送USR2信號可達到目的。例如:
kill -s SIGUSR2 <nginx master pid>? ? ? 這時,運行中的Nginx會將pid文件重命名,如將/usr/local/nginx/los/nginx.pid重命名為/usr/local/nginx/logs/nginx.pid.oldbin,這樣新的Nginx才有可能啟動成功。
? ? ?2) 啟動新版本的Nginx,可以使用以上介紹過的任意一種啟動方法,這時通過ps命令可以發現新舊版本的Nginx在同時運行。
? ? ?3) 通過kill命令向舊版本的master進程發送SIGQUIT信號,以“優雅”的方式關閉舊版本的Nginx。隨后將只有新版本的Nginx服務運行,此時平滑升級完畢。
7.部署后Nginx進程間的關系
? ?
8.系統調用gettimeofday的執行頻率
? ?默認情況下,每次內核的事件調用(如epoll、select、poll、kqueue等)返回時,都會執行一次gettimeofdata,實現用內核的時鐘來更新Nginx中的緩存時鐘。
9.server_name后可以跟多個主機名稱,如server_name www.testweb.com、download.testweb.com;。
? 在開始處理一個HTTP請求時,Nginx會取出header頭中的Host,與每個server中的server_name進行匹配,以此決定到底由哪一個server塊來處理這個請求。有可能一個Host與多個server塊中的server_name都匹配,這是就會根據匹配優先級來選擇實際處理的server塊。server_name與Host的匹配優先級如下:
? ? 1) 首先選擇所有字符串完全匹配的server_name,如www.testweb.com.
? ? 2)其次選擇通配符在前面的server_name,如 *.testweb.com。
? ? 3)再次選擇通配符在后面的server_name,如www.testweb.*。
? ? 4)最后選擇使用正則表達式才匹配的server_name,如~^\.testweb\.com$。
10.作為靜態Web服務器與反向代理服務器的Nginx
? ??
11.Nginx作為反向代理服務器時轉發請求的流程
? ??
? ?當客戶端發來HTTP請求時,Nginx并不會立刻轉發到上游服務器,而是先把用戶的請求(包括HTTP包體)完整地接收到Nginx所在服務器的硬盤或者內存中,然后再向上游服務器發起連接,把緩存的客戶端請求轉發到上游服務器。而Squid等代理服務器則采用一邊接收客戶端請求,一邊轉發到上游服務器的方式。
? ?Nginx的這種工作方式有什么優缺點呢?很明顯,缺點是延長了一個請求的處理時間,并增加了用于緩存請求內容的內存內核磁盤空間。而優點則是降低了上游服務器的負載,盡量把壓力放在Nginx服務器上。
12.Nginx HTTP模塊調用的簡化流程
? ??
13.在Linux平臺下,Nginx對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成員當做字符串來使用的情況,都可能導致內存越界!
? ?Nginx使用ngx_str_t可以有效地降低內存使用量。例如,用戶請求“GET /test?a=1 http/1.1\r\n”存儲到內存地址0x1d0b0110上,這時只需要把r->method_name設置為{len = 3,data = 0x1d0b0110}就可以表示方法名"GET",而不需要單獨為method_name再分配內存冗余的存儲字符串。
15.ngx_list_t是Nginx封裝的鏈表容器,它在Nginx中使用得很頻繁,例如HTTP的頭部就是用ngx_list_t來存儲的。先看一下ngx_list_t相關成員的定義:
typedef struct ngx_list_part_s ngx_list_part_t;struct ngx_list_part_s {void *elts; //指向數組的起始地址。ngx_uint_t nelts; //表示數組中已經使用了多少個元素,當然,nelts必須小于ngx_list_t結構體中的nalloc.ngx_list_part_t *next; //下一個鏈表元素ngx_list_part_t的地址。 };typedef struct {ngx_list_part_t *last; //指向鏈表的最后一個數組元素ngx_list_part_t part; //鏈表的首個數組元素size_t size; //鏈表中的每個ngx_list_part_t元素都是一個數組。因為數組存儲的是某種類型的數據結構,且ngx_list_t是非常靈活的數據結構,所以它不會限制存儲什么樣的數據,只是通過size限制每一個數組元素的占用的空間大小,也就是用戶要存儲的一個數據所占用的字節數必須小于或等于size。ngx_uint_t nalloc; //鏈表的數組元素一旦分配后是不可更改的。nalloc表示每個ngx_list_part_t數組的容量,即最多可存儲多少個數據。ngx_pool_t *pool; //鏈表中管理內存分配的內存池對象。用戶要存放的數據占用的內存都是由pool分配的。 } ngx_list_t;? ngx_list_t的內存分布情況如下:
? ?
? ?上圖中是由3個ngx_list_part_t數組元素組成的ngx_list_t鏈表中可能擁有的一種內存分布結構。這里,pool內存池為其分配了連續的內存,最前端內存存儲的是ngx_list_t結構中的成員,緊接著是第一個ngx_list_part_t結構占用的內存,然后是ngx_list_part_t結構指向的數組,它們一共占用size*nalloc字節,表示數組中擁有nalloc個大小為size的元素。其后面是第2個ngx_list_part_t結構以及它所指向的數組,依次類推。
16.ngx_table_elt_t數據結構如下所示:
typedef struct {ngx_uint_t hash; //表明ngx_table_elt_t也可以是某個散列表數據結構(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.緩沖區ngx_buf_t是Nginx處理大數據的關鍵數據結構,它既應用于內存數據也應用于磁盤數據。下面主要介紹ngx_buf_t結構體本身:
typedef struct ngx_buf_s ngx_buf_t; typedef void * ngx_buf_tag_t; struct ngx_buf_s {/* pos通常是用來告訴使用者本次應該從pos這個位置開始處理內存中的數據,這樣設置是因為同一個ngx_buf_t可能被多次反復處理,當然,pos的含義是由使用它的模塊定義的*/u_char *pos;/* last通常表示有效的內容到此為止,注意,pos與last之間的內存是希望nginx處理的內容*/u_char *last;/*處理文件時,file_pos與file_last的含義與處理內存時的pos與last相同,file_pos表示將要處理的文件位置,file_last表示截止的文件位置*/off_t file_pos;off_t file_last;//如果ngx_buf_t緩沖區用于內存,那么start指向這段內存的起始地址u_char *start;//與start成員對應,指向緩沖區內存的末尾u_char *end;/* 表示當前緩沖區的類型,例如由哪個模塊使用就指向這個模塊ngx_module_t變量的地址*/ngx_buf_tag_t tag;//引用的文件ngx_file_t *file;/* 當前緩沖區的影子緩沖區,該成員很少用到,僅僅在12.8節描述的使用緩沖區轉發上游服務器的響應時才使用了shadow成員,這是因為Nginx太節約內存了,分配一塊內存并使用ngx_buf_t表示接收到的上游服務器響應后,在向下游客戶端轉發時可能會把這塊內存存儲到文件中,也可能直接向下游發送,此時Nginx絕不會重新復制一份內存用于新的目的,而是再次建立一個ngx_buf_t結構體指向原內存,這樣多個ngx_buf_t結構體指向了同一塊內存,它們之間的關系就通過shadow成員來引用。這種設計過于復雜,通常不建議使用*/ngx_buf_t *shadow;//臨時內存標志位,為1時表示數據在內存中且這段內存可以修改unsigned temporary:1;//標志位,為1時表示數據在內存中且這段內存不可以被修改unsigned memory:1;//標志位,為1時表示這段內存使用mmap系統調用映射過來的,不可以被修改unsigned mmap:1;//標志位,為1時表示可回收unsigned recycled:1;//標志位,為1時表示這段緩沖區處理的是文件而不是內存unsigned in_file:1;//標志位,為1時表示需要執行flush操作unsigned flush:1;/*標志位,對于操作這塊緩沖區時是否使用同步方式,需謹慎考慮,這可能會阻塞Nginx進程,Nginx中所有操作幾乎都是異步的,這是它支持高并發的關鍵。有些框架代碼在sync為1時可能會有阻塞的方式進行I/O操作,它的意義視使用它的Nginx模塊而定*/unsigned sync:1;/*標志位,表示是否是最后一塊緩沖區,因為ngx_buf_t可以由ngx_chain_t鏈表串聯起來,因為,當last_buf為1時,表示當前是最后一塊待處理的緩沖區*/unsigned last_buf:1;//標志位,表示是否是ngx_chain-t中的最后一塊緩沖區unsigned last_in_chain:1;/* 標志位,表示是否是最后一個影子緩沖區,與shadow域配合使用。通常不建議使用它*/unsigned last_shadow:1;//標志位,表示當前緩沖區是否屬于臨時文件unsigned temp_file:1; };18.ngx_chain_t是與ngx_buf_t配合使用的鏈表數據結構,來看一下定義:
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s {ngx_buf_t *buf; //指向當前的ngx_buf_t緩沖區ngx_chain-t *next; //用來指向下一個ngx_chain_t,如果這是最后一個ngx_chain_t,則需要把next置為NULL。 };? 在向用戶發送HTTP包體時,就要傳入ngx_chain_t鏈表對象,注意,如果是最后一個ngx_chain_t,那么必須將next置為NULL,否則永遠不會發送成功,而且這個請求將一直不會結束(Nginx框架的要求).
19.ngx_module_t是一個Nginx模塊的數據結構,如下所示:
typedef struct ngx_module_s ngx_module_t; struct ngx_module_s {/* 下面的ctx_index、index、spare0、spare1、spare2、spare3、version變量不需要在定義時賦值,可以用Nginx準備好的宏NGX_MODULE_V1來定義,它已經定義好了這7個值 #define NGX_MODULE_V1 0,0,0,0,0,0,1對于一類模塊(由下面的type成員決定類別)而言,ctx_index表示當前模塊在這類模塊中的序號。這個成員常常是由管理這類模塊的一個Nginx核心模塊設置的,對于所有的HTTP模塊而言,ctx_index是由核心模塊ngx_http_module設置的。ctx_index非常重要,Nginx的模塊化設計非常依賴于各個模塊的順序,它們既用于表達優先級,也用于表明每個模塊的位置,借以幫助Nginx框架快速獲得某個模塊的數據*/ngx_uint_t ctx_index;/*index表示當前模塊在ngx_modules數組中的序號,注意,ctx_index表示的是當前模塊在一類模塊中的序號,而index表示當前模塊在所有模塊中的序號,它同樣關鍵。Nginx啟動時會根據ngx_modules數組設置各模塊的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;//模塊的版本,便于將來的擴展。目前只有一種,默認為1ngx_uint_t version;/*ctx用于指向一類模塊的上下文結構體,為什么需要ctx呢?因為前面說過,Nginx模塊有許多種類,不同類模塊之間的功能差別很大。例如,事件類型的模塊主要處理I/O事件相關的功能,HTTP類型的模塊主要處理HTTP應用層的功能。這樣,每個模塊都有了自己的特性,而ctx將會指向特定類型模塊的公共接口。例如,在HTTP模塊中,ctx需要指向ngx_http_module_t結構體*/void *ctx;//commands將處理nginx.conf中的配置項ngx_command_t *commands;/*type表示該模型的類型,它與ctx指針是緊密相關的。在官方Nginx中,它的取值范圍是以下5種:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE。*/ngx_uint_t type;/*在Nginx的啟動、停止過程中,以下7個函數指針表示有7個執行點會分別調用者7種方法。對于任一個方法而言,如果不需要Nginx在某個時刻執行它,那么簡單地把它設為NULL空指針即可*//*雖然從字面上理解應當在master進程啟動時回調init_master,但到目前為止,框架代碼從來不會調用它,因此,可將init_master設為NULL */ngx_int_t (*init_master)(ngx_lot_t *log);/* init_module回調方法在初始化所有模塊時被調用。在master/worker模式下,這個階段將在啟動worker子進程前完成*/ngx_int_t (*init_module)(ngx_cycle_t *cycle);/*init_process回調方法在正常服務前被調用。在master/worker模式下,多個worker子進程已經產生,在每個worker進程的初始化過程會調用所有模塊的init_process函數 */ngx_int_t (*init_process)(ngx_cycle_t *cycle);/*由于Nginx暫不支持多線程模式,所以init_thread在框架代碼中沒有被調用過,設為NULL*/ngx_int_t (*init_thread)(ngx_cycle_t *cycle);//同上,exit_thread也不支持,設為NULL.void (*exit_process)(ngx_cycle_t *cycle);//exit_master回調方法將在master進程退出前被調用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框架在讀取、重載配置文件時定義了由ngx_http_module_t接口描述的8個階段,HTTP框架在啟動過程中會在每一個階段中調用ngx_http_module_t中相應的方法。
typedef struct {//解析配置文件前調用ngx_int_t (*preconfiguration)(ngx_conf_t *cf);//完成配置文件的解析后調用ngx_int_t (*postconfiguration)(ngx_conf_t *cf);/*當需要創建數據結構用于存儲main級別(直屬于http{...}塊的配置項)的全局配置項時,可以通過create_main_conf回調方法創建存儲全局配置項的結構體 */void *(*create_main_conf)(ngx_conf_t *cf);//常用于初始化main級別配置項char *(*init_main_conf)(ngx_conf_t *cf, void *conf);/* 當需要創建數據結構用于存儲srv級別(直屬于虛擬主機server{...}塊的配置項)的配置項時,可以通過實現create_srv_conf回調方法創建存儲srv級別配置項的結構體 */void *(*create_srv_conf)(ngx_conf_t *cf);//merge_srv_conf回調方法主要用于合并main級別和srv級別下的同名配置項char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);/*當需要創建數據結構用于存儲loc級別(直屬于location{...}塊的配置項)的配置項時,可以實現create_loc_conf回調方法*/void *(*create_loc_conf)(ngx_conf_t *cf);//merge_loc_conf回調方法主要用于合并srv級別和loc級別下的同名配置項char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_t;21.每一個ngx_command_t結構體定義了自己感興趣的一個配置項:
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s {//配置項名稱,如"gzip"ngx_str_t name;/*配置項類型,type將指定配置項可以出現的位置。例如,出現在server{}或location{}中,以及它可以攜帶的參數個數 */ngx_uint_t type;//出現了name中指定的配置項后,將會調用set方法處理配置項的參數char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//在配置文件中的偏移量ngx_uint_t conf;/*通常用于使用預設的解析方法解析配置項,這是配置模塊的一個優秀設計。*/ngx_uint_t offset;//配置項讀取后的處理方法,必須是ngx_conf_post_t結構的指針void *post; };? ?ngx_null_command只是一個空的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前,這時rewrite重寫URL也作為一個獨立的HTTP階段*/NGX_HTTP_SERVER_REWRITE_PHASE,/*根據URI尋找匹配的location,這個階段通常由ngx_http_core_module模塊實現,不建議其他HTTP模塊重新定義這一階段的行為*/NGX_HTTP_FIND_CONFIG_PHASE,/*在NGX_HTTP_FIND_CONFIG_PHASE階段之后重寫URL的意義與NGX_HTTP_SERVER_REWRITE_PHASE階段顯然是不同的,因為這兩者會導致查找到不同的location塊(location是與URI進行匹配的) */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,/*這個階段用于讓HTTP模塊判斷是否允許這個請求訪問Nginx服務器*/NGX_HTTP_ACCESS_PHASE,/*當NGX_HTTP_ACCESS_PHASE階段中HTTP模塊的handler處理方法返回不允許訪問的錯誤碼時(實際是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),這個階段將負責構造拒絕服務的用戶響應。所以,這個階段實際上用于給NGX_HTTP_ACCESS_PHASE階段收尾*/NGX_HTTP_POST_ACCESS_PHASE,/*這個階段完全是為了try_files配置項而設計的。當HTTP請求訪問靜態文件資源時,try_files配置項可以使這個請求順序地訪問多個靜態文件資源,如果某一次訪問失敗,則繼續訪問try_files中指定的下一個靜態資源。另外,這個功能完全是在NGX_HTTP_TRY_FILES_PHASE階段中實現的 */NGX_HTTP_TRY_FILES_PHASE,//用于處理HTTP請求內容的階段,這是大部分HTTP模塊最喜歡介入的階段NGX_HTTP_CONTENT_PHASE,/* 處理完請求后記錄日志的階段,例如,ngx_http_log_module模塊就在這個階段中加入了一個handler處理方法,使得每個HTTP請求處理完畢后會記錄access_log日志 */NGX_HTTP_LOG_PHASE } ngx_http_phases;23.處理方法的返回值,其中包括了HTTP框架已經在/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.請求的所有信息(如方法、URI、協議版本號和頭部等)都可以在傳入的ngx_http_request_t類型參數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; //表示沒有進行URL解碼的原始請求。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則存儲已經解析過的HTTP頭部。
typedef struct {/* 所有解析過的HTTP頭部都在headers鏈表中,可以使用遍歷鏈表的方法來獲取所有的HTTP頭部。注意:這里headers鏈表的每一個元素都是ngx_table_elt_t成員*/ngx_list_t headers;/*以下每個ngx_table_elt_t成員都是RFC1616規范中定義的HTTP頭部,它們實際都指向headers鏈表中的響應成員。注意,當它們為NULL空指針時,表示沒有解析到響應的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才會用到的成員,這里可以忽略*/ngx_str_t user;ngx_str_t passwd;/* cookies是以ngx_array_t數組存儲的。*/ngx_array_t cookies;//server名稱ngx_str_t server;//根據ngx_table_elt_t *content_length計算出的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個標志位是HTTP框架根據瀏覽器傳來的"useragent"頭部,它們可用來判斷瀏覽器的類型,值為1時表示是相應的瀏覽器發來的請求,值為0時則相反 */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包體的長度有可能非常大,如果視圖一次性調用并讀取完所有的包體,那么多半會阻塞Nginx進程,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是一個異步方法,調用它只是說明要求Nginx開始接收請求的包體,并不表示是否已經接收完,當接收完所有的包體內容后,post_handler指向的回調方法會被調用。因此,即使在調用了ngx_http-read_client_request_body方法后它已經返回,也無法確定這時是否已經調用過post_handler指向的方法。換句話說,ngx_http_read_client_request_body返回時既有可能已經接收完請求中所有的包體(假設包體的長度很小),也有可能還沒開始接受包體。
27.HTTP框架提供的發送HTTP頭部的方法如下所示:
ngx_int_t ngx_http_send_header(ngx_http_request_t *r);28.headers_out的結構類型ngx_http_headers_out_t:
typedef struct {//待發送的HTTP頭部鏈表,與headers_in中的headers成員類似ngx_list_t headers;/*響應中的狀態值,如200表示成功。*/ngx_str_t status;//響應的狀態行,如"HTTP/1.1 201 CREATED"ngx_str_t status_line;/*以下成員(包括ngx_table_elt_t)都是RFC1616規范中定義的HTTP頭部,設置后,ngx_http_header_filter_module過濾模塊可以把它們加到待發送的網絡包中*/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;/* 可以調用ngx_http_set_content_type(r)方法幫助我們設置Content-Type頭部,這個方法會根據URI中的文件擴展名餅對應著mime.type來設置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中設置響應長度*/off_t content_length_n;time_t date_time;time_t last_modified_time; } ngx_http_headers_out_t;? ?ngx_http_send_header方法會首先調用所有的HTTP過濾模塊共同處理headers_out中定義的HTTP響應頭部,全部處理完畢后才會序列化為TCP字符流發送到客戶端。
28.注意:在向用戶發送響應包體時,必須牢記Nginx是全異步的服務器,也就是說,不可以在進程的棧里分配內存并將其作為包體發送。當ngx_http_output_filter方法返回時,可能由于TCP連接上的緩沖區還不可寫,所以導致ngx_buf_t緩沖區指向的內存還沒有發送,可這時方法返回已把控制權交給Nginx了,又會導致棧里的內存被釋放,最后就會造成內存越界錯誤。因此,在發送響應包體時,盡量將ngx_buf_t中的pos指針指向從內存池里分配的內存。
29.經典的"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;}//丟棄請求中的包體ngx_int_t rc = ngx_http_discard_request_body(r);if (rc != NGX_OK) {return rc;}/* 設置返回的Content-Type.注意,ngx_str_t有一個很方便的初始化宏ngx_string,它可以把ngx_str_t的data和len成員都設置好 */ngx_str_t type = ngx_string("text/plain");//返回的包體內容ngx_str_t type = ngx_string("Hello World!");//設置返回狀態碼r->headers_out.status = NGX_HTTP_OK;//響應包是有包體內容的,需要設置Content-Lenght的長度r->headers_out.content_length_n = response.len;//設置Content-Typer->headers_out.content_type = type;//發送HTTP頭部rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {return rc;}//構造ngx_buf_t結構體準備發送包體ngx_buf_t *b;b = ngx_create_temp_buf(r->pool, response.len);if (b == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}//將Hello World復制到ngx_buf_t指向的內存中ngx_memcpy(b->pos, response.data, response.len);//注意,一定要設置好last指針b->last = b->pos + response.len;//聲明這是最后一塊緩沖區b->last_buf = 1;//構造發送時的ngx_chain_t結構體ngx_chain_t out;//賦值ngx_buf_tout.buf = b;//設置next為NULLout.next = NULL;/*最后一步為發送包體,發送結束后HTTP框架會調用ngx_http_finalize_request方法結束請求 */return ngx_http_output_filter(r, &out); }30.ngx_file_t的結構如下:
typedef struct ngx_file_s ngx_file_t; struct ngx_file_s {//文件句柄描述符ngx_fd_t fd;//文件名稱ngx_str_t name;//文件大小等資源信息,實際就是Linux系統定義的stat結構ngx_file_info_t info;/*該偏移量告訴Nginx現在處理到文件何處了,一般不用設置它,Nginx框架會根據當前發送狀態設置它*/off_t offset;//當前文件系統偏移量,一般不用設置它off_t sys_offset;//日志對象,相關的日志會輸出到log指定的日志文件中ngx_log_t *log;//目前未使用unsigned valid_info:1;//與配置文件中的directio配置項相對應,在發送大文件時可以設為1unsigned directio:1; };? Nginx不只對stat數據結構做了封裝,對于由操作系統中獲取文件信息的stat方法,Nginx也使用一個宏進行了簡單的封裝,如下:
#define ngx_file_info(file, sb) stat((const char *) file, sb)? ?之后必須要設置Content-Length頭部:
r->headers_out.content_length_n = b->file->info.st_size;? ?還需要設置ngx_buf_t緩沖區的file_pod和file_last:
b->file_pos = 0; b->file_last = b->file->info.st_size;? ?這里告訴Nginx從文件的file_pos偏移量開始發送文件,一直到達file_last偏移量處截止。
31. HTTP框架定義了3個級別的配置main、srv、loc,分別表示直接出現在http{}、server{}、location{}塊內的配置項。當nginx.conf中出現http{}時,HTTP框架會接管配置文件中http{}塊內的配置項解析。當遇到http{...}配置塊時,HTTP框架會調用所有HTTP模塊可能實現的create_main_conf、create_srv_conf、create_loc_conf方法生成存儲main級別配置參數的結構體;在遇到server{...}塊時會再次調用所有HTTP模塊的create_srv_conf、create_loc_conf回調方法生成存儲srv級別配置參數的結構體;在遇到location{...}時則會再次調用create_loc_conf回調方法生成存儲loc級別配置參數的結構體。因此,實現這3個回調方法的意義是不同的。
32.設定配置項的解析方式
? ? ngx_command_t結構詳解
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是配置項名稱。
? ? (2) ngx_uint_t type
? ? ? ? 其中,type決定這個配置項可以在哪些塊(如http、server、location、if、upstream塊等)中出現,以及可以攜帶的參數類型和個數等。
? ? ? ? ? ngx_command_t結構體中type成員的取值及其意義
| type類型 | type取值 | 意義 |
| 處理配置項時獲取當前配置塊的方式 | NGX_DIRECT_CONF | 一般由NGX_CORE_MODULE類型的核心模塊使用,僅與下面的NGX_MAIN_CONF同時設置,表示模塊需要解析不屬于任何{}內的全局配置項。它實際上會指定set方法里的第3個參數conf的值,使之指向每個模塊解析全局配置項的配置結構體(表下面解釋) |
| NGX_ANY_CONF | 目前未使用,設置與否均無意義 | |
| 配置項可以在哪些{}配置塊中出現 | NGX_MAIN_CONF | 配置項可以出現在全局配置中,即不屬于任何{}配置塊 |
| NGX_EVENT_CONF | 配置項可以出現在events{}塊內 | |
| NGX_MAIL_MAIN_CONF | 配置項可以出現在mail{}塊或者imap{}塊內 | |
| NGX_MAIL_SRV_CONF | 配置項可以出現在server{}塊內,然而該server{}塊必須屬于mail{}塊或者imap{}塊 | |
| NGX_HTTP_MAIN_CONF | 配置項可以出現在http{}塊內 | |
| NGX_HTTP_SRV_CONF | 配置項可以出現在server{}塊內,然而該server塊必須屬于http{}內 | |
| NGX_HTTP_LOC_CONF | 配置項可以出現在location{}塊內,然而該location塊必須屬于http{}內 | |
| NGX_HTTP_UPS_CONF | 配置項可以出現在upstream{}塊內,然而該upstream塊必須屬于http{}塊 | |
| NGX_HTTP_SIF_CONF | 配置項可以出現在server塊內的if{}塊中。目前僅有rewrite模塊會使用,該if塊必須屬于http{}塊 | |
| NGX_HTTP_LIF_CONF | 配置項可以出現在location塊內的if{}塊中。目前僅有rewrite模塊會使用,該if塊必須屬于http{}塊 | |
| NGX_HTTP_LMT_CONF | 配置項可以出現在limit_except{}塊內,然而該limit_except塊必須屬于http{}塊 | |
| 限制配置項的參數個數 | NGX_CONF_NOARGS | 配置項不攜帶任何參數 |
| NGX_CONF_TAKE1 | 配置項必須攜帶1個參數 | |
| NGX_CONF_TAKE2 | 配置項必須攜帶2個參數 | |
| NGX_CONF_TAKE3 | 配置項必須攜帶3個參數 | |
| NGX_CONF_TAKE4 | 配置項必須攜帶4個參數 | |
| NGX_CONF_TAKE5 | 配置項必須攜帶5個參數 | |
| NGX_CONF_TAKE6 | 配置項必須攜帶6個參數 | |
| NGX_CONF_TAKE7 | 配置項必須攜帶7個參數 | |
| NGX_CONF_TAKE12 | 配置項可以攜帶1個參數或2個參數 | |
| NGX_CONF_TAKE13 | 配置項可以攜帶1個參數或3個參數 | |
| NGX_CONF_TAKE23 | 配置項可以攜帶2個參數或3個參數 | |
| NGX_CONF_TAKE123 | 配置項可以攜帶1~3個參數 | |
| NGX_CONF_TAKE1234 | 配置項可以攜帶1~4個參數 | |
| 限制配置項后的參數出現的形式 | NGX_CONF_ARGS_NUMBERS | 目前未使用,無意義 |
| NGX_CONF_BLOCK | 配置項定義了一種新的{}塊。例如,http、server、location等配置,它們的ttype都必須定義為NGX_CONF_BLOCK | |
| NGX_CONF_ANY | 不驗證配置項攜帶的參數個數 | |
| NGX_CONF_FLAG | 配置項攜帶的參數只能是1個,并且參數的值只能是on或者off | |
| NGX_CONF_1MORE | 配置項攜帶的參數個數必須超過1個 | |
| NGX_CONF_2MORE | 配置項攜帶的參數個數必須超過2個 | |
| NGX_CONF_MULTI | 表示當前配置項可以出現在任意塊中(包括不屬于任何塊的全局配置),它僅用于配合其他配置項使用。type中未加入NGX_CONF_MULTI時,如果一個配置項出現在type成員未標明的配置塊中,那么Nginx會認為該配置項非法,最后將導致Nginx啟動失敗。但如果type中加入了NGX_CONF_MULTI,則認為該配置項一定是合法的,然而又會有兩種不同的結果: a.如果配置項出現在type指示的塊中,則會調用set方法解析配置項;b.如果配置項沒有出現在type指示的塊中,則不對該配置項做任何處理。因此,NGX_CONF_MULTI會使得配置項出現在未知塊中時不會出錯。目前,還沒有官方模塊使用過NGX_CONF_MULTI. |
解釋:每個進程總都有一個唯一的ngx_cycle_t核心結構體,它有一個成員conf_ctx維護著所有模塊的配置結構體,其類型是void ****。conf_ctx意義為首先指向一個成員皆為指針的數組,其中每個成員指針又指向另外一個成員皆為指針的數組,第2個子數組中的成員指針才會指向各模塊生成的配置結構體。這正是為了事件模塊、http模塊、mail模塊而設計的,這有利于不同于NGX_CORE_MODULE類型的特定模塊解析配置項。然而,NGX_CORE_MODULE類型的核心模塊解析配置項時,配置項一定是全局的,不會從屬于任何{}配置塊的,它不需要上述這種雙數組設計。解析標識為NGX_DIRECT_CONF類型的配置項時,會把void ****類型的conf_ctx強制轉換為void **,也就是說,此時,在conf_ctx指向的指針數組中,每個成員指針不再指向其他數組,直接指向核心模塊生成的配置結構體。因此,NGX_DIRECT_CONF僅由NGX_CORE_MODULE類型的核心模塊使用,而且配置項只應該出現在全局配置中。
? ?c. char*(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
? ? 預設的14個配置項解析方法
| 預設方法名 | 行為 |
| ngx_conf_set_flag_slot | 如果ngxin.conf文件中某個配置項的參數是on或者off(即希望配置項表達打開或者關閉某個功能的意思),而且在Nginx模塊的代碼中使用ngx_flag_t變量來保存這個配置項的參數,就可以將set回調方法設為ngx_conf_set_flag_slot。當nginx.conf文件中參數是on時,代碼中的ngx_flag_t類型變量將設為1,參數為off時則設為0 |
| ngx_conf_set_str_slot | 如果配置項后只有1個參數,同時在代碼中我們希望用ngx_str_t類型的變量來保存這個配置項的參數,則可以使用ngx_conf_set_str_slot方法 |
| ngx_conf_set_str_array_slot | 如果這個配置項會出現多次,每個配置項后面都跟著1個參數,而在程序中我們希望僅用一個ngx_array_t動態數組來存儲所有的參數,且數組中的每個參數都以ngx_str_t來存儲,那么預設的ngx_conf_set_str_array_slot方法可以幫我們做到 |
| ngx_conf_set_keyval_slot | 與ngx_conf_set_str_array_slot類似,也使用用一個ngx_array_t數組來存儲所有同名配置項的參數。只是每個配置項的參數不再只有1個,而必須是兩個,且以“配置項名 關鍵字 值;”的形式出現在nginx.conf文件中,同時,ngx_conf_set_keyval_slot將把這些配置項轉化為數組,其中每個元素都存儲著key/value鍵值對。 |
| ngx_conf_set_num_slot | 配置項后必須攜帶1個參數,且只能是數字。存儲這個參數的變量必須是整形 |
| ngx_conf_set_size_slot | 配置項后必須攜帶1個參數,表示空間大小,可以是一個數字,這時表示字節數(Byte)。如果數字后跟著k或者K,就表示Kilobyt,1KB=1024B;如果數字后跟著m或者M,就表示Megabyte,1MB=1024KB。ngx_conf_set_size_slot解析后將把配置項后的參數轉化成以字節數為單位的數字 |
| ngx_conf_set_off_slot | 配置項后必須攜帶1個參數,表示空間上的偏移量。它與設置的參數非常類似,其參數是一個數字時表示Byte,也可以在后面加單位,但與ngx_conf_set_size_slot不同的是,數字后面的單位不僅可以是k或者K、m或者M,還可以是g或者G,這時表示Gigabyte,1GB=1024MB,ngx_conf_set_off_slot解析后將把配置項后的參數轉化成以字節數為單位的數字 |
| ngx_conf_set_msec_slot | 配置項后必須攜帶1個參數,表示時間。這個參數可以在數字后面加單位,如果單位為s或者沒有任何單位,那么這個數字表示秒;如果單位為m,則表示分鐘,1m=60s;如果單位為h,則表示小時,1h=60m;如果單位為d,則表示天,1d=24h;如果單位為w,則表示周,1w=7d;如果單位為M,則表示月,1M=30d;如果單位為y,則表示年,1y=265d。ngx_conf_set_msec_slot解析后將把配置項后的參數轉化成以毫秒為單位的數字 |
| ngx_conf_set_sec_slot | 與ngx_conf_set_msec_slot非常類似,唯一的區別是ngx_conf_set_msec_slot解析后將把配置項后的參數轉化成以毫秒為單位的數字,而ngx_conf_set_sec_slot解析后會把配置項后的參數轉化成以秒為單位的數字 |
| ngx_conf_set_bufs_slot | 配置項后必須攜帶一兩個參數,第1個參數是數字,第2個參數表示空間大小。例如:"gzip_buffers 4 8k;"(通常用來表示有多少個ngx_buf_t緩沖區),其中第1個參數不可以攜帶任何單位,第2個參數不帶任何單位時表示Byte,如果yik或者K作為單位,則表示Kilobyte,如果以m或者M作為單位,則表示Megabyte。ngx_conf_set_bufs_slot解析后會把配置項后的兩個參數轉化成ngx_bufs_t結構體下的兩個成員,這個配置項對應于Nginx最喜歡用的多緩沖區的解決方案(如接受連接對端發來的TCP流) |
| ngx_conf_set_enum_slot | 配置項后必須攜帶1個參數,其取值范圍必須是我們設定好的字符串之一(就像C語言中的枚舉一樣),首先,我們要用ngx_conf_enum_t結構定義配置項的取值范圍,并設定每個值對應的序列號,然后,ngx_conf_set_enum_slot將會把配置項參數轉化為對應的序列號。 |
| ngx_conf_set_bitmask_slot | 與ngx_conf_set_bitmask_slot類似,配置項后必須攜帶1個參數,其取值范圍必須是設定好的字符串只注意。首先,我們要用ngx_conf_bitmask_t結構定義配置項的取值范圍,并設定每個值對應的比特位。注意,每個值所對應的比特位都要不同,然后ngx_conf_set_bitmask_slot將會把配置項參數轉化為對應的比特位 |
| ngx_conf_set_access_slot | 這個方法用于設置目錄或者文件的讀寫權限。配置項后可以攜帶1~3個參數,可以是如下形式:user:rw group:rw all:rw。注意,它的意義與Linux上文件或者目錄的權限意義視一致的,但是user/group/all后面的權限只可以設為rw(讀/寫)或者r(只讀),不可以有其他任何形式,如w或者rx等。ngx_conf_set_access_slot將會把這些參數轉化為一個整形。 |
| ngx_conf_set_path_slot | 這個方法用于設置路徑,配置項后必須攜帶1個參數,表示1個有意義的路徑。ngx_conf_set_path_slot將會把參數轉化為ngx_path_t結構。 |
? ? d.ngx_uint_t conf
? ? ? ?conf用于指示配置項所處內存的相對偏移位置,僅在type中沒有設置NGX_DIRECT_CONF和NGX_MAIN_CONF時才會生效。對于HTTP模塊,conf是必須要設置的,它的取值范圍如下:
| conf在HTTP模塊中的取值 | 意義 |
| NGX_HTTP_MAIN_CONF_OFFSET | 使用create_main_conf方法產生的結構體來存儲解析出的配置項參數 |
| NGX_HTTP_SRV_CONF_OFFSET | 使用create_srv_conf方法產生的結構體來存儲解析出的配置項參數 |
| NGX_HTTP_LOC_CONF_OFFSET | 使用create_loc_conf方法產生的結構體來存儲解析出的配置項參數 |
? ? e.ngx_uint_t offset
? ? ? offset表示當前配置項在整個存儲配置項啊的結構體中的偏移位置(以字節(Byte)為單位)。
? ? f.void *post
? ? ?一般置為NULL。
33.解析HTTP配置的流程
? ??
? ?(1) 主循環是指Nginx進程的主循環,主循環只有調用配置文件解析器才能解析nginx.conf文件(這里的"主循環"是指解析全部配置文件的循環代碼)。
? ?(2) 當發現配置文件中含有http{}關鍵字時,HTTP框架開始啟動,這一過程見ngx_http_block方法。
? ?(3) HTTP框架會初始化所有HTTP模塊的序列號,并創建3個數組用于存儲所有HTTP模塊的create_main_conf、create_srv_conf、create_loc_conf方法返回的指針地址,并把這3個數組的地址保存到ngx_http_conf_ctx_t結構中。
? ?(4) 調用每個HTTP模塊(當然也包括例子中的mytest模塊)的create_main_conf、create_srv_conf、create_loc_conf(如果實現的話)方法。
? ?(5) 把各HTTP模塊上述3個方法的地址依次保存到ngx_http_conf_ctx_t結構體的3個數組中。
? ?(6) 調用每個HTTP模塊的preconfiguration方法(如果實現的話).
? ?(7) 注意,如果preconfiguration返回失敗,那么Nginx進程將會停止。
? ?(8) HTTP框架開始循環解析nginx.conf文件中http{...}里面的所有配置項,注意,這個過程到第19步才會返回。
? ?(9) 配置文件解析器在檢測到1個配置項后,會遍歷所有的HTTP模塊,檢查它們的ngx_command_t數組中的name項是否與配置項名相同。
? ? (10) 如果找到有1個HTTP模塊對這個配置項感興趣,就調用ngx_command_t結構中的set方法來處理。
? ? (11) set方法返回是否處理成功。如果處理失敗,那么Nginx進程會停止。
? ? (12) 配置文件解析器繼續監測配置項。如果發現server{...}配置項,就會調用ngx_http_core_module模塊來處理。因為ngx_http_core_module模塊明確表示希望處理server{}塊下的配置項。注意,這次調用到第18步才會返回。
? ? (13) ngx_http_core_module模塊在解析server{...}之前,也會如第3步一樣建立ngx_http_conf_ctx_t結構,并建立數組保存所有HTTP模塊返回的指針地址。然后,它會調用每個HTTP模塊的create_srv_conf、create_loc_conf方法(如果實現的話).
? ? (14) 將上一步各HTTP模塊返回的指針地址保存到ngx_http_conf_ctx_t對應的數組中。
? ? (15) 開始調用配置文件解析器來處理server{...}里面的配置項,注意,這個過程在第17步返回。
? ? (16) 繼續重復第9步的過程,遍歷nginx.conf中當前server{...}內的所有配置項。
? ? (17)配置文件解析器繼續解析配置項,發現當前server塊已經遍歷到尾部,說明server塊內的配置項處理完畢,返回ngx_http_core_module模塊。
? ? (18) http core模塊也處理完server配置項了,返回至配置文件解析器繼續解析后面的配置項。
? ? (19) 配置文件解析器繼續解析配置項,這時發現處理到了http{...}的尾部,返回給HTTP框架繼續處理。
? ? (20) 在第3步和第13步,以及我們沒有列出來的某些步驟中(如發現其他server塊或者location塊),都創建了ngx_http_conf_ctx_t結構體,這時將開始調用merge_srv_conf、merge_loc_conf等方法合并這些不同塊(http、server、location)中每個HTTP模塊分配的數據結構。
? ? (21) HTTP框架處理完畢http配置項(也就是ngx_command_t結構中的set回調方法處理完畢),返回給配置文件解析器繼續處理其他http{...}外的配置項。
? ? (22) 配置文件解析器處理完所有配置項后會告訴Nginx主循環配置項解析完畢,這時Nginx才會啟動Web服務器。
34.http塊與server塊下的ngx_http_conf_ctx_t所指向的內存間的關系
? ?
35.合并配置項過程的活動圖,主要包含四大部分內容:
? ?(1) 如果HTTP模塊實現了merge_srv_conf方法,就將http{...}塊下create_srv_conf生成的結構體與遍歷每一個server{...}配置塊下的結構體做merge_srv_conf操作;
? ?(2) 如果HTTP模塊實現了merge_loc_conf方法,就將http{...}塊下create_loc_conf生成的結構體與嵌套的每一個server{...}配置塊下生成的結構體做merge_loc_conf操作;
? ?(3) 如果HTTP模塊實現了merge_loc_conf方法,就將server{...}塊下create_loc_conf生成的結構體與嵌套的每一個location{...}配置塊下create_loc_conf生成的數據結構做merge_loc_conf操作;
? ?(4) 如果HTTP模塊實現了merge_loc_conf方法,就將location{...}塊下create_loc_conf生成的結構體與繼續嵌套的每一個location{...}配置塊下create_loc_conf生成的數據結構做merge_loc_conf操作。
? ?
? 上圖中包括4重循環,第1層(最外層)遍歷所有的HTTP模塊,第2層遍歷所有的server{...}配置塊,第3層是遍歷某個server{}塊中嵌套的所有location{...}塊,第4層遍歷某個location{}塊中繼續嵌套的所有location塊(實際上,它會一直遞歸下去以解析可能被層層嵌套的location塊).
36.請求的上下文
? ? 在Nginx中,上下文有很多種含義。HTTP框架定義的這個上下文是針對于HTTP請求的,而且一個HTTP請求對應于每一個HTTP模塊都可以有一個獨立的上下文結構體(并不是一個請求的上下文由所有HTTP模塊共用)。
37.ngx_http_get_module_ctx和ngx_http_set_ctx這兩個宏可以完成HTTP上下文的設置和使用。
#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;? ?當請求第1次進入mytest模塊處理時,創建ngx_http_mytest_ctx_t結構體,并設置到這個請求的上下文中。
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) {//首先調用ngx_http_get_module_ctx宏來獲取上下文結構體ngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);//如果之前沒有設置過上下文,那么應當返回NULL。if (myctx == NULL) {/*必須在當前請求的內存池r->pool中分配上下文結構體,這樣請求結束時結構體占用的內存才會釋放*/myctx = ngx_palloc(r->pool, sizeof(ngx_http_mytest_ctx_t));if (myctx == NULL) {return NGX_ERROR;}//將剛分配的結構體設置到當前請求的上下文中ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);}//之后可以任意使用myctx這個上下文結構體... }38.模塊在處理任何一個請求時都有ngx_http_request_t結構的對象r,而請求r中又有一個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.啟動upstream的流程圖
? ?
40.upstream執行的一般流程
??
41.ngx_http_upstream_t結構體
typedef struct ngx_http_upstream_s ngx_http_upstream_t; struct ngx_http_upstream_s {.../* request_bufs決定發送什么樣的請求給上游服務器,在實現create_request方法是需要設置它 */ngx_chain_t *request_bufs;//upstream訪問時的所有限制性參數ngx_http_upstream_conf_t *conf;//通過resolved可以直接指定上游服務器地址ngx_http_upstream_resolved_t *resolved;/* buffer成員存儲接收自上游服務器發來的響應內容,由于它會被復用,所以具有下例多種意義:* a) 在使用process_header方法解析上游響應的包頭時,buffer中將會保存完整的響應包頭;* b) 當下面的buffering成員為1,而且此時upstream是向下游轉發上游的包體時,buffer沒有意義;* c) 當buffering標志位位0時,buffer緩沖區會被用于反復地接收上游的包體,進而向下游轉發;* d) 當upstream并不用于轉發上游包體時,buffer會被用于反復接收上游的包體,HTTP模塊實現的input_filter方法需要關注它*/ngx_buf_t buffer;//構造發往上游服務器的請求內容ngx_int_t (*create_request)(ngx_http_request_t *r);/* 銷毀upstream請求時調用 */void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);//5個可選的回調方法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協議訪問上游服務器unsigned ssl:1;/* 在向客戶端轉發上游服務器的包體時才有用。當buffering為1時,表示使用多個緩沖區以及磁盤文件來轉發上有的響應包體。當Nginx與上游間的網速遠大于Nginx與下游客戶端見的網速時,讓Nginx開辟更多的內存甚至使用磁盤文件來緩存上游的響應包體,這是有意義的,它可以減輕上游服務器的并發壓力。當buffering為0時,表示只使用上面的這一個buffer緩沖區來向下游轉發響應包體*/unsigned buffering:1;... };42.設置upstream的限制性參數
? ? ngx_http_upstream_t中的conf成員,它用于設置upstream模塊處理請求時的參數,包括連接、發送、接收的超時時間等。
typedef struct {...//連接上游服務器的超時時間,單位為毫秒ngx_msec_t connect_timeout;//發送TCP包到上游服務器的超時時間,單位為毫秒ngx_msec_t send_timeout;//接收TCP包到上游服務器的超時時間,單位為毫秒ngx_msec_t read_timeout;... } ngx_http_upstream_conf_t;43.ngx_http_upstream_t結構中的resolved成員可以直接設置上游服務器的地址,首先介紹一下resolved的類型:
typedef struct {...//地址個數ngx_uint_t naddrs;//上游服務器的地址struct sockaddr *sockaddr;socklen_t socklen;... } ngx_http_upstream_resolved_t;44.直接執行ngx_http_upstream_init方法即可啟動upstream機制。
45.create_request回調方法
? ??
? ? 如上圖,步驟分別如下:
? ? 1) 在Nginx主循環(之類的主循環是指ngx_worker_process_cycle方法)中,會定期地調用事件模塊,以檢查是否有網絡事件發生。
? ? 2)事件模塊在接收到HTTP請求后會調用HTTP框架來處理。假設接收、解析完HTTP頭部后發現應該由mytest模塊處理,這時會調用mytest模塊的ngx_http_mytest_handler來處理。
? ? 3)設置回調函數和第三方地址;
? ? 4)調用ngx_http_upstream_init方法啟動upstream;
? ? 5)upstream模塊會去檢查文件緩存,如果緩存中已經有合適的響應包,則會直接返回緩存(當然必須是在使用反向代理文件緩存的前提下)。
? ? 6)回調mytest模塊已經實現的create_request回調方法;
? ? 7)mytest模塊通過設置r->upstream->request_bufs已經決定好發送什么樣的請求到上有服務器。
? ? 8)upstream模塊會檢查已經介紹過的resolved成員,如果有resolved成員的話,就根據它設置好上游服務器的地址r->upstream->peer成員。
? ? 9)用無阻塞的TCP套接字建立連接;
? ? 10)無論連接是否建立成功,負責建立連接的connect方法都會立刻返回。
? ? 11)ngx_http_upstream_init返回;
? ? 12)mytest模塊的ngx_http_mytest_handler方法返回NGX_DONE。
? ? 13)當事件模塊處理完這批網絡事件后,將控制權交還給Nginx主循環.
46.reinit_request可能會被多次回調。它被調用的原因只有一個,就是在第一次試圖向上游服務器建立連接時,如果連接由于各種異常原因失敗,那么會根據upstream中conf參數的策略要求再次重連上游服務器,而這時就會調用reinit_request方法了。
? ??
? ? ? 上圖中的流程的步驟描述如下:
? ? ? 1) Nginx主循環中會定期地調用事件模塊,檢查是否有網絡事件發生。
? ? ? 2) 事件模塊在確定與上游服務器的TCP連接建立成功后,會回調upstream模塊的相關方法處理。
? ? ? 3) upstream模塊這時會把r->upstream->request_sent標志位置為1,表示連接已經建立成功了,現在開始向上游服務器發送請求內容。
? ? ? 4) 發送請求到上游服務器。
? ? ? 5) 發送方法當然是無阻塞的(使用了無阻塞的套接字),會立刻返回。
? ? ? 6) upstream模塊處理第2步中的TCP連接建立成功事件。
? ? ? 7) 事件模塊處理完本輪網絡事件后,將控制權交還給Nginx主循環。
? ? ? 8) Nginx主循環重復第1步,調用事件模塊檢查網絡事件。
? ? ? 9) 這時,如果發現與上游服務器建立的TCP連接已經異常斷開,那么事件模塊會通知upstream模塊處理它。
? ? ? 10) 在符合重試次數的前提下,upstream模塊會毫不猶豫地再次用無阻塞的套接字試圖建立連接。
? ? ? 11) 無論連接是否建立成功都立刻返回。
? ? ? 12) 這時檢查r->upstream->request_sent標志位,會發現它已經被置為1了。
? ? ? 13) 如果mytest模塊沒有實現reinit_request方法,那么是不會調用它的。而如果reinit_request不為NULL空指針,就會回調它。
? ? ? ?14) mytest模塊在reinit_request中處理完自己的事情。
? ? ? ?15) 處理完第9步中的TCP連接斷開事件,將控制權交還給事件模塊。
? ? ? ?16) 事件模塊處理完本輪網絡事件后,交還控制權給Nginx主循環。
47.finalize_request回調方法
? ? 當調用ngx_http_upstream_init啟動upstream機制后,在各種原因(無論成功還是失敗)導致該請求被銷毀前都會調用finalize_request方法。
? ? 在finalize_request方法中可以不做任何事情,但必須實現finalize_request方法,否則Nginx會出現空指針調用的嚴重錯誤。
48.process_header回調方法
? ? process_header是用于解析上游服務器返回的基于TCP的響應頭部的,因此,process_header可能會被多次調用,它的調用次數與process_header的返回值有關。如果process_header返回NGX_AGAIN,這意味著還沒有接收到完整的響應頭部,如果再次接收到上游服務器發來的TCP流,還會把它當做頭部,仍然調用process_header處理。如果process_header返回NGX_OK(或者其他非NGX_AGAIN的值),那么在這次連接的后續處理中將不會再次調用process_header。
? ?
? ? 上圖中的步驟解釋:
? ?1) Nginx主循環中會定期地調用事件模塊,檢查是否有網絡事件發生。
? ?2) 事件模塊接收到上游服務器發來的響應時,會回調upstream模塊處理。
? ?3) upstream模塊這時可以從套接字緩沖區中讀取到來自上游的TCP流。
? ?4) 讀取的響應會存放到r->upstream->buffer指向的內存中。注意:在未解析完響應頭部前,若多次接收到字符流,所有接收自上游的響應頭回完整地存放到r->upstream->buffer緩沖區中。因此,在解析上游響應包頭時,如果buffer緩沖區全滿卻還沒有解析到完整的響應頭部(也就是說,process_header一直在返回NGX_AGAIN),那么請求就會出錯。
? ?5) 調用mytest模塊實現的process_header方法。
? ?6) process_header方法實際上就是在解析r->upstream->buffer緩沖區,試圖從中取到完整的響應頭部(當然,如果上游服務器與Nginx通過HTTP通信,就是接收到完整的HTTP頭部)。
? ?7) 如果process_header返回NGX_AGAIN,那么表示還沒有解析到完整的響應頭部,下次還會調用process_header處理接收到的上游響應。
? ?8) 調用無阻塞的讀取套接字接口。
? ?9) 這時有可能返回套接字緩沖區已經為空。
? ?10) 當第2步中的讀取上游響應時間處理完畢后,控制權交還給事件模塊。
? ?11) 事件模塊處理完本輪網絡事件后,交還控制權給Nginx主循環。
49.舉例說明upstream機制。實現的功能很簡單:"即以訪問mytest模塊的URL參數作為搜索引擎的關鍵字,用upstream方式訪問google,查詢URL里的參數,然后把google的結果返回給用戶。"
? ?每一個HTTP請求都會有獨立的ngx_http_upstream_conf_t結構體,在mytest模塊的例子中,所有的請求都將共享同一個ngx_http_upstream_conf_t結構體,因此,這里把它放到ngx_http_mytest_conf_t配置結構體中,如下所示:
typedef struct {ngx_http_upstream_conf_t upstream; } ngx_http_mytest_conf_t;? ?(1) 在啟動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;}/* 以下簡單的硬編碼ngx_http_upstream_conf_t結構中的各成員,如超時事件,都設為1分鐘,這也是HTTP反向代理模塊的默認值*/mycf->upstream.connect_timeout = 60000;mycf->upstream.send_timeout = 60000;mycf->upstream.read_timeout = 60000;mycf->upstream.store_access = 0600;/* 實際上,buffering已經決定了將以固定大小的內存作為緩沖區來轉發上游的響應包體,這塊固定緩沖區的大小就是buffer_size。如果buffering為1,就會使用更多的內存緩存來不及發往下游的響應。例如,最多使用bufs.num個緩沖區且每個緩沖區大小為bufs.size。另外,還會使用臨時文件,臨時文件的最大長度為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在解析完上游服務器返回的包頭時,會調用ngx_http_upstream_process_headers方法按照hide_headers成員將本應轉發給下游的一些HTTP頭部隱藏),這里將它賦為NGX_CONF_UNSET_PTR,這是為了在merge合并配置項方法中使用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上游服務器的響應包,因為upstream模塊每次接收到一段TCP流時都會回調mytest模塊實現的process_header方法解析,這樣就需要有一個上下文保存解析狀態。
? ? ? (2) 在create_request方法中構造請求
? ? ? ? ?下面方法用于創建發送給上游服務器的HTTP請求,upstream模塊將會回調它,實現如下:
static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r) {/* 在發往google上游服務器的請求很簡單,就是模仿正常的搜索請求,以/search?q=...的URL來發起搜索請求。backendQueryLine中的%V等轉化格式的用法*/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;/* 必須在內存池中申請內存,這有以下兩點好處:一個好處是,在網絡情況不佳的情況下,向上游服務器發送請求時,可能需要epoll多次調度send才能發送完成,這時必須保證這段內存不會被釋放;另一個好處是,在請求結束時,這段內存會被自動釋放,減低內存泄露的可能性*/ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen);if (b == NULL) {return NGX_ERROR;}//last要指向請求的末尾b->last = b->pos + queryLineLen;//作用相當于snprintfngx_snprintf(b->pos, queryLineLen, (char *)backendQueryLine.data, &r->args);/* r->upstream->request_bufs是一個ngx_chain_t結構,它包含著要發送給上游服務器的請求 */r->upstream->request_bufs = ngx_alloc_chain_link(r->pool);if (r->upstream->request_bufs == NULL) return NGX_ERROR;//request_bufs在這里只包含1個ngx_buf_t緩沖區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負責解析上游服務器發來的基于TCP的包頭,在本例中,就是解析HTTP響應行和HTTP頭部,因此,這里使用mytest_process_status_line方法解析HTTP響應行,使用mytest_upstream_process_header方法解析http響應頭部。
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;//上下文中才會保存多次解析HTTP響應行的狀態,下面首先取出請求的上下文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響應行,它的輸入就是收到的字符流和上下文中的ngx_http_status_t結構 */rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);//返回NGX_AGAIN時,表示還沒有解析出完整的HTTP響應行,需要接受更多的字符流再進行解析if (rc == NGX_AGAIN) {return rc;}//返回NGX_ERROR時,表示沒有接收到合法的HTTP響應行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響應行時,會做一些簡單的賦值操作,將解析出的信息設置到r->upstream->headers_in結構體中。當upstream解析完所有的包頭時,會把headers_in中的成員設置到 將要向下游發送的r->headers_out結構體中,也就是說,現在用戶向headers_in中設置的信息,最終都會發往下游客戶端。為什么不直接設置r->headers_out而要多此一舉呢?因為upstream希望能夠按照ngx_http_upstream_conf_t配置結構體中的hide_headers等成員對發往下游的響應頭部做統一處理 */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頭部。設置process_header回調方法為mytest_upstream_process_header,之后再收到的新字符流將由mytest_upstream_process_header解析 */u->process_header = mytest_upstream_process_header;/* 如果本次收到的字符流除了HTTP響應行外,還有多余的字符,那么將由mytest_upstream_process_header方式解析 */return mytest_upstream_process_header(r); }? mytest_upstream_process_header方法可以解析HTTP響應頭部,而這里只是簡單地把上游服務器發送的HTTP頭部添加到了請求r->upstream->headers_in.headers鏈表中。如果有需要特殊處理的HTTP頭部,那么應該在mytest_upstream_process_header方法中進行。
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模塊配置項ngx_http_upstream_main_conf_t取出來,目的只有一個,就是對將要轉發給下游客戶端的HTTP響應頭部進行統一處理。該結構體中存儲了需要進行統一處理的HTTP頭部名稱和回調方法 */umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);//循環地解析所有的HTTP頭部for (;;) {/* HTTP框架提供了基礎性的ngx_http_parse_haeder_line方法,它用于解析HTTP頭部 */rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);//返回NGX_OK時,表示解析出一行HTTP頭部if (rc == NGX_OK) {// 向headers_in.headers這個ngx_list_t鏈表中添加HTTP頭部h = ngx_list_push(&r->upstream->headers_in.headers);if (h == NULL) {return NGX_ERROR;}//下面開始構造剛剛添加到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;//必須在內存池中分配存放HTTP頭部的內存空間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模塊會對一些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時,表示響應中所有的HTTP頭部解析完畢,接下來在接收到的都將是HTTP包體 */if (rc == NGX_HTTP_PARSE_HEADER_DONE) {/* 如果之前解析HTTP頭部時沒有發現server和date頭部,那么下面會根據HTTP協議規范添加這兩個頭部 */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, 則表示狀態機還沒有解析到完整的HTTP頭部,此時要求upstream模塊繼續接收新的字符流,然后交由process_header回調方法解析 */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;} }? ?當mytest_upstream_process_header返回NGX_OK后,upstream模塊開始把上游的包體(如果有的話)直接轉發到下游客戶端。
? ? ? ? ? (4) 在finalize_request方法中釋放資源
? ? ? ? ? ? ? ? 當請求結束時,將會回調finalize_request方法,如果我們希望此時釋放資源,如打開的句柄等,那么可以把這樣的代碼添加到finalize_request方法中。本例中定義了mytest_upstream_finalize_request方法,由于我們沒有任何需要釋放的資源,所以該方法沒有完成任何實際工作,只是因為upstream模塊要求必須實現finalize_request回調方法。
? ? ? ?? ?(5) 在ngx_http_mytest_handler方法中啟動upstream
? ? ? ? ? ? ? ? ?在開始介入處理客戶端請求的ngx_http_mytest_handler方法中啟動upstream機制,而何時會結束,則視Nginx與上游的google服務器間的通信而定。
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_r *r) {//首先建立HTTP上下文結構體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;}//得到配置結構體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;//這里用配置文件中的結構體來賦給r->upstream->conf成員u->conf = &mycf->upstream;//決定轉發包體時使用的緩沖區u->buffering = mycf->upstream.buffering;//以下代碼開始初始化resolved結構體,用來保存上游服務器的地址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;}//這里的上游服務器就是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;}//訪問上游服務器的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);//將地址設置到resolved成員中u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;u->resolved->socklen = sizeof(struct sockaddr_in);u->resolved->naddrs = 1;// 設置3個必須實現的回調方法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++;//啟動upstreamngx_http_upstream_init(r);//必須返回NGX_DONEreturn NGX_DONE;}? ? ? ? ? ?到此為止,高性能地訪問第三方服務的upstream例子就介紹完了。在本例中,可以完全異步地訪問第三方服務,并發訪問數也只會受制于物理內存的大小,完全可以輕松達到幾十萬的并發TCP連接。
50.使用subrequest的方式只需要完成以下4步操作即可:
? ? 1) 在nginx.conf文件中配置好子請求的處理方式;
? ? 2) 啟動subrequest子請求;
? ? 3) 實現子請求執行結束時的回調方法;
? ? 4) 實現父請求被激活時的回調方法。
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 是當前的請求,也就是父請求。
? ? ? 2) ngx_str_t *uri
? ? ? ? ? ngx_str_t *uri是子請求的URI,它對究竟選用nginx.conf配置文件中的哪個模塊來處理子請求起決定性作用。
? ? ? 3) ngx_str_t *args
? ? ? ? ? ngx_str_t *args是子請求的URI參數,如果沒有參數,可以傳送NULL空指針。
? ? ? ?4) ngx_http_request_t **psr
? ? ? ? ? psr是輸出參數而不是輸入參數,它將把ngx_http_subrequest生成的子請求傳出來。
? ? ? ? 5) ngx_http_post_subrequest_t *ps
? ? ? ? ? 這里傳入創建的ngx_http_post_subrequest_t結構體地址,它指出子請求結束時必須回調的處理方法。
? ? ? ? ?6) ngx_uint_t flags
? ? ? ? ? ?flag的取值范圍包括: (1) 0.在沒有特殊需求的情況下都應該填寫它; (2) NGX_HTTP_SUBREQUEST_IN_MEMORY。這個宏會將子請求的subrequest_in_memory標志位置為1,這意味著如果子請求使用upstream訪問上游服務器,那么上游服務器的響應都將會在內存中處理;(3) NGX_HTTP_SUBREQUEST_WAITED。這個宏會將子請求的waited標志位置為1,當子請求提前結束時,有個done標志位置為1,但目前HTTP框架并沒有針對這兩個標志位做任何實質性處理。注意,flag是按比特位操作的,這樣可以同時包含上述3個值。
? ? ? ? ?7) 返回值
? ? ? ? ? ? 返回NGX_OK表示成功建立子請求;返回NGX_ERROR表示建立子請求失敗。
52.如何啟動subrequest
? ? ?處理父請求的過程中會創建子請求,在父請求的處理方法返回NGX_DONE后,HTTP框架會開始執行子請求
? ? ??
? ? ? ?上圖中的步驟如下:
? ? ? ?1) Nginx主循環中會定期地調用事件模塊,檢查是否有網絡事件發生;
? ? ? ?2) 事件模塊發現這個請求的回調方法屬于HTTP框架,交由HTTP框架來處理請求。
? ? ? ?3) 根據解析完的URI來決定使用哪個location下的模塊來處理這個請求。
? ? ? ?4) 調用mytest模塊的ngx_http_mytest_handler方法處理這個請求。
? ? ? ?5) 設置subrequest子請求的URI及回調方法。
? ? ? ?6) 調用ngx_http_subrequest方法創建子請求。
? ? ? ?7) 創建的子請求會添加到原始請求的posted_requests鏈表中,這樣保證第10步時會在父請求返回NGX_DONE的情況下開始執行子請求。
? ? ? ?8) ngx_http_subrequest方法執行完畢,子請求創建成功。
? ? ? ?9) ngx_http_mytest_handler方法執行完畢,返回NGX_DONE,這樣父請求不會被銷毀,將等待以后的再次激活。
? ? ? ?10) HTTP框架執行完當前請求(父請求)后,檢查posted_requests鏈表中是否還有子請求,如果存在子請求,則調用子請求的write_event_handler方法。
? ? ? ? 11) 根據子請求的URI(第5步中建立),檢查nginx.conf文件中所有的location配置,確定應由哪個模塊來執行子請求。在本章的例子中,子請求是交由反向代理模塊執行的。
? ? ? ? 12) 調用反向代理模塊的入口方法ngx_http_proxy_handler來處理子請求。
? ? ? ? ?13) 由于反向代理模塊使用了upstream機制,所以它也要通過許多次的異步調用才能完整地處理完子請求,這時它的入口方法會返回NGX_DONE.
? ? ? ? ?14) 再次檢查是否還有子請求,這時會發現已經沒有子請求需要執行了。當然,子請求可以繼續建立新的子請求,只是這里的反向代理模塊不會這樣做。
? ? ? ? ?15) 當第2步中的網絡讀取事件處理完畢后,交還控制權給事件模塊。
? ? ? ? ?16) 當本輪網絡事件處理完畢后,交還控制權給Nginx主循環。
53.如何轉發多個子請求的響應包體
? ? ?每個請求的ngx_http_request_t結構體中都有一個postponed成員:
struct ngx_http_request_s {...ngx_http_postponed_request_t *postponed;... } 它實際上是一個鏈表: 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; };? ? ? ? 多個ngx_http_postponed_request_t之間使用next指針連接成一個單向鏈表。ngx_http_postponed_request_t中的out成員是ngx_chain_t結構,它指向的是來自上游的、將要轉發給下游的響應包體。
? ? ? ? 每當使用ngx_http_output_filter方法(反向代理模塊也使用該方法轉發響應)向下游的客戶端發送響應包體時,都會調用到ngx_http_postpone_filter_module過濾模塊處理這段要發送的包體。
//這里的參數in就是將要發送給客戶端的一段包體 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保存的是原始請求c = r->connection;//如果當前請求r是一個子請求(因為c->data指向原始請求)if (r != c->data) {/* 如果待發送的in包體不為空,則把in加到postponed鏈表中屬于當前請求的ngx_http_postponed_request_t結構體的out鏈表中,同時返回NGX_OK,這意味著本次不會把in包體發送客戶端*/if (in) {ngx_http_postpone_filter_add(r, in);return NGX_OK;}//如果當前請求是子請求,而in包體又為空,那么直接返回即可return NGX_OK;}//如果postponed為空,表示請求r沒有子請求產生的響應需要轉發if (r->postponed == NULL) {/* 直接調用下一個HTTP過濾模塊繼續處理in包體即可。如果沒有錯誤的話,就會向下游客戶端發送響應 */if (in || c->buffered) {return ngx_http_next_filter(r->main, in);}return NGX_OK;}/* 至此,說明postponed鏈表中是有子請求產生的響應需要轉發的,可以先把in包體加到待轉發響應的末尾 */if (in) {ngx_http_postpone_filter_add(r, in);}//循環處理postponed鏈表中所有子請求待轉發的包體do {pr = r->postponed;/* 如果pr->request是子請求,則加入到原始請求的posted_requests隊列中,等待HTTP框架下次調用這個請求時再來處理 */if (pr->request) {r->postponed = pr->next;c->data = pr->request;return ngx_http_post_request(pr->request, NULL);}//調用下一個HTTP過濾模塊轉發out鏈表中保存的待轉發的包體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.子請求在結束前會回調在ngx_http_post_subrequest_t中實現的handler方法,在這個handler方法中,又設置了父請求被激活后的執行方法mytest_post_handler,流程如下:
? ? ? ? ? ? ? ??
? ?上圖中的步驟如下:
? ? 1) Nginx主循環中會定期地調用事件模塊,檢查是否有網絡事件發生。
? ? 2) 如果事件模塊檢測到連接關閉事件,而這個請求的處理方法屬于upstream模塊,則交由upstream模塊來處理請求。
? ? 3) upstream模塊開始調用ngx_http_upstream_finalize_request方法來結束upstream機制下的請求。
? ? 4) 調用HTTP框架提供的ngx_http_finalize_request方法來結束子請求。
? ? 5) ngx_http_finalize_request方法會檢查當前的請求是否是子請求,如果是子請求,則會回調post_subrequest成員中的handler方法,也就是會調用mytest_subrequest_post_handler方法。
? ? 6) 在實現的子請求回調方法中,解析子請求返回的響應包。注意,這時需要通過write_event_handler設置父請求被激活后的回調方法(因此此時父請求的回調方法已經被HTTP框架設置為什么事情也不做的ngx_http_request_empty_handler方法).
? ? 7) 子請求的回調方法執行完畢后,交由HTTP框架的ngx_http_finalize_request方法繼續向下執行。
? ? 8) ngx_http_finalize_request方法執行完畢。
? ? 9) HTTP框架如果發現當前請求后還有父請求需要執行,則調用父請求的write_event_handler回調方法。
? ? 10) 這里可以根據第6步中解析子請求響應后的結果來構造響應包。
? ? ?11) 調用無阻塞的ngx_http_send_header、ngx_http_output_filter發送方法,向客戶端發送響應包。
? ? ?12) 無阻塞發送方法會立刻返回,即使目前未發送完,Nginx之后也會異步地發送完所有的響應包,然后再結束請求。
? ? ? 13) 父請求的回調方法執行完畢。
? ? ? 14) 當第2步中的上游服務器連接關閉時間處理完畢后,交還控制權給事件模塊。
? ? ? 15) 當本輪網絡事件處理完畢后,交還控制權給Nginx主循環。
55.subrequest是分解復雜請求的設計方法,派生出的子請求使用某些HTTP模塊基于upstream訪問第三方服務是最常見的用法,通過subrequest可以使Nginx在保持高并發的前提下處理復雜的業務。
56.
57.默認即編譯進Nginx的HTTP過濾模塊
| 默認即編譯進Nginx的HTTP過濾模塊 | 功能 |
| ngx_http_not_modified_filter_module | 僅對HTTP頭部進行處理。在返回200成功時,根據請求中If-Modified-Since或者If-Unmodified-Since頭部取得瀏覽器緩存文件的時間,再分析返回用戶文件的最后修改時間,以此決定是否直接發送304 Not Modified響應給用戶 |
| ngx_http_range_body_filter_module | 處理請求中的Range信息,根據Range中的要求返回文件的一部分給用戶 |
| ngx_http_copy_filter_module | 僅對HTTP包體做處理。將用戶發送的ngx_chain_t結構的HTTP包體復制到新的ngx_chain-t結構中(都是各種指針的復制,不包括實際HTTP響應內容),后續的HTTP過濾模塊處理的ngx_chain-t類型的成員都是ngx_http_copy_filter_module模塊處理后的變量 |
| ngx_http_headers_filter_module | 僅對HTTP頭部做處理。允許通過修改nginx.conf配置文件,在返回給用戶的響應中添加任意的HTTP頭部 |
| ngx_http_userid_filter_module | 僅對HTTP頭部做處理。這就是執行configure命令時提到的http_userid_module模塊,它基于cookie提供了簡單的認證管理功能 |
| ngx_http_charset_filter_module | 可以將文本類型返回給用戶的響應包,按照nginx.conf中的配置重新進行編碼,再返回給用戶 |
| ngx_http_ssi_filter_module | 支持SSI(Server Side Include,服務器端嵌入)功能,將文件內容包含到網頁中并返回給用戶 |
| ngx_http_postpone_filter_module | 僅對HTTP包體做處理。它僅應用于subrequest產生的子請求。它使得多個子請求同時向客戶端發送響應時能夠有序,所謂的“有序”是指按照子請求的順序發送響應 |
| ngx_http_gzip_filter_module | 對特定的HTTP響應包體(如網頁或者文本文件)進行gzip壓縮,再把壓縮后的內容返回給用戶 |
| ngx_http_range_header_filter_module | 支持range協議 |
| ngx_http_chunked_filter_module | 支持chunk編碼 |
| ngx_http_header_filter_module | 僅對HTTP頭部做處理。該過濾模塊將會把r->headers_out結構體中的成員序列化為返回給用戶的HTTP響應字符流,包括響應行(如HTTP/1.1 200 OK)和響應頭部,并通過調用ngx_http_write_filter_module過濾模塊中的過濾方法直接將HTTP包頭發送給客戶端 |
| ngx_http_write_filter_module | 僅對HTTP包體做處理。該模塊負責向客戶端發送HTTP響應 |
58.過濾模塊例子中,HTTP頭部處理方法的執行活動圖
?
59.過濾模塊例子中,HTTP包體處理方法的執行活動圖
? ?
60.參考另一篇:https://blog.csdn.net/zhangge3663/article/details/83180659
61.ngx_module_t接口及其對核心、事件、HTTP、mail等4類模塊ctx上下文成員的具體化
? ??
62.Nginx常用模塊及其之間的關系
? ??
63.傳統Web服務器和Nginx間的重要差別:前者是每個事件消費者獨占一個進程資源,后者的事件消費者只是被事件分發者進程短期調用而已。
64.在阻塞代碼段上按照下面4種方式來劃分階段:
? ?(1) 將阻塞進程的方法按照相關的觸發事件分解為兩個階段
? ?(2) 將阻塞方法調用按照時間分解為多個階段的方法調用
? ?(3) 在“無所事事”且必須等待系統的響應,從而導致進程空轉時,使用定時器劃分階段
? ?(4) 如果阻塞方法完全無法繼續劃分,則必須使用獨立的進程執行這個阻塞方法
65.內存池的設計
? ? 為了避免出現內存碎片、減少向操作系統申請內存的次數、降低各個模塊的開發復雜度,Nginx設計了簡單的內存池。這個內存池沒有很復雜的功能:通常它不負責回收內存池中已經分配出的內存。
? ? 通常每一個請求都有一個這種簡易的獨立內存池(Nginx為每一個TCP連接都分配一個內存池,HTTP框架為每一個HTTP請求又分配了1個內存池),而在請求結束時則會銷毀整個內存池,把曾經分配的內存一次性歸還給操作系統。
66.Nginx核心的框架代碼一直圍繞著一個結構體展開,它就是ngx_cycle_t。無論是master管理進程、worker工作進程還是cache manager(loader)進程,每一個進程都毫無例外地擁有唯一一個ngx_cycle_t結構體。
? ? 作為一個Web服務器,Nginx首先需要監聽端口并處理其中的網絡事件。ngx_cycle_t對象中有一個動態數組成員叫做listening,它的每一個數組元素都是ngx_listening_t結構體,而每個ngx_listen_t結構體又代表著Nginx服務器監聽的一個端口。
typedef struct ngx_listening_s ngx_listening_t; struct ngx_listening_s {//socket套接字句柄ngx_socket_t fd;//監聽sockaddr地址struct sockaddr *sockaddr;//sockaddr地址長度socklen_t socklen;/* 存儲IP地址的字符串addr_text最大長度,即它指定了addr_text所分配的內存大小 */size_t addr_text_max_len;//以字符串形式存儲IP地址ngx_str_t addr_text;//套接字地址。例如,當type是SOCK_STREAM時,表示TCPint type;/* TCP實現監聽時的backlog隊列,它表示允許正在通過三次握手建立TCP連接但還沒有任何進程開始處理的連接最大個數 */int backlog;//內核中對于這個套接字的接收緩沖區大小int rcvbuf;//內核中對于這個套接字的發送緩沖區大小int sndbuf;//當新的TCP連接成功建立后的處理方法ngx_connection_handler_pt handler;/* 實際上框架并不使用servers指針,它更多的是作為一個保留指針,目前主要用于HTTP或者mail等模塊,用于保存當前監聽端口對應著的所有主機名 */void *servers;//log和logp都是可用的日志對象的指針ngx_log_t log;ngx_lot_t *logp;//如果為新的TCP連接創建內存池,則內存池的初始大小應該是pool_sizesize_t pool_size;/* TCP_DEFER_ACCEPT選項將在建立TCP連接成功且接收到用戶的請求數據后,才向對監聽套接字感興趣的進程發送事件通知,而連接建立成功后,如果post_accept_timeout秒后仍然沒有收到的用戶數據,則內核直接丟棄連接 */ngx_msec_t post_accept_timeout;/* 前一個ngx_listening_t 結構,多個ngx_listening_t結構體之間由previous指針組成單鏈表 */ngx_listening_t *previous;//當前監聽句柄對應著的ngx_connection_t結構體ngx_connection_t *connection;/* 標志位,為1則表示在當前監聽句柄有效,且執行ngx_init_cycle時不關閉監聽端口,為0時則正常關閉。該標志位框架代碼會自動設置 */unsigned open:1;/* 標志位,為1表示使用已有的ngx_cycle_t來初始化新的ngx_cycle_t結構體時,不關閉原來打開的監聽端口,這對運行中升級程序很有用,remain為0時,表示正常關閉曾經打開的監聽端口。該標志位框架代碼會自動設置,參見ngx_init_cycle方法 */unsigned remain:1;/* 標志位,為1時表示跳過設置當前ngx_listening_t結構體中的套接字,為0時正常初始化套接字。該標志位框架代碼會自動設置 */unsigned ignore:1;//表示是否已經綁定。實際上目前該標志位沒有使用unsigned bound:1; /* 已經綁定 *//* 表示當前監聽句柄是否來自前一個進程(如升級Nginx程序),如果為1,則表示來自前一個進程。一般會保留之前已經設置好的套接字,不做改變 */unsigned inherited:1; /* 來自前一個進程 *///目前未使用unsigned nonblocking_accept:1;//標志位,為1時表示當前結構體對應的套接字已經監聽unsigned listen:1;//表示套接字是否阻塞,目前該標志位沒有意義unsigned nonblocking:1;//目前該標志位沒有意義unsigned shared:1;//標志位,為1時表示Nginx會將網絡地址轉變為字符串形式的地址unsigned addr_ntop:1; };67.Nginx框架是圍繞著ngx_cycle_t結構體來控制進程運行的。
typedef struct ngx_cycle_s ngx_cycle_t; struct ngx_cycle_s {/* 保存著所有模塊存儲配置項的結構體的指針,它首先是一個數組,每個數組成員又是一個指針,這個指針指向另一個存儲著指針的數組,因此會看到void **** */void ****conf_ctx;//內存池ngx_pool_t *pool;/* 日志模塊中提供了生成基本ngx_log_t日志對象的功能,這里的log實際上是在還沒有執行ngx_init_cycle方法前,也就是還沒有解析配置前,如果有信息需要輸出到日志,就會暫時使用log對象,它會輸出到屏幕。在ngx_init_cycle方法執行后,將會根據nginx.conf配置文件中的配置項,構造出正確的日志文件,此時會對log重新賦值 */ngx_lot_t *log;/*由nginx.conf配置文件讀取到日志文件路徑后,將開始初始化error_log日志文件,由于log對象還在用于輸出日志到屏幕,這時會用new_log對象暫時性地替代log日志,待初始化成功后,會用new_log的地址覆蓋上面的log指針 */ngx_lot_t new_log;//與下面的files成員配合使用,指出files數組里元素的總數ngx_uint_t files_n;/* 對于poll、rtsig這樣的事件模塊,會以有效文件句柄數來預先建立這些ngx_connection_t結構體,以加速事件的收集、分發。這時files就會保存所有ngx_connection_t的指針組成的數組,files_n就是指針的總數,而文件句柄的值用來訪問files數組成員 */ngx_connection_t **files;//可用連接池,與free_connection_n配合使用ngx_connection_t *free_connections;//可用連接池中連接的總數ngx_uint_t free_connection_n;/* 雙向鏈表容器,元素類型是ngx_connection_t結構體,表示可重復使用連接隊列 */ngx_queue_t reusable_connections_queue;/*動態數組,每個數組元素存儲著ngx_listening_t成員,表示監聽端口及相關的參數 */ngx_array_t listening;/*動態數組容器,它保存著Nginx所有要操作的目錄。如果有目錄不存在,則會視圖創建,而創建目錄失敗將會導致Nginx啟動失敗。例如,上傳文件的臨時目錄也在pathes中,如果沒有權限創建,則會導致Nginx無法啟動 */ngx_array_t pathes;/* 單鏈表容器,元素類型是ngx_open_file_t結構體,它表示Nginx已經打開的所有文件。事實上,Nginx框架不會向open_files鏈表中添加文件,而是由對此感興趣的模塊向其中添加文件路徑名,Nginx框架會有ngx_init_cycle方法中打開這些文件 */ngx_list_t open_files;/* 單鏈表容器,元素的類型是ngx_shm_zone_t結構體,每個元素表示一塊共享內存*/ngx_list_t shared_memory;//當前進程中所有連接對象的總數,與下面的connections成員配合使用ngx_uint_t connection_n;//指向當前進程中的所有連接對象,與connection_n配合使用ngx_connection_t *connections;//指向當前進程中的所有讀事件對象,connection_n同時表示所有讀事件的總數ngx_event_t *read_events;//指向當前進程中的所有寫事件對象,connection_n同時表示所有寫事件的總數ngx_event_t *write_events;/* 舊的ngx_cycle_t對象用于引用上一個ngx_cycle_t對象中的成員。例如ngx_init_cycle方法,在啟動初期,需要建立一個臨時的ngx_cycle_t對象保存一些變量,再調用ngx_init_cycle方法時就可以把舊的ngx_cycle_t對象傳進去,而這時old_cycle對象就會保存這個前期的ngx_cycle_t對象 */ngx_cycle_t *old_cycle;//配置文件相對于安裝目錄的路徑名稱ngx_str_t conf_file;/* Nginx處理配置文件時需要特殊處理的在命令行攜帶的參數,一般是-g選項攜帶的參數 */ngx_str_t conf_param;//Nginx配置文件所在目錄的路徑ngx_str_t conf_prefix;//Nginx安裝目錄的路徑ngx_str_t prefix;//用于進程間同步的文件鎖名稱ngx_str_t lock_file;//使用gethostname系統調用得到的主機名ngx_str_t hostname; };68.Nginx啟動過程的流程圖
? ?
69.worker進程正常工作、退出時的流程圖
? ??
70.master進程不需要處理網絡事件,它不負責業務的執行,只會通過管理worker等子進程來實現重啟服務、平滑升級、更換日志文件、配置文件實時生效等功能。
71.epoll是目前Linux操作系統上最強大的事件管理機制。
72.首先,Nginx定義了一個核心模塊ngx_events_module,這樣在Nginx啟動時會調用ngx_init_cycle方法解析配置項,一旦在nginx.conf配置文件中找到ngx_events_module感興趣的“events{}”配置項,ngx_events_module模塊就開始工作了。ngx_events_module模塊定義了事件類型的模塊,它的全部工作就是為所有的事件模塊解析"events{}"中的配置項,同時管理這些事件模塊存儲配置項的結構體。
? ? 其次,Nginx定義了一個非常重要的事件模塊ngx_event_core_module,這個模塊會決定使用哪種事件驅動機制,以及如何管理事件。
? ? 最后,Nginx定義了一系列運行在不同操作系統、不同內核版本上的事件驅動模塊,包括: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模塊的初始化過程中,將會從以上模塊中選取一個作為Nginx進程的事件驅動模塊。
73.ngx_connection_t連接池示意圖
? ??
74.所有事件模塊配置項結構體的指針是如何管理的。
??
75.ngx_event_core_module事件模塊啟動時的工作流程
? ? ?
? 76.epoll在Linux內核中申請了一個簡易的文件系統,把原來的一個select或者poll調用分成了3個部分:調用epoll_create建立1個epoll對象(在epoll文件系統中給這個句柄分配資源)、調用epoll_ctx向epoll對象中添加這100萬個連接的套接字、調用epoll_wait收集發生事件的連接。
77.ngx_event_accept方法建立新連接的流程
? ?
78.ngx_process_events_and_times方法中的事件框架處理流程
? ?
79.解析server{}塊內配置項的流程
? ?
80.解析location{}配置塊的流程
? ??
81.HTTP框架的初始化流程
? ??
82.接收、解析HTTP請求行的流程圖
? ??
83.ngx_http_process_request_headers方法接收HTTP頭部的流程圖
? ??
84.ngx_http_process_request處理HTTP請求的流程圖
? ??
85.ngx_http_core_run_phases方法的執行流程
? ??
86.ngx_http_request_handler方法的執行流程
? ??
87.ngx_http_read_client_request_body方法的流程圖
? ??
88.upstream機制的場景示意圖
? ??
89.ngx_http_upstream_connect方法的流程圖
? ??
90.ngx_http_upstream_send_request方法的流程圖
? ? ?
91.郵件代理功能的示意序列圖
? ??
? ?從網絡通信的角度來看,Nginx實現郵件代理功能時會把一個請求分為以下4個階段。
? ?1) 接收并解析客戶端初始請求的階段;
? ?2) 向認證服務器驗證請求合法性,并獲取上游郵件服務器地址的階段;
? ?3) Nginx根據用戶信息多次與上游郵件服務器交互驗證合法性的階段;
? ?4) Nginx在客戶端與上游郵件服務器間純粹透傳TCP流的階段。
92.初始化郵件請求的流程
? ??
93.啟動郵件認證、向認證服務器發起連接的流程
? ?
94.Nginx框架使用了3種傳遞消息傳遞方式:共享內存、套接字、信號。Nginx各進程間共享數據的主要方式就是使用共享內存.
總結
以上是生活随笔為你收集整理的《深入理解NGINX 模块开发与架构解析》之摘抄学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pstack命令学习
- 下一篇: Linux Sendfile的优势