歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Nginx的模塊開發指南

Nginx的模塊開發指南

日期:2017/3/1 11:52:05   编辑:關於Linux

0.先決條件

你應該熟悉C,呃,當然不是語法層次上的熟悉; 你應該知道結構體,不要被什麼指針和函數引用嚇跑,並認識到預處理器的。 如果你需要提升這些的話,沒有什麼比這本書好了 。《The C Programming Language》

HTTP有基本的了解是有益的。 你會工作在Web服務器上,畢竟。

您還應該熟悉Nginx的配置文件。 如果你熟悉,這裡是它的要點:有四個上下文contexts (稱為main, server, upstream, 和 location),它可以包含一個或多個參數的指令。 在main上下文指令適用於一切; 在server上下文指令適用於一個特定的主機、端口; 在upstream上下文指令適用於一組後端服務器; location上下文指令只適用於匹配網絡位置(如”/”, “/images” 等)。
location上下文從server上下文繼承,server上下文從main上下文繼承。 upstream既不繼承,也沒有賦予它的屬性; 它有沒有真正在其他地方適用其自己的特殊指令。 我將把上述四個環境相當多,所以…不要忘記他們。
(或者可以看看這個《(總結)Nginx配置文件nginx.conf中文詳解》)

讓我們開始吧。。。。

1.Ngnix高級概述之模塊授權( Module Delegation)

Nginx的模塊有三個角色,我們會逐一介紹它們。

處理程序處理請求並產生輸出 用一個handler對輸出進行過濾 當超過一個合適的後端服務器存在時候,負載均衡器選擇後端服務器發送一個請求。

這些模塊做的真正工作,也許你會想與web服務器聯系起來。當Nginx需要提供文件或需要代理到另一台服務器的請求,這時候就需要一個處理程序模塊; 當Ngnix需要輸出一個壓縮文件或者是執行一個遠程的服務器程序,這時候就需要一個過濾器模塊;

Nginx的核心只是照顧所有的網絡和應用協議和設置模塊,可處理一個請求的序列。在集中式體系結構中,它對於你來說就像是一個很好的獨立單元,去完成著工作。呃,簡單兩個圖說明什麼是集中式體系結構,或者說集中式系統對應的是分布式系統。

集中式:這裡寫圖片描述

