Nginx 模塊的基本結(jié)構(gòu)

2022-03-23 15:39 更新

在這一節(jié)我們將會對通常的模塊開發(fā)過程中,每個模塊所包含的一些常用的部分進行說明。這些部分有些是必須的,有些不是必須的。同時這里所列出的這些東西對于其他類型的模塊,例如 filter 模塊等也都是相同的。

模塊配置結(jié)構(gòu)

基本上每個模塊都會提供一些配置指令,以便于用戶可以通過配置來控制該模塊的行為。那么這些配置信息怎么存儲呢?那就需要定義該模塊的配置結(jié)構(gòu)來進行存儲。

大家都知道 Nginx 的配置信息分成了幾個作用域(scope,有時也稱作上下文),這就是 main,server 以及 location。同樣的每個模塊提供的配置指令也可以出現(xiàn)在這幾個作用域里。那對于這三個作用域的配置信息,每個模塊就需要定義三個不同的數(shù)據(jù)結(jié)構(gòu)去進行存儲。當然,不是每個模塊都會在這三個作用域都提供配置指令的。那么也就不一定每個模塊都需要定義三個數(shù)據(jù)結(jié)構(gòu)去存儲這些配置信息了。視模塊的實現(xiàn)而言,需要幾個就定義幾個。

有一點需要特別注意的就是,在模塊的開發(fā)過程中,我們最好使用 Nginx 原有的命名習慣。這樣跟原代碼的契合度更高,看起來也更舒服。

對于模塊配置信息的定義,命名習慣是ngx_http_<module name>_(main|srv|loc)_conf_t。這里有個例子,就是從我們后面將要展示給大家的 hello module 中截取的。

    typedef struct
    {
        ngx_str_t hello_string;
        ngx_int_t hello_counter;
    }ngx_http_hello_loc_conf_t;

模塊配置指令

一個模塊的配置指令是定義在一個靜態(tài)數(shù)組中的。同樣地,我們來看一下從 hello module 中截取的模塊配置指令的定義。

    static ngx_command_t ngx_http_hello_commands[] = {
       { 
            ngx_string("hello_string"),
            NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
            ngx_http_hello_string,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_hello_loc_conf_t, hello_string),
            NULL },

        { 
            ngx_string("hello_counter"),
            NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
            ngx_http_hello_counter,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_hello_loc_conf_t, hello_counter),
            NULL },               

        ngx_null_command
    };

其實看這個定義,就基本能看出來一些信息。例如,我們是定義了兩個配置指令,一個是叫 hello_string,可以接受一個參數(shù),或者是沒有參數(shù)。另外一個命令是 hello_counter,接受一個 NGX_CONF_FLAG 類型的參數(shù)。除此之外,似乎看起來有點迷惑。沒有關系,我們來詳細看一下 ngx_command_t,一旦我們了解這個結(jié)構(gòu)的詳細信息,那么我相信上述這個定義所表達的所有信息就不言自明了。

ngx_command_t 的定義,位于src/core/ngx_conf_file.h中。

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

name: 配置指令的名稱。

type: 該配置的類型,其實更準確一點說,是該配置指令屬性的集合。Nginx 提供了很多預定義的屬性值(一些宏定義),通過邏輯或運算符可組合在一起,形成對這個配置指令的詳細的說明。下面列出可在這里使用的預定義屬性值及說明。

  • NGX_CONF_NOARGS:配置指令不接受任何參數(shù)。
  • NGX_CONF_TAKE1:配置指令接受 1 個參數(shù)。
  • NGX_CONF_TAKE2:配置指令接受 2 個參數(shù)。
  • NGX_CONF_TAKE3:配置指令接受 3 個參數(shù)。
  • NGX_CONF_TAKE4:配置指令接受 4 個參數(shù)。
  • NGX_CONF_TAKE5:配置指令接受 5 個參數(shù)。
  • NGX_CONF_TAKE6:配置指令接受 6 個參數(shù)。
  • NGX_CONF_TAKE7:配置指令接受 7 個參數(shù)。

