1. 程式人生 > >《深入理解NGINX 模組開發與架構解析》之摘抄學習

《深入理解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 ]; then
    have=NGX_DEBUG . auto/have
fi

# 現在開始檢查作業系統引數是否支援後續編譯
if test -z "$NGX_PLATFORM"; then
    echo "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" in
        MINGW32_*)
            NGX_PLATFORM=win32
            ;;
        esac
    else
        echo "building for $NGX_PLATFORM"
        NGX_SYSTEM=$NGX_PLATFORM
    fi
    
    #檢查並設定編譯器,如GCC是否安裝、GCC版本是否支援後續編譯nginx
    . auto/cc/conf

    # 對非Windows作業系統定義一些必要的標頭檔案,並檢查其是否存在,一次決定configure後續步驟是否可以成功
    if [ "$NGX_PLATFORM" != win32 ]; then
        . auto/headers
    fi

    # 對於當前作業系統,定義一些特定的作業系統相關的方法並檢查當前環境是否支援。例如,對於Linux,在這裡使用sched_sestaffinity設定程序優先順序,使用Linux特有的sendfile系統呼叫來加速向網路中傳送檔案塊
    . auto/os/conf

    # 定義類UNIX作業系統中通用的標頭檔案和系統呼叫等,並檢查當前環境是否支援
    if [ "$NGX_PLATFORM" != win32 ]; then
        . auto/unix
    fi

    #最核心的構造執行期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" != "." ]; then
        have=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/define
    fi

    # 處理Nginx安裝後,二進位制檔案、pid、lock等其他檔案的路徑可參見configure引數中路徑類選項的說明
    have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/define
    have=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/define
    have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define
    have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define
    have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define

    have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define
    have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/define
    have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\"" . auto/define
    have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\"" . auto/define
    have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\"" . auto/define
    have=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/define
    have=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define
    
    # 顯示configure執行的結果,如果失敗,則給出原因
    . auto/summary

4.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;
    //模組的版本,便於將來的擴充套件。目前只有一種,預設為1
    ngx_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 507

24.請求的所有資訊(如方法、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;
#endif

    ngx_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 Allowed
    if (!(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-Type
    r->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_t
    out.buf = b;
    //設定next為NULL
    out.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配置項相對應,在傳送大檔案時可以設為1
    unsigned 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偏移量處截止。