分布式:這裡寫圖片描述vczYtqi1xM671sOho8jnufvT0NK7uPbS1MnPtcS0psDts8zQ8sGsvdO1vczYtqi1xM671sOjrNa709DSu7j2u+EmbGRxdW87064mcmRxdW87o6i1q9K7uPa6w7XExeTWw87EvP6yu7vhyMOz5c27t6LJ+qOpoaMgtKbA7bPM0PK/ydLUzai5/cj91ta3vcq9t7W72KO60rvH0La8uty6w6Os09C07c7zo6zSsr/J0tQ/P77cvvi0psDtx+vH86OssqLNxrPZtb3ErMjPtcS0psDts8zQ8qOozaizo8rH0rvQqb6yzKzOxLz+o6mho6Oo0uvV39eio7q/ycTcyse9stXiuPbS4su8o6zU2rf+zvHG98XcxvDAtMqxuvKjrMO/uPa0psDts8zQ8rvhwbS909K7uPbWuLaotcTEo7/po6zIu7rzyOe5+9PQz+DNrLXEtKbA7bPM0PLBtL3TzazSu7j2xKO/6dTy1rvT0NK7uPazybmmoaO087jFysfV4rj20uLLvLDJP6OpPC9wPg0KPHA+yOe5+7SmwO3G98rH0ru49re0z/K0+sDttcTSu9CpuvO2y7f+zvHG96Osu7nT0MHt0rvW1sDg0M21xMSjv+m85LXEuLrU2Ma9uuKhoyC4utTYxr264sb30OjSqtK7uPbH68fzus3Su9fpuvO2y7f+zvHG96OssqK+9raoxMS49rf+zvHG972ru/G1w7XEx+vH86GjIE5naW54tcQ8c3Ryb25nPri9tPjBvbj2uLrU2L75uuLEo7/pPC9zdHJvbmc+o7o8L3A+DQrC1tGvo6zP8bTyxsu/y8TH0fnC1sH318Wz6cXGxMfR+aOswtbXxcC0oaMgSVDJosHQo6zS0dPDwLTIt7Gj0rvQqczYyuK1xMfrx/O74cO/tM6077W9z+DNrLXEuvO2y7f+zvHG98nPoaMNCjxwPjxzdHJvbmc+yOe5+7SmwO2zzNDysruy+sn6tO3O86Os1PK4w7n9wsvG97G7tffTw6GjPC9zdHJvbmc+ILbguPa5/cLLxve/ydLUway907W9w7+49s671sOjrNLUseOjqL7ZwP3X06Opz+zTpr/J0tSxu9G5y/WjrMi7uvO31r/poaMgy/vDx9a00NC1xMuz0PLKx9TaseDS68qxyLe2qLXEoaMguf3Cy8b309DJ6LzGxKPKvb6tteS1xDxzdHJvbmc+JmxkcXVvO9TwyM7BtCZyZHF1bzs8L3N0cm9uZz6jutK7uPa5/cLLxvexu7X308PKsaOs1/bG5Lmk1/ejrMi7uvO199PDz8LSu7j2uf3Cy8b3o6zWsbW91+6687XEuf3Cy8b3sbu199PDo6xOZ2lueLXEzeqzycHLz+zTpqGjPC9wPg0KPHA+09C52Ln9wsvG98G0tcS63L/htcTKx6Osw7/Su7j2uf3Cy8b3sru1yLT9x7DD5rXEuf3Cy8b3wLTN6rPJO9LyzqrL/L/J0tS0psDt0tTHsLn9wsvG97XEyuSz9qOsyLu687SmwO3N6tf3zqrX1Ly6tcTK5LP2o6zT0LXjz/FsaW51eM+1zbO1xLnctcDSu9H5o6zSu7j2ysfJ+rL61d+jrNK7uPbKx8/7t9HV36Gj1rvKx9PQteO/4ezFtcTKx6Osvs3P8cqzzu/BtKOs0rvR+aOsw7+49s/7t9HV37/JxNzSss6qz8LSu7y2tcTP+7fR1d/M4bmpxNzBv9K70fmho6GjoaO2+Ln9wsvG98nPtcS7urPlx/ggo6zV4s2os6PKx9K7uPbSs8Pmo6g0S6OptcS55sSjvq3TqqOsy+TIu8Tjv8nS1NTaxOO1xG5naW54LmNvbma4xLHk1eLW1sfpv/aho77ZwP3X07DJo6zV4r7N0uLOttfF0ru49sSjv+m/ydLU1Nq/qsq80bnL9brzo6y+zciltKvK5L/Nt/62y6OsvLTKubu5w7vT0LXDtb3V+7j20bnL9brztcS94bn7oaMo0rux39G5y/XSu7Hfyc+05qOs1+7NqMvXtcTA7b3io6zfwKOsuPbIy8jPzqrKx9XiuPbS4su8tcQpIMyrusPBy6OhPC9wPg0KPHA+0vK0y6Os19y94bXEuMXE7rjFyvajrLXk0M21xLSmwO3W3MbayOfPwqO6PC9wPg0KPHA+v827p7bLt6LLzUhUVFDH68fzJnJhcnI7Tmdpbni1xLj5vt2xvrXYxeTWw87EvP7RodTxus/KyrXEaGFuZGxlciZyYXJyO6Oox7DM4crHuvO2y7f+zvHG99Kqus/KyqOpuLrU2Ma9uuLG98z00aHSu7j2uvO2y7f+zvHG9yZyYXJyO7SmwO2zzNDy1/a1xMrCx+mjrLKivavDv7j2yuSz9ru6s+XH+LXEtdrSu7j2uf3Cy8b3JnJhcnI7tdrSu7n9wsu1xM/g06a0psDts8zQ8r2ryuSz9rW9tdq2/rn9wsvG9yZyYXJyO7Xatv7WwbXayP0mcmFycju12sj9tb212svEJnJhcnI7tcgmcmFycjvX7tbVz+zTpreiy821vb/Nu6e2yzwvcD4NCjxwPs7Sy7UmbGRxdW87zaizoyZyZHF1bzujrMrH0vLOqk5naW54tcS1xMSjv+m199PDysfM2LHwtqjWxrXEoaPL/LDR0ru49rrctPO1xLi6taO3xdTa0LTEo7/pxeTWw7XExMe49rzSu+/J7cnPKNKyvs3Kx7PM0PLUsc2s0acmaGVsbGlwOymjrMC0tqjS5cjnus7S1Lywus7KsbjDxKO/6dOmuMPUy9DQo6jO0r71tcPMq7TztcS4urWjo6mho7X308PKtbzKyc/Kx82ouf3Su8+1wdDK/cS/srvJ2bXEu9i196Os0rK+zcrHy7WjrMTjv8nS1MzhuanSu7j2uq/K/cC01rTQ0KO6PC9wPg0Kvs3U2rf+zvHG97bByKHF5NbDzsS8/taux7AgttTT2sv5z9TKvrXEzrvWw7rNt/7O8cb3tcTDv9K7uPbF5NbD1rjB7iC1sU5naW54tcSz9cq8u69tYWluIMXk1sMgtbFOZ2lueLP1yry7r3NlcnZlciCjqLy01ve7+i+2y7/ao6nF5NbDILWxTmdpbni77LrPc2VydmVyxeTWw9PrbWFpbsXk1sMgtbFOZ2lueMXk1sOz9cq8u69sb2NhdGlvbsXk1sMgtbFOZ2lueLrPsqJsb2NhdGlvbsXk1sPT68bkcGFyZW50IHNlcnZlcsXk1sMgtbFuZ2lueLXE1ve9+LPMxvS2ryC1sdK7uPa5pNf3vfizzM3Ls/YgtbFtYXN0ZXK9+LPMzcuz9iC0psDt0ru49sfrx/Mguf3Cy8/s06bNtyC5/cLLz+DTpszlINGh1PG687bLt/7O8cb3IM/yuvO2y7f+zvHG97eixvDH68fzINbY0MLG9LavttS687bLt/7O8cb3tcTH68fzILSmwO3AtNfUuvO2y7f+zvHG97XEz+zTpiDN6rPJ0+u687bLt/7O8cb3tcS9u7ulPGJyIC8+DQq6w7zSu++joSDV4srH0ru49tPQtePKxrK7v8m1sqGjxOO/ydLUwPvTw9Xi0KmjrLHIyOfLtcC5vdjSu9CpzNi2qLXEz/vPoqOovMbL47v606LT79bQ19yz9s/WtcQmcmRxdW87aG9va3MmcmRxdW87IMrHwLm92MzYtqjP+8+itcTS4su8o6yyu8rHubPX07XE0uLLvCwsLCzAz83itcTKwL3nudu+v765ysfU9cO00fm1xKOs1PXDtM7SuNC+9cv7w8fLtbuwwt+8rbrDz/G6zc7Sw8fKx83qyKuyu9K70fm1xKGjoaOjqaOs1NmxyMjny7XA+9PDyc/D5szhtb21xNK7ttS6r8r9o6zWu9KqxOPA+9PD1eLQqcTjytbNt8nPtcTXytS0o6zE47/J0tTX9rW9usO24NPQ08O1xMrCx+mjrNLyzqrE47XEyKjBpsyrtPPByyZoZWxsaXA7usOwyaOsysfKsbryye7I67XEwcu94sSz0rvQqcSjv+nBy6Oho6GjoaOho6GjoaOho6ENCjxoMiBpZD0="2-nginx的模塊組件">2. Nginx的模塊組件

正如我所說的,你有很大的靈活性,當談到作出Nginx的模塊。 本節將描述幾乎總是存在的部分。 它的目的是為理解一個模塊的指南,以及當你覺得你已經准備好開始編寫一個模塊的引用。
2.1 模塊配置結構體
模塊最多可以定義三個配置結構,main,server,location。大多數模塊只需要一個位置配置。 這些命名約定是ngx_http__(main|srv|loc)_conf_t 。 舉例子,拿DAV模塊來說:

typedef struct {
    ngx_uint_t  methods;
    ngx_flag_t  create_full_put_path;
    ngx_uint_t  access;
} ngx_http_dav_loc_conf_t;

注意到Nginx的具有特殊的數據類型( ngx_uint_t和ngx_flag_t ); 這些都是你知道的基本數據類型的別名,如果你感興趣的話。可以看核心/ ngx_config.h這個可以滿足你的好奇心。
2.2 模塊指令
一個模塊的指令出現在一個ngx_command_ts靜態數組裡面。這裡有一個官網公布的例子,一個我(原文作者)寫的小小的模塊。