可以組合多個屬性,比如一個指令即可以不填參數(shù),也可以接受1個或者2個參數(shù)。那么就是NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2。如果寫上面三個屬性在一起,你覺得麻煩,那么沒有關系,Nginx 提供了一些定義,使用起來更簡潔。

  • NGX_CONF_TAKE12:配置指令接受 1 個或者 2 個參數(shù)。
  • NGX_CONF_TAKE13:配置指令接受 1 個或者 3 個參數(shù)。
  • NGX_CONF_TAKE23:配置指令接受 2 個或者 3 個參數(shù)。
  • NGX_CONF_TAKE123:配置指令接受 1 個或者 2 個或者 3 參數(shù)。
  • NGX_CONF_TAKE1234:配置指令接受 1 個或者 2 個或者 3 個或者 4 個參數(shù)。
  • NGX_CONF_1MORE:配置指令接受至少一個參數(shù)。
  • NGX_CONF_2MORE:配置指令接受至少兩個參數(shù)。
  • NGX_CONF_MULTI: 配置指令可以接受多個參數(shù),即個數(shù)不定。
  • NGX_CONF_BLOCK:配置指令可以接受的值是一個配置信息塊。也就是一對大括號括起來的內(nèi)容。里面可以再包括很多的配置指令。比如常見的 server 指令就是這個屬性的。
  • NGX_CONF_FLAG:配置指令可以接受的值是"on"或者"off",最終會被轉(zhuǎn)成 bool 值。
  • NGX_CONF_ANY:配置指令可以接受的任意的參數(shù)值。一個或者多個,或者"on"或者"off",或者是配置塊。

最后要說明的是,無論如何,Nginx 的配置指令的參數(shù)個數(shù)不可以超過 NGX_CONF_MAX_ARGS 個。目前這個值被定義為 8,也就是不能超過 8 個參數(shù)值。

下面介紹一組說明配置指令可以出現(xiàn)的位置的屬性。

  • NGX_DIRECT_CONF:可以出現(xiàn)在配置文件中最外層。例如已經(jīng)提供的配置指令 daemon,master_process 等。
  • NGX_MAIN_CONF: http、mail、events、error_log 等。
  • NGX_ANY_CONF: 該配置指令可以出現(xiàn)在任意配置級別上。

對于我們編寫的大多數(shù)模塊而言,都是在處理http相關的事情,也就是所謂的都是NGX_HTTP_MODULE,對于這樣類型的模塊,其配置可能出現(xiàn)的位置也是分為直接出現(xiàn)在http里面,以及其他位置。

  • NGX_HTTP_MAIN_CONF: 可以直接出現(xiàn)在 http 配置指令里。
  • NGX_HTTP_SRV_CONF: 可以出現(xiàn)在 http 里面的 server 配置指令里。
  • NGX_HTTP_LOC_CONF: 可以出現(xiàn)在 http server 塊里面的 location 配置指令里。
  • NGX_HTTP_UPS_CONF: 可以出現(xiàn)在 http 里面的 upstream 配置指令里。
  • NGX_HTTP_SIF_CONF: 可以出現(xiàn)在 http 里面的 server 配置指令里的 if 語句所在的 block 中。
  • NGX_HTTP_LMT_CONF: 可以出現(xiàn)在 http 里面的 limit_except 指令的 block 中。
  • NGX_HTTP_LIF_CONF: 可以出現(xiàn)在 http server 塊里面的 location 配置指令里的 if 語句所在的 block 中。

set: 這是一個函數(shù)指針,當 Nginx 在解析配置的時候,如果遇到這個配置指令,將會把讀取到的值傳遞給這個函數(shù)進行分解處理。因為具體每個配置指令的值如何處理,只有定義這個配置指令的人是最清楚的。來看一下這個函數(shù)指針要求的函數(shù)原型。

char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

先看該函數(shù)的返回值,處理成功時,返回 NGX_OK,否則返回 NGX_CONF_ERROR 或者是一個自定義的錯誤信息的字符串。