static ngx_command_t  ngx_http_circle_gif_commands[] = {
    { ngx_string("circle_gif"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_circle_gif,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("circle_gif_min_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_circle_gif_loc_conf_t, min_radius),
      NULL },
      ...
      ngx_null_command
};

一個模塊的指令出現在靜態ngx_command_t數組裡面(類似4在int數組中這個意思),這裡有一個官網公布的例子,一個我(原文作者)寫的小小的模塊。

static ngx_command_t  ngx_http_circle_gif_commands[] = {
    { ngx_string("circle_gif"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_circle_gif,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("circle_gif_min_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_circle_gif_loc_conf_t, min_radius),
      NULL },
      ...
      ngx_null_command
};

這裡是ngx_command_t 結構體的聲明,具體看 core/ngx_conf_file.h

struct ngx_command_t {
    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是指令字符串,沒有空格,通常這樣實例化(舉例子說)ngx_str(“proxy_pass”);
直接上定義,實際有點像c++的string一樣,用C語言模擬的一個string類,這樣能理解了吧。

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

type是一組標志,使用位操作按位與之類的,主要有:

NGX_HTTP_MAIN_CONF :指令是在main配置有效 NGX_HTTP_SRV_CONF :指令是在server配置有效 NGX_HTTP_LOC_CONF :指令在location配置有效 NGX_HTTP_UPS_CONF :指令是upstream配置有效 NGX_CONF_NOARGS :指令可以接受0參數 NGX_CONF_TAKE1 :指令可以接受1參數 NGX_CONF_TAKE2 :指令可以接受2參數 … NGX_CONF_TAKE7 :指令可以接受7參數 NGX_CONF_FLAG :指令取一個boolean(“開”或“關”) NGX_CONF_1MORE :指令可以接受大於等於1個參數 NGX_CONF_2MORE :指令可以接受大於等於2個參數

還有超級多其他的選項,具體的參考core/ngx_conf_file.h.
set是一個函數指針,指向一個函數,該函數功能是設置模塊配置。通常此功能將轉化傳遞給這個指令的參數,並保存在它的配置結構(configuration struct)適當的值。 這種設置功能將需要三個參數:

一個指向ngx_conf_t結構的指針,注:ngx_conf_t包含傳給該指令的參數 一個指針指向當前ngx_command_t結構 一個指針指向模塊的定制配置結構(configuration struct)

當遇到指令時,該設置函數將被調用。 nginx的提供了許多功能,在自定義配置結構設置特定類型的值。 這些功能包括:

ngx_conf_set_flag_slot :轉換“開”或“關”,1或0 ngx_conf_set_str_slot :保存字符串作為ngx_str_t ngx_conf_set_num_slot :解析一個數字,並將其保存到一個int ngx_conf_set_size_slot :解析數據大小(“8K”,“1m”等)並將其保存到一個size_t

還有一些很方便的功能,具體的參考 /core/ ngx_conf_file.h
模塊還可以把一個參照自己的功能在這裡,如果內置的插件是不夠好

這些內置函數講如何知道在哪裡保存數據? 這就是接下來的ngx_command_t接下的兩個成員:conf和offset來做的事情了 。

conf告訴Nginx的這個值是否會被保存到模塊的main配置,server配置,或location配置(對應NGX_HTTP_MAIN_CONF_OFFSET , NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET )
-offset指定寫入配置結構(configuration struct)的哪些部分
最後,post只是一個指針,當正在讀配置文件時候,它可能需要指向其他休息的模塊。它通常為NULL。(原文:Finally, post is just a pointer to other crap the module might need while it’s reading the configuration. It’s often NULL.)
ngx_null_command作為命令序列的終止,放在最後一位。(譯者注,就是有點類似C語言的字符串結尾’\0’一樣,起到標識結尾的作用,不懂可以ctrl+f定位一下ngx_null_command,就能發現static ngx_command_t ngx_http_circle_gif_commands[]中最後一個就是ngx_null_command)
2.3 模塊上下文
這裡有一個靜態的ngx_http_module_t結構,這玩意兒只是大量的函數引用,這些函數作用都是用於創建三個配置並將它們合並在一起之類的。
因此,這些函數引用分別由: 預配置 preconfiguration 配置後 postconfiguration 創建main配置(即做一個malloc和設置默認值) 初始化主要配置(默認設置nginx.conf裡的內容) 創建server 的配置

與server 的配置的配置合並

這些不同的參數取決於他們在做什麼。 這裡的結構定義,取自HTTP / ngx_http_config.h所以你可以看到不同的回調函數簽名:

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;

你可以設置不需要的功能為NULL,Ngnix會搞定它的~~
大多數的handlers 只是使用最後的兩個,一個功能用於給特定位置configuration分配內存(ngx_http_create_loc_conf ),另外一個功能用於設置默認值,並將此配置合並到任何繼承的配置中(ngx_http_merge_loc_conf)。如果配置無效,則合並函數也負責產生錯誤;這些錯誤停止服務器啟動。

這裡有一個例子模塊上下文結構( module context struct):

static ngx_http_module_t  ngx_http_circle_gif_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

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

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

    ngx_http_circle_gif_create_loc_conf,  /* create location configuration */
    ngx_http_circle_gif_merge_loc_conf /* merge location configuration */
};

是時候更深入了解他們了。所有的配置看起來都像是調用相同的Ngnix API一樣,你們不覺得這樣很應該深入了解他們嗎?

2.3.1 create_loc_conf

這是一個極其簡單的create_loc_conf功能的樣子,從我寫的circle_gif模塊中搞來的,見源它需要一個指令結構( ngx_conf_t ),並返回一個新創建的模塊配置結構(在本例子中是ngx_http_circle_gif_loc_conf_t )

static void *
ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_circle_gif_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->min_radius = NGX_CONF_UNSET_UINT;
    conf->max_radius = NGX_CONF_UNSET_UINT;
    return conf;
}

要注意的第一件事是Nginx的內存分配;它考慮到正在free時候,如果模板正在使用ngx_palloc (一個malloc 的封裝函數)或者ngx_pcalloc (一個cmalloc 的封裝函數).
可能UNSET常數是ngx_conf_unset_uint,ngx_conf_unset_ptr,ngx_conf_unset_size,ngx_conf_unset_msec,和捕捉所有ngx_conf_unset。UNSET告訴合並功能,應該重寫的值。
2.3.2 merge_loc_conf
這裡的circle_gif模塊中使用的合並功能:

static char *
ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_circle_gif_loc_conf_t *prev = parent;
    ngx_http_circle_gif_loc_conf_t *conf = child;

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);
    ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);

    if (conf->min_radius < 1) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 
            "min_radius must be equal or more than 1");
        return NGX_CONF_ERROR;
    }
    if (conf->max_radius < conf->min_radius) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 
            "max_radius must be equal or more than min_radius");
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

首先要注意的Nginx為不同的數據類型提供了很好的融合功能( ngx_conf_merge__value ); 參數是:

這個位置的值 this location’s value 值繼承,如果#1沒有設置 the value to inherit if #1 is not set 默認如果沒有#1#也2被設置 the default if neither #1 nor #2 is set

然後將結果存儲在第一個參數。 可用的合並功能包括ngx_conf_merge_size_value , ngx_conf_merge_msec_value和其他。 見core/ ngx_conf_file.h的完整列表。

小問題:如何將這些功能寫的第一個參數,因為第一個參數是按值傳遞嗎?
答:這些功能是由預處理器定義,然後你們懂了....

注意這裡還是會產生錯誤:函數寫的東西到日志文件,並返回NGX_CONF_ERROR 。返回代碼暫停服務器啟動。(由於消息是NGX_LOG_EMERG級別記錄,該消息將標准輸出,僅供參考core/ ngx_log.h, 這裡有日志級別的列表。)

2.4 模塊定義
計算機世界裡面有一句名言,計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決。接下來我們添加一個間接更多的層, ngx_module_t結構。 該變量稱為ngx_http__module 。這是對上下文和指令的引用,以及余下的回調函數。模塊定義有時被用作key來查找與特定模塊相關的數據。 該模塊的定義通常是這樣的:

ngx_module_t  ngx_http__module = {
    NGX_MODULE_V1,
    &ngx_http__module_ctx, /* module context */
    ngx_http__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
};

替換恰如其分。模塊可以添加的回調,進程/線程創建和死亡,但大部分模塊讓事情變得簡單。 (傳遞給每一個回調的參數,請參閱 core/ ngx_conf_file.h)。

2.5 模塊安裝

安裝模塊是否合適,這取決於模塊是處理,過濾,或負載平衡器
The proper way to install a module depends on whether the module is a handler, filter, or load-balancer

3.處理程序

現在我們會把一些小的模塊放在顯微鏡下觀察它們是如何工作的。

3.1 Handler 剖析(非代理)
處理程序通常做四件事: 獲得 位置配置(location configuration), 生成相應的響應, 發送頭部, 和發送主體.。處理程序有一個參數就是和請求結構。 請求結構有很多關於客戶端的請求,如請求方法,URI,和頭有關的信息。我們將會逐一分析他們。

3.1.1 獲得 位置配置(location configuration)
這部分容易。 所有你需要做的就是調用ngx_http_get_module_loc_conf並傳入當前請求結構和模塊定義。比如我寫的ngx_http_circle_gif_handler

static ngx_int_t
ngx_http_circle_gif_handler(ngx_http_request_t *r)
{
    ngx_http_circle_gif_loc_conf_t  *circle_gif_config;
    circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
    。。。

現在我有了我在合並函數中設置的所有變量
3.1.2 生成相應的響應
這是模塊實際上做的工作中最有趣的一部分。
這時候你需要知道這個有用的請求結構

typedef struct {
...
/* the memory pool, used in the ngx_palloc functions */
    ngx_pool_t                       *pool; 
    ngx_str_t                         uri;
    ngx_str_t                         args;
    ngx_http_headers_in_t             headers_in;

...
} ngx_http_request_t;

uri是請求的路徑,例如“/query.cgi”。
args是問號(如“NAME =john”)之後的請求的一部分。(譯者注,自己看看浏覽器在跳轉時候,域名的變化…就知道啦)
headers_in有很多有用的東西,如cookies和浏覽器的信息,但許多模塊不需要這玩意兒。呃,如果你有興趣。請看這裡,HTTP / ngx_http_request.h
這應該是足夠的信息,以產生一些有用的輸出。完整的ngx_http_request_t結構中可以找到http/ngx_http_request.h
3.1.3 發送頭部
響應頭存活在一個叫headers_out的結構中,這個結構是對的請求結構引用。handler 設置它想要的那些,然後調用ngx_http_send_header(r) 一些有用的部分headers_out包括:

typedef stuct {
...
    ngx_uint_t                        status;
    size_t                            content_type_len;
    ngx_str_t                         content_type;
    ngx_table_elt_t                  *content_encoding;
    off_t                             content_length_n;
    time_t                            date_time;
    time_t                            last_modified_time;
..
} ngx_http_headers_out_t;

(其余可以在這裡找到:http/ngx_http_request.h )。
舉例來說,如果一個模塊設置Content-Type為“image / GIF”,內容長度設置為100,並返回一個200 OK響應,該代碼會做的伎倆:

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 100;
    r->headers_out.content_type.len = sizeof("image/gif") - 1;
    r->headers_out.content_type.data = (u_char *) "image/gif";
    ngx_http_send_header(r);

大多數合法的HTTP頭是可用的,這種情況下你一般會感覺到很開心,然而,一些頭設置有一點麻煩,相比你在上面看到的那些;例如,content_encoding類型(ngx_table_elt_t *),模塊必須為它分配內存。這是用一個被調用的函數完成ngx_list_push ,這需要在一個ngx_list_t (類似於一個數組),並返回一個引用,這個引用指向list 剛剛創建出來的成員(類型ngx_table_elt_t )。 下面的代碼編碼設置為”deflate” ,然後發送頭部。

  r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
    if (r->headers_out.content_encoding == NULL) {
        return NGX_ERROR;
    }
    r->headers_out.content_encoding->hash = 1;
    r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
    r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
    r->headers_out.content_encoding->value.len = sizeof("deflate") - 1;
    r->headers_out.content_encoding->value.data = (u_char *) "deflate";
    ngx_http_send_header(r);

這種機制通常被用來當一個頭可以同時在多個值;它(理論上)更便於過濾器模塊來增加和同時保留其他刪除某一些值,因為他們不必訴諸於字符串操作。
3.1.4 發送主體
現在,模塊已經產生一個響應,並把它在內存中,它需要分配給一個特殊的緩沖區的反應,然後分配緩沖區鏈鏈接 , 然後調用鏈條上的“發送體”的功能。

什麼是鏈節? nginx的允許處理模塊生成(和過濾器模塊處理)響應一次一個緩沖器; 每個鏈節保持的指針鏈中的下一個鏈接,或者NULL ,如果它是最後一個。 我們將保持簡單,假設只有一個緩沖區。

首先,一個模塊將宣布緩沖器和鏈條:

    ngx_buf_t    *b;
    ngx_chain_t   out;

下一個步驟是對我們的響應數據分配緩沖區並且指向它:

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 
            "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->pos = some_bytes; /* first position in memory of the data */
    b->last = some_bytes + some_bytes_length; /* last position */

    b->memory = 1; /* content is in read-only memory */
    /* (i.e., filters should copy it rather than rewrite in place) */

    b->last_buf = 1; /* there will be no more buffers in the request */

現在,模塊將它連接到鏈上。

     out.buf = B;
     out.next = NULL;

最後,我們返回主體(boby),並返回輸出過濾器鏈的狀態代碼

    return ngx_http_output_filter(r, &out);

緩沖區鏈的Nginx的IO模型的重要組成部分,所以你必須熟悉它們的工作原理。

小問題:為什麼緩沖區有last_buf變量,我們可以告訴我們正處在一個鏈的末端通過檢查“下一個” NULL ?
回答:A鏈可能是不完整的,也就是說,有多個緩沖區,而不是在此請求或響應所有緩沖器。 因此,一些緩沖區在鏈的末端,但不是請求的結束。 這給我們帶來...

明天再更吧。。。。。。

=======================================================
好吧,我們接著往下寫

3.2。 upstream(即代理服務器)處理器的剖析

我揮動雙手,讓你的處理程序生成一個響應。有時你會能夠得到這種應對只是一段 C 代碼,但往往你會想要談到另一台服務器 (例如,如果您正在編寫一個模塊來執行另一個網絡協議)。你可以親自做所有的網絡編程,但是如果你接收到部分響應會發生什麼?你不想要阻止與您自己的事件循環的主事件循環,而你在等待響應的休息。你會殺了 Nginx 的性能。幸運的是,Nginx 可以掛接到其自身機制的權利處理後端服務器 (稱為”上游”),這樣您的模塊可以談到另一台服務器不妨礙其他請求。本節描述如何模塊會談到上游,例如 Memcached、 FastCGI 或另一個 HTTP 服務器。
3.2.1.摘要上游回調
與其他模塊的處理程序函數,不同的是上游的模塊的處理程序函數做小小的”真正的工作”。它並不調用ngx_http_output_filter。它只是設置准備寫入和讀取從上游服務器時將調用的回調。有實際上 6 可用掛鉤(掛鉤意思:攔截指定的消息,並用自己的方式處理)
create_request:精心制作一個請求緩沖區 (或鏈著他們) 發送到上游
reinit_request被稱為如果到後端的連接被重置 (只是之前create_request被稱為第二次)
process_header處理的第一位的上游的反應,並通常保存一個指針,指向上游的”負載”
如果客戶端中止請求,則調用abort_request
Nginx 完成時調用finalize_request從上游讀取消息
input_filter是一個正文過濾器,可以在響應正文調用 (例如,要刪除一個預告片)
這些該如何連接?這裡是一個簡化的版的代理模塊處理程序 ︰

static ngx_int_t
ngx_http_proxy_handler(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_upstream_t        *u;
    ngx_http_proxy_loc_conf_t  *plcf;

    plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);

/* set up our upstream struct */
    u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
    if (u == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    u->peer.log = r->connection->log;
    u->peer.log_error = NGX_ERROR_ERR;

    u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;

    u->conf = &plcf->upstream;

/* attach the callback functions */
    u->create_request = ngx_http_proxy_create_request;
    u->reinit_request = ngx_http_proxy_reinit_request;
    u->process_header = ngx_http_proxy_process_status_line;
    u->abort_request = ngx_http_proxy_abort_request;
    u->finalize_request = ngx_http_proxy_finalize_request;

    r->upstream = u;

    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return rc;
    }

    return NGX_DONE;
}

它做一點內部事務,但重要的部件是回調。此外注意到關於ngx_http_read_client_request_body位。這位被設置時候, Nginx 已經完成從客戶端讀取另一個回調。
每個這些回調將做什麼?通常, reinit_request, abort_request和finalize_request將設置或重置一些排序的內部狀態,只有幾行字。真正的主力是create_request和process_header.

3.2.2.create_request 回調

為了簡單起見,假設我有上游服務器,讀取一個字符,並打印出兩個字符。我的函數會是什麼樣子?

create_request需要為單字符請求分配一個緩沖區,為該緩沖區分配鏈鏈接,然後指向那鏈條上游的結構。看起來會像這樣 ︰

static ngx_int_t
ngx_http_character_server_create_request(ngx_http_request_t *r)
{
/* make a buffer and chain */
    ngx_buf_t *b;
    ngx_chain_t *cl;

    b = ngx_create_temp_buf(r->pool, sizeof("a") - 1);
    if (b == NULL)
        return NGX_ERROR;

    cl = ngx_alloc_chain_link(r->pool);
    if (cl == NULL)
        return NGX_ERROR;

/* hook the buffer to the chain */
    cl->buf = b;
/* chain to the upstream */
    r->upstream->request_bufs = cl;

/* now write to the buffer */
    b->pos = "a";
    b->last = b->pos + sizeof("a") - 1;

    return NGX_OK;
}

這不那麼壞,是嗎?當然,在現實生活中你會可能想要一些有意義的方式使用請求的 URI。它是可用,如ngx_str_t中r->uri,和 GET 參數是在r->args,別忘了你也享有請求標頭和cookies。

3.2.3 process_header 回調
現在是時候為process_header。正如create_request添加到請求正文, process_header 轉移指向的部分,則客戶將接收的響應的指針。它還從上游頭中讀取,並相應地設置客戶端響應標頭。

這裡是一個最低的例子,閱讀在這兩個字符響應。讓我們假設的第一個字符是”地位”字符。如果它是一個問號,我們想要返回到客戶端找不到 404 文件和無視其他字符。如果它是一個空間,然後我們要到客戶端和 200 OK 的響應返回的其他字符。好吧,它不是最有用的協議,但它是一個好的演示。我們如何能寫此process_header函數?

static ngx_int_t
ngx_http_character_server_process_header(ngx_http_request_t *r)
{
ngx_http_upstream_t *u;
u = r->upstream;

/* read the first character */
switch(u->buffer.pos[0]) {
    case '?':
        r->header_only; /* suppress this buffer from the client */
        u->headers_in.status_n = 404;
        break;
    case ' ':
        u->buffer.pos++; /* move the buffer to point to the next character */
        u->headers_in.status_n = 200;
        break;
}

return NGX_OK;

}
就是這樣,他做了操作hander、 更改pointer.請注意, headers_in實際上響應標頭結構像我們以前見過 (參見)http/ngx_http_request.h,但它可以填入從上游的標頭。一個真正的代理模塊將做更多頭處理,更何況錯誤處理,但已經得到這種的處理思想了。
但是……如果我們沒有從來自上游的一個緩沖區的整個頭部?
3.2.4 保持狀態
好吧,記得我怎麼說abort_request,reinit_request和finalize_request可用於復位內部狀態?這是因為許多上游模塊有內部狀態。該模塊需要定義一個自定義上下文結構來跟蹤它已經從上游迄今讀取。這是不一樣的“模塊上下文”上文提到的。這是一個預先定義的類型,而自定義背景下能有什麼元素,你需要的數據(這是你的結構)。這個上下文結構應該在內部被實例化的create_request功能,也許是這樣的:

    ngx_http_character_server_ctx_t   *p;   /* my custom context struct */

    p = ngx_pcalloc(r->pool, sizeof(ngx_http_character_server_ctx_t));
    if (p == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_http_set_ctx(r, p, ngx_http_character_server_module);

最後一行基本上注冊自定義上下文結構與特定的請求和模塊名稱,以便於以後檢索。每當你需要此上下文結構 (可能在回調),只是做 ︰

    ngx_http_proxy_ctx_t  *p;
    p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);

p會有的當前狀態。設置它,將其重置,遞增、 遞減,推任意數據在那裡,任何你想要的東西。這是偉大的方式使用持久性狀態機從上游讀取時返回數據中的塊,再無阻塞主事件循環。太好了 !
3.3.處理程序安裝
通過將代碼添加到回調的指令,使該模塊可以安裝處理程序。例如,這個有一個例子ngx_command_t 這樣:

    { ngx_string("circle_gif"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_circle_gif,
      0,
      0,
      NULL }

回調是第三個元素,在此案例ngx_http_circle_gif。還記得對此回調函數的參數是指令結構 (ngx_conf_t,其中包含用戶的參數)、 有關ngx_command_t結構和模塊的自定義配置結構體的指針。在本例子中,功能看起來像 ︰

static char *
ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_circle_gif_handler;

    return NGX_CONF_OK;
}

有下面兩個步驟 ︰ 第一,”核心”結構獲得這個位置,然後將處理程序分配給它。很簡單,是嗎?

我說過我只知道處理模塊。它是時間來移動到過濾器模塊,輸出過濾器鏈中的組件上。

4.過濾器

過濾器 操作生成的處理程序響應。過濾器操作的 HTTP 標頭和正文的過濾操作的響應內容。