再看一下這個函數(shù)被調(diào)用的時候,傳入的三個參數(shù)。

  • cf: 該參數(shù)里面保存從配置文件讀取到的原始字符串以及相關的一些信息。特別注意的是這個參數(shù)的args字段是一個 ngx_str_t類型的數(shù)組,該數(shù)組的首個元素是這個配置指令本身,第二個元素是指令的第一個參數(shù),第三個元素是第二個參數(shù),依次類推。

  • cmd: 這個配置指令對應的 ngx_command_t 結(jié)構(gòu)。

  • conf: 就是定義的存儲這個配置值的結(jié)構(gòu)體,比如在上面展示的那個 ngx_http_hello_loc_conf_t。當解析這個 hello_string 變量的時候,傳入的 conf 就指向一個 ngx_http_hello_loc_conf_t 類型的變量。用戶在處理的時候可以使用類型轉(zhuǎn)換,轉(zhuǎn)換成自己知道的類型,再進行字段的賦值。

為了更加方便的實現(xiàn)對配置指令參數(shù)的讀取,Nginx 已經(jīng)默認提供了對一些標準類型的參數(shù)進行讀取的函數(shù),可以直接賦值給 set 字段使用。下面來看一下這些已經(jīng)實現(xiàn)的 set 類型函數(shù)。

  • ngx_conf_set_flag_slot: 讀取 NGX_CONF_FLAG 類型的參數(shù)。
  • ngx_conf_set_str_slot:讀取字符串類型的參數(shù)。
  • ngx_conf_set_str_array_slot: 讀取字符串數(shù)組類型的參數(shù)。
  • ngx_conf_set_keyval_slot: 讀取鍵值對類型的參數(shù)。
  • ngx_conf_set_num_slot: 讀取整數(shù)類型(有符號整數(shù) ngx_int_t)的參數(shù)。
  • ngx_conf_set_size_slot:讀取 size_t 類型的參數(shù),也就是無符號數(shù)。
  • ngx_conf_set_off_slot: 讀取 off_t 類型的參數(shù)。
  • ngx_conf_set_msec_slot: 讀取毫秒值類型的參數(shù)。
  • ngx_conf_set_sec_slot: 讀取秒值類型的參數(shù)。
  • ngx_conf_set_bufs_slot: 讀取的參數(shù)值是 2 個,一個是 buf 的個數(shù),一個是 buf 的大小。例如: output_buffers 1 128k;
  • ngx_conf_set_enum_slot: 讀取枚舉類型的參數(shù),將其轉(zhuǎn)換成整數(shù) ngx_uint_t 類型。
  • ngx_conf_set_bitmask_slot: 讀取參數(shù)的值,并將這些參數(shù)的值以 bit 位的形式存儲。例如:HttpDavModule 模塊的 dav_methods 指令。

conf: 該字段被 NGX_HTTP_MODULE 類型模塊所用 (我們編寫的基本上都是 NGX_HTTP_MOUDLE,只有一些 Nginx 核心模塊是非 NGX_HTTP_MODULE),該字段指定當前配置項存儲的內(nèi)存位置。實際上是使用哪個內(nèi)存池的問題。因為 http 模塊對所有 http 模塊所要保存的配置信息,劃分了 main, server 和 location 三個地方進行存儲,每個地方都有一個內(nèi)存池用來分配存儲這些信息的內(nèi)存。這里可能的值為 NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET 或 NGX_HTTP_LOC_CONF_OFFSET。當然也可以直接置為 0,就是 NGX_HTTP_MAIN_CONF_OFFSET。

offset: 指定該配置項值的精確存放位置,一般指定為某一個結(jié)構(gòu)體變量的字段偏移。因為對于配置信息的存儲,一般我們都是定義個結(jié)構(gòu)體來存儲的。那么比如我們定義了一個結(jié)構(gòu)體 A,該項配置的值需要存儲到該結(jié)構(gòu)體的 b 字段。那么在這里就可以填寫為 offsetof(A, b)。對于有些配置項,它的值不需要保存或者是需要保存到更為復雜的結(jié)構(gòu)中時,這里可以設置為 0。

post: 該字段存儲一個指針??梢灾赶蛉魏我粋€在讀取配置過程中需要的數(shù)據(jù),以便于進行配置讀取的處理。大多數(shù)時候,都不需要,所以簡單地設為 0 即可。

看到這里,應該就比較清楚了。ngx_http_hello_commands 這個數(shù)組每 5 個元素為一組,用來描述一個配置項的所有情況。那么如果有多個配置項,只要按照需要再增加 5 個對應的元素對新的配置項進行說明。