4.1.標題篩選器的解剖

標題篩選器包括三個基本步驟 ︰

決定是否要動手術,這種反應 操作的相應 操作的下一個過濾器

舉一個例子,這裡的一個簡化的版的”不修改”標題過濾器,哪集到 304 狀態不修飾的如果客戶端的如果修改時間以來頭匹配響應的最後修改時間標頭。注意標題過濾器都需要在ngx_http_request_t結構中,作為唯一的參數,這使我們能夠訪問客戶端頭和很快發送響應標頭。

static
ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    time_t  if_modified_since;

    if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
                              r->headers_in.if_modified_since->value.len);

/* step 1: decide whether to operate */
    if (if_modified_since != NGX_ERROR && 
        if_modified_since == r->headers_out.last_modified_time) {

/* step 2: operate on the header */
        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
        r->headers_out.content_type.len = 0;
        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);
    }

/* step 3: call the next filter */
    return ngx_http_next_header_filter(r);
}

headers_out結構是一樣因為我們看見節中關於處理程序 (參見http/ngx_http_request.h),並可以操縱,沒有止境。

4.2 正文篩選器的解剖

緩沖區鏈使它有點棘手寫體篩選器中,因為正文過濾器可以一次只操作一個緩沖區 (鏈條)。模塊必須決定是否向覆蓋輸入緩沖區,替換緩沖區與新分配的緩沖區,或插入一個新的緩沖區之前或之後的緩沖區問題。把事情復雜化,有時模塊將接收幾個緩沖區,它已不完全緩沖鏈,它必須動手術。不幸的是,Nginx 不操縱緩沖區鏈,所以正文過濾器可以很難理解 (和寫) 提供一個高級別的 API。但是,這裡有一些你可能會看到在行動中的操作。

正文過濾器原型可能看起來像 (本例中取自 Nginx 源中的”分塊”篩選器) ︰

static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);

第一個參數是我們的老朋友請求結構。第二個參數是一個指針,指向當前部分鏈 (它可以包含 0、 1 或更多的緩沖區) 的頭。

讓我們看一個簡單的例子。假設我們想要插入文本”

    ngx_chain_t *chain_link;
    int chain_contains_last_buffer = 0;

    chain_link = in;
    for ( ; ; ) {
        if (chain_link->buf->last_buf)
            chain_contains_last_buffer = 1;
        if (chain_link->next == NULL)
            break;
        chain_link = chain_link->next;
    }

現在讓我們來保釋出來,如果我們沒有那最後的緩沖區 ︰

    if (!chain_contains_last_buffer)
        return ngx_http_next_body_filter(r, in);

現在最後一個緩沖區存儲在 chain_link 中。現在我們分配一個新的緩沖區 ︰

    ngx_buf_t    *b;
    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

把一些數據放進去 ︰

    b->pos = (u_char *) "{C}";
    b->last = b->pos + sizeof("{C}") - 1;

並將其掛鉤緩沖區進入一個新的鏈鏈接 ︰

    ngx_chain_t   *added_link;

    added_link = ngx_alloc_chain_link(r->pool);
    if (added_link == NULL)
        return NGX_ERROR;

    added_link->buf = b;
    added_link->next = NULL;

最後,鉤到最後的鏈條,我們發現之前的新鏈鏈接 ︰chain_link->next = added_link;
和重置要反映現實的”last_buf”變量 ︰

    chain_link->buf->last_buf = 0;
    added_link->buf->last_buf = 1;

和沿著改性鏈傳遞到下一個輸出篩選器 ︰

 return ngx_http_next_body_filter(r, in);

得到的函數需要更多的努力,比你會做什麼用,比如說 mod_perl ($response->body =~ s/$//),但緩沖區鏈是一個非常強大的構造,使程序員能夠以增量方式處理數據,以便客戶端獲取的東西,盡快。然而,在我看來,緩沖區鏈迫切需要一個更清潔的接口,程序員不能離開鏈處於不一致狀態。現在,操作風險由您自己承擔。

4.3.過濾器安裝

篩選器被安裝後配置步驟中。我們在同一個地方安裝頭過濾器和正文過濾器。

讓我們看看一個簡單的例子的分塊的過濾器模塊。其模塊上下文看起來像這樣 ︰


static ngx_http_module_t  ngx_http_chunked_filter_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_chunked_filter_init,          /* postconfiguration */
  ...
};

這裡是在ngx_http_chunked_filter_init中發生:

static ngx_int_t
ngx_http_chunked_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_chunked_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_chunked_body_filter;

    return NGX_OK;
}

這裡怎麼一回事?好吧,如果你還記得,過濾器設置了責任鏈。當處理程序生成的響應時,它會調用兩個函數 ︰ ngx_http_output_filter,其中要求全局函數參考ngx_http_top_body_filter;和ngx_http_send_header,它將全局的函數調用引用ngx_http_top_header_filter.

ngx_http_top_body_filter和ngx_http_top_header_filter是各自的”首長”的正文和頭的篩選器鏈。每個鏈上的”鏈接”保持到下一個鏈接的函數引用鏈 (這些引用被稱為ngx_http_next_body_filter和ngx_http_next_header_filter) 中。當篩選完成後執行的它只是調用下一個過濾器,,直到特別定義的”寫”篩選器被調用,其中包扎的 HTTP 響應。在此 filter_init 函數你看到什麼是模塊將自身添加到篩選器鏈;它在”下一步”的變量中保持對舊的”頂”篩選器的引用,並聲明的功能,成為新的”頂”過濾器。(因此,最後一個篩選器來安裝是首先被處決)

邊注 ︰ 到底是如何工作?

每個篩選器返回一個錯誤代碼,或者使用此參數作為返回語句 ︰

return ngx_http_next_body_filter();
因此,如果篩選器鏈的上游 (特別規定) 鏈末端的"OK"響應是返回,但如果是一路走來,錯誤鏈被切斷短 Nginx 服務了相應的錯誤消息。它是一個單向鏈接與快速故障實施僅與函數引用列表。

5.負載平衡器

負載平衡器只是方式來決定哪個後端服務器將接受一個特定的請求;實現存在分發請求在輪循機制方式或哈希處理一些有關請求的信息。這一節將描述負載平衡器的安裝和它的調用,使用 upstream_hash 模塊 (完整源代碼) 作為一個例子。upstream_hash 選擇一個後端通過散列變量中指定的 nginx.conf。
負載平衡的模塊有六件 ︰

有利的配置指令 (例如,hash;) 將調用注冊函數 注冊功能將定義規定server選項 (例如,weight=) 和注冊上游初始化函數 上游的初始化函數被稱為剛配置得到驗證,和它 ︰

3.1.將server名稱解析為特定的 IP 地址
3.2.對於套接字分配空間
3.3.設置回調到同行的初始化函數
同行的初始化函數,調用一次每個請求,設置數據結構的負載平衡功能將訪問和操作; 負載平衡功能決定在哪路由請求;每個客戶端請求 (更多,如果後端請求失敗),它被稱為至少一次。這是有趣的事情發生的地方。 並且最後,同行釋放函數可以更新統計後與特定的後端服務器的通信已完成 (無論成功或不成功)