需要注意的是,就是在ngx_http_hello_commands這個數(shù)組定義的最后,都要加一個ngx_null_command作為結(jié)尾。

模塊上下文結(jié)構(gòu)

這是一個 ngx_http_module_t 類型的靜態(tài)變量。這個變量實際上是提供一組回調(diào)函數(shù)指針,這些函數(shù)有在創(chuàng)建存儲配置信息的對象的函數(shù),也有在創(chuàng)建前和創(chuàng)建后會調(diào)用的函數(shù)。這些函數(shù)都將被 Nginx 在合適的時間進行調(diào)用。

    typedef struct {
        ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
        ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

        void       *(*create_main_conf)(ngx_conf_t *cf);
        char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

        void       *(*create_srv_conf)(ngx_conf_t *cf);
        char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

        void       *(*create_loc_conf)(ngx_conf_t *cf);
        char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
    } ngx_http_module_t; 
  • preconfiguration: 在創(chuàng)建和讀取該模塊的配置信息之前被調(diào)用。

  • postconfiguration: 在創(chuàng)建和讀取該模塊的配置信息之后被調(diào)用。

  • create_main_conf: 調(diào)用該函數(shù)創(chuàng)建本模塊位于 http block 的配置信息存儲結(jié)構(gòu)。該函數(shù)成功的時候,返回創(chuàng)建的配置對象。失敗的話,返回 NULL。

  • init_main_conf: 調(diào)用該函數(shù)初始化本模塊位于 http block 的配置信息存儲結(jié)構(gòu)。該函數(shù)成功的時候,返回 NGX_CONF_OK。失敗的話,返回 NGX_CONF_ERROR 或錯誤字符串。

  • create_srv_conf: 調(diào)用該函數(shù)創(chuàng)建本模塊位于 http server block 的配置信息存儲結(jié)構(gòu),每個 server block 會創(chuàng)建一個。該函數(shù)成功的時候,返回創(chuàng)建的配置對象。失敗的話,返回 NULL。

  • merge_srv_conf: 因為有些配置指令既可以出現(xiàn)在 http block,也可以出現(xiàn)在 http server block 中。那么遇到這種情況,每個 server 都會有自己存儲結(jié)構(gòu)來存儲該 server 的配置,但是在這種情況下 http block 中的配置與 server block 中的配置信息發(fā)生沖突的時候,就需要調(diào)用此函數(shù)進行合并,該函數(shù)并非必須提供,當預計到絕對不會發(fā)生需要合并的情況的時候,就無需提供。當然為了安全起見還是建議提供。該函數(shù)執(zhí)行成功的時候,返回 NGX_CONF_OK。失敗的話,返回 NGX_CONF_ERROR 或錯誤字符串。

  • create_loc_conf: 調(diào)用該函數(shù)創(chuàng)建本模塊位于 location block 的配置信息存儲結(jié)構(gòu)。每個在配置中指明的 location 創(chuàng)建一個。該函數(shù)執(zhí)行成功,返回創(chuàng)建的配置對象。失敗的話,返回 NULL。

  • merge_loc_conf: 與 merge_srv_conf 類似,這個也是進行配置值合并的地方。該函數(shù)成功的時候,返回 NGX_CONF_OK。失敗的話,返回 NGX_CONF_ERROR 或錯誤字符串。

Nginx 里面的配置信息都是上下一層層的嵌套的,對于具體某個 location 的話,對于同一個配置,如果當前層次沒有定義,那么就使用上層的配置,否則使用當前層次的配置。

這些配置信息一般默認都應該設為一個未初始化的值,針對這個需求,Nginx 定義了一系列的宏定義來代表各種配置所對應數(shù)據(jù)類型的未初始化值,如下:

    #define NGX_CONF_UNSET       -1
    #define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
    #define NGX_CONF_UNSET_PTR   (void *) -1
    #define NGX_CONF_UNSET_SIZE  (size_t) -1
    #define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1