它是很多,但我會把它分解成碎片。

5.1.有利的指令
指令聲明,召回,指定它們是有效和函數調用時遇上了你。一個指令,一個負載平衡器應該有的NGX_HTTP_UPS_CONF標記設置,以便 Nginx 知道此指令只是在upstream的塊內有效。它應該提供一個注冊函數指針。這裡是從 upstream_hash 模塊的指令聲明 ︰

    { ngx_string("hash"),
      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
      ngx_http_upstream_hash,
      0,
      0,
      NULL },

5.2.注冊功能
回調ngx_http_upstream_hash以上是注冊功能,因此被命名 (我) 因為它與周圍的upstream配置注冊上游初始化函數。此外,注冊函數定義了哪些選項的server指令是此特定upstream塊內部法律 (例如,weight=, fail_timeout=)。下面是 upstream_hash 模塊的注冊函數 ︰

ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
    ngx_http_upstream_srv_conf_t  *uscf;
    ngx_http_script_compile_t      sc;
    ngx_str_t                     *value;
    ngx_array_t                   *vars_lengths, *vars_values;

    value = cf->args->elts;

    /* the following is necessary to evaluate the argument to "hash" as a $variable */
    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    vars_lengths = NULL;
    vars_values = NULL;

    sc.cf = cf;
    sc.source = &value[1];
    sc.lengths = &vars_lengths;
    sc.values = &vars_values;
    sc.complete_lengths = 1;
    sc.complete_values = 1;

    if (ngx_http_script_compile(&sc) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
    /* end of $variable stuff */

    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    /* the upstream initialization function */
    uscf->peer.init_upstream = ngx_http_upstream_init_hash;

    uscf->flags = NGX_HTTP_UPSTREAM_CREATE;

    /* OK, more $variable stuff */
    uscf->values = vars_values->elts;
    uscf->lengths = vars_lengths->elts;

    /* set a default value for "hash_method" */
    if (uscf->hash_function == NULL) {
        uscf->hash_function = ngx_hash_key;
    }

    return NGX_CONF_OK;
 }

我們以後可以評價$variable ,它是相當簡單;指定一個回調,設置一些標志。提供了什麼標志?

NGX_HTTP_UPSTREAM_CREATE︰ 讓有server指示此上游的塊中。我想不到的地方你不會使用這種情況。 NGX_HTTP_UPSTREAM_WEIGHT︰ 讓server指令weight=選項 NGX_HTTP_UPSTREAM_MAX_FAILS︰ 允許max_fails=選項 NGX_HTTP_UPSTREAM_FAIL_TIMEOUT︰ 允許fail_timeout=選項 NGX_HTTP_UPSTREAM_DOWN︰ 允許down選項 NGX_HTTP_UPSTREAM_BACKUP︰ 允許backup選項

每個模塊將具有對這些配置值的訪問。是模塊來決定該拿他們怎麼辦。那就是, max_fails將不會自動執行;所有的故障邏輯是模塊的作者。以後會更多。現在,我們還沒有完成追蹤回調。接下來,我們有上游初始化函數 (以前的目標函數中的init_upstream回調)。

5.3.上游初始化函數

上游的初始化函數的用途是解析主機名稱,為套接字,分配空間並分配 (另一個) 回調。這裡是 upstream_hash 如何它 ︰

ngx_int_t
ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
    ngx_uint_t                       i, j, n;
    ngx_http_upstream_server_t      *server;
    ngx_http_upstream_hash_peers_t  *peers;

    /* set the callback */
    us->peer.init = ngx_http_upstream_init_upstream_hash_peer;

    if (!us->servers) {
        return NGX_ERROR;
    }

    server = us->servers->elts;

    /* figure out how many IP addresses are in this upstream block. */
    /* remember a domain name can resolve to multiple IP addresses. */
    for (n = 0, i = 0; i < us->servers->nelts; i++) {
        n += server[i].naddrs;
    }

    /* allocate space for sockets, etc */
    peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_hash_peers_t)
            + sizeof(ngx_peer_addr_t) * (n - 1));

    if (peers == NULL) {
        return NGX_ERROR;
    }

    peers->number = n;

    /* one port/IP address per peer */
    for (n = 0, i = 0; i < us->servers->nelts; i++) {
        for (j = 0; j < server[i].naddrs; j++, n++) {
            peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
            peers->peer[n].socklen = server[i].addrs[j].socklen;
            peers->peer[n].name = server[i].addrs[j].name;
        }
    }

    /* save a pointer to our peers for later */
    us->peer.data = peers;

    return NGX_OK;
}

這個函數是不是一個人可能會希望更多地參與。大部分的工作看起來像它應該相當抽象,但它不是,所以這就是我們的相處。一種策略簡化事情是調用另一個模塊,上游的初始化函數有它做的髒讀工作 (同行分配等),並隨後重寫us->peer.init回調之後。有關示例,請參見
http/modules/ngx_http_upstream_ip_hash_module.c.
5.4.同行初始化函數

同行的初始化函數的每個請求調用一次。它設置了一個數據結構,模塊將使用正試圖找到一個適當的後端服務器來服務這項要求;這種結構在後端重新嘗試,是持續的因此它是一個方便的地方,來跟蹤連接失敗或計算出來的散列值的數目。按照慣例,這個結構被稱為ngx_http_upstream__peer_data_t.
此外,同行初始化函數設置兩個回調 ︰

get︰ 負載平衡功能 free︰ 同行釋放功能 (當連接完成後,通常只是更新一些統計數據)

仿佛這還不夠,它還 initalizes 變量稱為tries。只要tries是積極的 nginx 將不斷重試此負載平衡器。當tries為零時,nginx 會放棄。它是get和free的功能,適當設置tries。

這裡是 upstream_hash 模塊中的對等方初始化函數 ︰

static ngx_int_t
ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *us)
{
    ngx_http_upstream_hash_peer_data_t     *uhpd;

    ngx_str_t val;

    /* evaluate the argument to "hash" */
    if (ngx_http_script_run(r, &val, us->lengths, 0, us->values) == NULL) {
        return NGX_ERROR;
    }

    /* data persistent through the request */
    uhpd = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t)
        + sizeof(uintptr_t) 
          * ((ngx_http_upstream_hash_peers_t *)us->peer.data)->number 
                  / (8 * sizeof(uintptr_t)));
    if (uhpd == NULL) {
        return NGX_ERROR;
    }

    /* save our struct for later */
    r->upstream->peer.data = uhpd;

    uhpd->peers = us->peer.data;

    /* set the callbacks and initialize "tries" to "hash_again" + 1*/
    r->upstream->peer.free = ngx_http_upstream_free_hash_peer;
    r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
    r->upstream->peer.tries = us->retries + 1;

    /* do the hash and save the result */
    uhpd->hash = us->hash_function(val.data, val.len);

    return NGX_OK;
}

沒那麼糟糕。現在,我們已准備好要選取上游服務器的功能。

5.5.負載平衡功能

負載平衡功能的原型看起來像 ︰

static ngx_int_t 
ngx_http_upstream_get__peer(ngx_peer_connection_t *pc, void *data);

data是我們有用的信息,關於此客戶端連接的結構。pc將有我們要連接到的服務器的信息。負載平衡功能的工作是填寫pc->sockaddr、 pc->socklen,和pc->name值。如果你知道一些網絡編程,那麼這些變量的名稱可能是熟悉;但他們實際上不到手頭的任務非常重要。我們不介意他們的代表;我們只是想要知道在哪裡可以找到適當的值來填充。

此函數必須找到可用服務器的列表,選擇一個行業,並將它的值分配給pc。讓我們看看 upstream_hash 如何它。

upstream_hash 以前藏服務器列表到回中調用ngx_http_upstream_init_hash (以上) 的ngx_http_upstream_hash_peer_data_t結構。此結構現已作為data:

ngx_http_upstream_hash_peer_data_t *uhpd = data;

同行的列表現在存儲在uhpd->peers->peer。讓我們計算出來的散列值除以服務器數目選取同行從這個數組 ︰

 ngx_peer_addr_t *peer = &uhpd->peers->peer[uhpd->hash % uhpd->peers->number];

結尾:

    pc->sockaddr = peer->sockaddr;
    pc->socklen  = peer->socklen;
    pc->name     = &peer->name;

    return NGX_OK;

這就完了,如果負載平衡器返回NGX_OK,這意味著,”下去,試此服務器”。如果它返回NGX_BUSY,它意味著所有後端主機不可用,和 Nginx 應該再試一次。
……,但怎麼做我們跟蹤的不可用的是什麼?我們不想再試一次嗎?

5.6.同行釋放函數

同行釋放函數操作後上游連接發生;其目的是跟蹤失敗。這裡是它的函數原型 ︰

void 
ngx_http_upstream_free__peer(ngx_peer_connection_t *pc, void *data, 
    ngx_uint_t state);

前兩個參數都一樣正如我們看到在負載平衡器的作用。第三個參數是一個state變量,指示連接是否成功。它可能包含按位或會在一起的兩個值 ︰ NGX_PEER_FAILED (連接失敗) 和NGX_PEER_NEXT (無論是連接失敗,或它成功,但應用程序返回了一個錯誤)。零表示連接成功了。

它是模塊的作者來決定如何處理這些失敗事件。如果他們在所有使用,結果應該存儲在data,自定義的每個請求的數據結構的指針。

但同行釋放功能的關鍵目的是設置pc->tries為零如果你不想 Nginx 在此請求過程中不斷嘗試此負載平衡器。最簡單的同行釋放函數將如下所示 ︰

 pc->tries = 0;

這將確保,如果有任何錯誤到達後端服務器,502 不良代理錯誤將返回給客戶端。

這裡是一個更復雜的例子,取自 upstream_hash 模塊。如果後端連接失敗時,它標記為失敗的位向量 (稱為tried,數組類型uintptr_t),然後選擇一個新的後端,直到它找到一個沒有保持。

#define ngx_bitvector_index(index) index / (8 * sizeof(uintptr_t))
#define ngx_bitvector_bit(index) (uintptr_t) 1 << index % (8 * sizeof(uintptr_t))

static void
ngx_http_upstream_free_hash_peer(ngx_peer_connection_t *pc, void *data,
    ngx_uint_t state)
{
    ngx_http_upstream_hash_peer_data_t  *uhpd = data;
    ngx_uint_t                           current;

    if (state & NGX_PEER_FAILED
            && --pc->tries)
    {
        /* the backend that failed */
        current = uhpd->hash % uhpd->peers->number;

       /* mark it in the bit-vector */
        uhpd->tried[ngx_bitvector_index(current)] |= ngx_bitvector_bit(current);

        do { /* rehash until we're out of retries or we find one that hasn't been tried */
            uhpd->hash = ngx_hash_key((u_char *)&uhpd->hash, sizeof(ngx_uint_t));
            current = uhpd->hash % uhpd->peers->number;
        } while ((uhpd->tried[ngx_bitvector_index(current)] & ngx_bitvector_bit(current)) && --pc->tries);
    }
}

這樣做是因為負載平衡功能將只是看看新的uhpd->hash值.

許多應用程序不需要重試或高可用性的邏輯,但它是可能提供的只是幾行代碼就像你在這裡看到。

6.編寫和編譯一個新的 Nginx 模塊

所以到現在為止,你應該准備看看 Nginx 模塊,試著了解什麼 (和你會知道去哪裡尋求幫助)。看看在src/http/modules/來查看可用的模塊。選擇一個模塊,類似於你來完成,並通過它看到底。東西看起來很熟悉嗎?它應該。請參閱本指南與模塊源要了解什麼。

但 Emiller 沒有寫出的球在指南閱讀 Nginx 模塊。地獄沒有。這是一個球出指南。我們沒有在讀。我們正在編寫。創建。與世界分享。

第一件事,你需要一個地方工作對您的模塊。讓您在您的硬盤的任何位置的模塊文件夾但分開 Nginx 源 (和確保您有最新的副本從nginx.net)新文件夾應該開始於包含兩個文件 ︰

"config"
"ngx_http__module.c"

“配置”文件將包括由./configure,和它的內容將取決於類型的模塊。

“配置”為濾波器模塊 ︰

ngx_addon_name=ngx_http__module
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http__module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http__module.c"

“配置”為其他模塊 ︰

ngx_addon_name=ngx_http__module
HTTP_MODULES="$HTTP_MODULES ngx_http__module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http__module.c"

現在為你的 C 文件。我推薦復制一個現有的模塊,做著類似的對你的希望,但重命名該”ngx_http__module.c”讓這成為你的模型,當你改變行為,以滿足您的需求,並參考本指南您理解並重塑的不同部分。

當你准備編譯時,只是進入的 Nginx 目錄

./configure --add-module=path/to/your/new/module/directory

然後make,make install像你通常會。如果一切順利,將正確編譯您的模塊中。不錯,是吧?不需要要與 Nginx 糞肥源,並將添加到新版本的 Nginx 模塊一個單元,只需使用同一./configure命令。順便說一句,如果您的模塊需要的任何動態鏈接的庫,您可以添加這到你的”配置”文件 ︰

CORE_LIBS="$CORE_LIBS -lfoo"

## 7.高級主題 ##
本指南涵蓋 Nginx 模塊開發的基本知識。在編寫更復雜的模塊上的提示,請務必查閱.Nginx 模塊開發 Emiller 的高級主題

附錄 a ︰ 代碼引用

Nginx 源代碼樹 (交叉引用)

Nginx 模塊目錄 (交叉引用)

示例插件 ︰ circle_gif

示例插件 ︰ upstream_hash

示例插件 ︰ upstream_fair

Copyright © Linux教程網 All Rights Reserved