又因為對于配置項的合并,邏輯都類似,也就是前面已經(jīng)說過的,如果在本層次已經(jīng)配置了,也就是配置項的值已經(jīng)被讀取進來了(那么這些配置項的值就不會等于上面已經(jīng)定義的那些 UNSET 的值),就使用本層次的值作為定義合并的結(jié)果,否則,使用上層的值,如果上層的值也是這些UNSET類的值,那就賦值為默認值,否則就使用上層的值作為合并的結(jié)果。對于這樣類似的操作,Nginx 定義了一些宏操作來做這些事情,我們來看其中一個的定義。

    #define ngx_conf_merge_uint_value(conf, prev, default) \
        if (conf == NGX_CONF_UNSET_UINT) {      \
            conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev; \
        }

顯而易見,這個邏輯確實比較簡單,所以其它的宏定義也類似,我們就列舉其中的一部分吧。

    ngx_conf_merge_value
    ngx_conf_merge_ptr_value
    ngx_conf_merge_uint_value
    ngx_conf_merge_msec_value
    ngx_conf_merge_sec_value

等等。

下面來看一下 hello 模塊的模塊上下文的定義,加深一下印象。

    static ngx_http_module_t ngx_http_hello_module_ctx = {
        NULL,                          /* preconfiguration */
        ngx_http_hello_init,           /* postconfiguration */

        NULL,                          /* create main configuration */
        NULL,                          /* init main configuration */

        NULL,                          /* create server configuration */
        NULL,                          /* merge server configuration */

        ngx_http_hello_create_loc_conf, /* create location configuration */
        NULL                        /* merge location configuration */
    };

注意:這里并沒有提供 merge_loc_conf 函數(shù),因為我們這個模塊的配置指令已經(jīng)確定只出現(xiàn)在 NGX_HTTP_LOC_CONF 中這一個層次上,不會發(fā)生需要合并的情況。

模塊的定義

對于開發(fā)一個模塊來說,我們都需要定義一個 ngx_module_t 類型的變量來說明這個模塊本身的信息,從某種意義上來說,這是這個模塊最重要的一個信息,它告訴了 Nginx 這個模塊的一些信息,上面定義的配置信息,還有模塊上下文信息,都是通過這個結(jié)構(gòu)來告訴 Nginx 系統(tǒng)的,也就是加載模塊的上層代碼,都需要通過定義的這個結(jié)構(gòu),來獲取這些信息。

我們先來看下 ngx_module_t 的定義

    typedef struct ngx_module_s      ngx_module_t;
    struct ngx_module_s {
        ngx_uint_t            ctx_index;
        ngx_uint_t            index;
        ngx_uint_t            spare0;
        ngx_uint_t            spare1;
        ngx_uint_t            abi_compatibility;
        ngx_uint_t            major_version;
        ngx_uint_t            minor_version;
        void                 *ctx;
        ngx_command_t        *commands;
        ngx_uint_t            type;
        ngx_int_t           (*init_master)(ngx_log_t *log);
        ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
        ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
        ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
        void                (*exit_thread)(ngx_cycle_t *cycle);
        void                (*exit_process)(ngx_cycle_t *cycle);
        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;
    };

    #define NGX_NUMBER_MAJOR  3
    #define NGX_NUMBER_MINOR  1
    #define NGX_MODULE_V1          0, 0, 0, 0,                              \
        NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR, NGX_NUMBER_MINOR
    #define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0

再看一下 hello 模塊的模塊定義。

    ngx_module_t ngx_http_hello_module = {
        NGX_MODULE_V1,
        &ngx_http_hello_module_ctx,    /* module context */
        ngx_http_hello_commands,       /* module directives */
        NGX_HTTP_MODULE,               /* module type */
        NULL,                          /* init master */
        NULL,                          /* init module */
        NULL,                          /* init process */
        NULL,                          /* init thread */
        NULL,                          /* exit thread */
        NULL,                          /* exit process */
        NULL,                          /* exit master */
        NGX_MODULE_V1_PADDING
    };

模塊可以提供一些回調(diào)函數(shù)給 Nginx,當 Nginx 在創(chuàng)建進程線程或者結(jié)束進程線程時進行調(diào)用。但大多數(shù)模塊在這些時刻并不需要做什么,所以都簡單賦值為 NULL。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號