### 關注校招、實習信息

### Nginx 模塊概述
Nginx 模塊有三種角色:
- 處理請求并產生輸出的 Handler 模塊;
- 處理由?Handler?產生的輸出的 Filter(濾波器)模塊;
- 當出現多個后臺服務器時,Load-balancer (負載均衡器)模塊負責選擇其中一個后臺服務器發送請求;
? ? ? 通常,服務器啟動時,任何 Handler 模塊都有可能去處理配置文件中的?location?定義。若出現多個Handler 模塊被配置成需要處理某一特定的?location?時,最終只有其中一個Handler 模塊是成功的。Handler 模塊有三種返回方式:
1. 接收請求,并成功返回;
1. 接收請求,但是出錯返回;
1. 拒絕請求,使默認的 Handler 模塊處理該請求;
? ? ? 若 Handler 模塊的作用是把一個請求反向代理到后臺服務器,則會出現另一種類型的空間模塊——?Load-balancer。?Load-balancer 負責決定將請求發送給哪個后端服務器。Nginx?目前支持兩種?Load-balancer?模塊:round-robin?(輪詢,處理請求就像打撲克時發牌那樣)和"IP hash" method(眾多請求時,保證來自同一 IP 的請求被分發的同一個后端服務器)。
? ? ? 若 Handler 模塊沒有產生錯誤返回時,則會調用?Filter 模塊。每個location 配置里都可以添加多個Filter 模塊?,因此響應可以被壓縮和分塊。Filter 模塊之間的處理順序是在編譯時就已經確定的。Filter 模塊采用“CHAIN OF RESPONSIBILITY”鏈式的設計模式。當有請求到達時,請求依次經過這條鏈上的全部?Filter 模塊,一個Filter 被調用并處理,接下來調用下一個Filter,直到最后一個Filter 被調用完成,Nginx 才真正完成響應流程。
? ? ? 總結如下,典型的處理形式如下:
~~~
Client sends HTTP request → Nginx chooses the appropriate handler based on the location config →
(if applicable) load-balancer picks a backend server →
Handler does its thing and passes each output buffer to the first filter →
First filter passes the output to the second filter → second to third → third to fourth → etc.
→ Final response sent to client
~~~

~~~
~~~
### Nginx 模塊的結構
### 模塊的配置結構
? ? ? 模塊最多可以定義三個配置結構:main、server、location。絕大多數模塊僅需要一個location 配置。名稱約定如下以ngx_http_<module name>_(main|srv|loc)_conf_t為例的dav module:
~~~
typedef struct {
ngx_uint_t methods;
ngx_flag_t create_full_put_path;
ngx_uint_t access;
} ngx_http_dav_loc_conf_t;
~~~
? ? ?Nginx 模塊的數據結構如下定義:
~~~
/* Nginx 模塊的數據結構 */
#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
struct ngx_module_s {
/* 模塊類別由type成員決定,ctx_index表示當前模塊在type類模塊中的序號 */
ngx_uint_t ctx_index;
/* index 區別與ctx_index,index表示當前模塊在所有模塊中的序號 */
ngx_uint_t index;
/* spare 序列保留變量,暫時不被使用 */
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
/* 當前模塊的版本 */
ngx_uint_t version;
/* ctx指向特定類型模塊的公共接口,例如在HTTP模塊中,ctx指向ngx_http_module_t結構體 */
void *ctx;
/* 處理nginx.conf中的配置項 */
ngx_command_t *commands;
/* type表示當前模塊的類型 */
ngx_uint_t type;
/* 下面的7個函數指針是在Nginx啟動或停止時,分別調用的7中方法 */
/* 在master進程中回調init_master */
ngx_int_t (*init_master)(ngx_log_t *log);
/* 初始化所有模塊時回調init_module */
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
/* 在worker進程提供正常服務之前回調init_process初始化進程 */
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);
/* 在worker進程停止服務之前回調exit_process */
void (*exit_process)(ngx_cycle_t *cycle);
/* 在master進程退出之前回調exit_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;
};
~~~
? ? ? 在該數據結構中,其中最重要的是兩個成員 ctx和commands,這里兩個成員會在分別在下面的模塊配置指令和模塊上下文中講解;若是HTTP 模塊時,type 字段必須定義為NGX_HTTP_MODULE;
### 模塊配置指令
? ? ? 模塊指令存儲在一個 ngx_command_t 類型的靜態數組結構中,例如:
~~~
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](http://lxr.nginx.org/source/src/core/ngx_conf_file.h):
~~~
struct ngx_command_s {
/* 配置項名稱 */
ngx_str_t name;
/* 配置項類型,type將指定配置項可以出現的位置以及攜帶參數的個數 */
ngx_uint_t type;
/* 處理配置項的參數 */
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
/* 在配置文件中的偏移量,conf與offset配合使用 */
ngx_uint_t conf;
ngx_uint_t offset;
/* 配置項讀取后的處理方法,必須指向ngx_conf_post_t 結構 */
void *post;
};
~~~
name?:配置指令的名稱;
type ? ?:該配置的類型,指定配置項的出現位置以及可攜帶參數的個數,下面規定只是其中一部分,更多信息可查看文件[core/ngx_conf_file.h](http://lxr.nginx.org/source/src/core/ngx_conf_file.h):
~~~
NGX_HTTP_MAIN_CONF: directive is valid in the main config
NGX_HTTP_SRV_CONF: directive is valid in the server (host) config
NGX_HTTP_LOC_CONF: directive is valid in a location config
NGX_HTTP_UPS_CONF: directive is valid in an upstream config
NGX_CONF_NOARGS: directive can take 0 arguments
NGX_CONF_TAKE1: directive can take exactly 1 argument
NGX_CONF_TAKE2: directive can take exactly 2 arguments
…
NGX_CONF_TAKE7: directive can take exactly 7 arguments
NGX_CONF_FLAG: directive takes a boolean ("on" or "off")
NGX_CONF_1MORE: directive must be passed at least one argument
NGX_CONF_2MORE: directive must be passed at least two arguments
~~~
set ? ??:這是一個函數指針,當Nginx 在解析配置時,若遇到該配置指令,將會把讀取到的值傳遞給這個函數進行分解處理。因為具體每個配置指令的值如何處理,只有定義這個配置指令的人是最清楚的。來看一下這個函數指針要求的函數原型。
~~~
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
~~~
? ? ? ? 該函數處理成功時,返回 NGX_OK,否則返回 NGX_CONF_ERROR 或者是一個自定義的錯誤信息的字符串。該函數傳入三個類型的參數:
1. cf ? ?:指向ngx_conf_t ?結構的指針,該結構包括從配置指令傳遞的參數;
1. cmd:指向當前ngx_command_t 結構;
1. conf:指向模塊配置結構;
? ? ? 為了方便實現對配置指令參數的讀取,Nginx 已經默認提供了對一些標準類型的參數進行讀取的函數,可以直接賦值給set 字段使用。下面是一部分已經實現的set 類型函數,更多可參考文件[core/ngx_conf_file.h](http://lxr.nginx.org/source/src/core/ngx_conf_file.h):
- ngx_conf_set_flag_slot?: 把 "on" 或 "off" 解析為 1 或 0;
- ngx_conf_set_str_slot ? : 解析字符串并保存?ngx_str_t類型;
- ngx_conf_set_num_slot: 解析一個數字并將其保存為int 類型;
- ngx_conf_set_size_slot: 解析數據大小 ("8k", "1m", etc.) 并將其保存為size_t;
conf ??:用于指示配置項所處內存的相對偏移量,僅在type 中沒有設置NGX_DIRECT_CONF 和NGX_MAIN_CONF 時才生效。對于HTTP 模塊,conf 必須設置,它的取值如下:
- NGX_HTTP_MAIN_CONF_OFFSET:使用create_main_conf 方法產生的結構體來存儲解析出的配置項參數;
- NGX_HTTP_SRV_CONF_OFFSET:使用 create_srv_conf 方法產生的結構體來存儲解析出的配置項參數;
- NGX_HTTP_LOC_CONF_OFFSET:使用 create_loc_conf 方法產生的結構體來存儲解析出的配置項參數;
offset?:表示當前配置項在整個存儲配置項的結構體中的偏移位置。
### 模塊上下文
? ? ? 這是一個靜態的 ngx_http_module_t 結構,它的名稱是ngx_http_<module name>_module_ctx。以下是該結構的定義,具體可查閱文件?[http/ngx_http_config.h](http://lxr.nginx.org/source/src/http/ngx_http_config.h):
- preconfiguration
- postconfiguration
- creating the main conf (i.e., do a malloc and set defaults)
- initializing the main conf (i.e., override the defaults with what's in nginx.conf)
- creating the server conf
- merging it with the main conf
- creating the location conf
- merging it with the server conf
~~~
typedef struct{/* 可以把不需要調用的函數指針設置為 NULL */
/* 解析配置文件之前被調用 */
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
/* 完成配置文件的解析后被調用 */
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
/* 創建存儲main級別的全局配置項的結構體(直屬于http塊) */
void *(*create_main_conf)(ngx_conf_t *cf);
/* 初始化main級別的配置項 */
char *(*init_main_conf)(ngx_conf_t *cf);
/* 創建存儲srv級別的配置項的結構體(直屬于server塊) */
void *(*create_srv_conf)(ngx_conf_t *cf);
/* 合并main級別與srv級別下的同名配置項 */
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
/* 創建存儲loc級別的配置項的結構體(直屬于location塊) */
void *(*create_loc_conf)(ngx_conf_t *cf);
/* 合并srv級別與loc級別下的同名配置項 */
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
}ngx_http_module_t;
~~~
? ? ?在以上的結構內容中,大多數模塊只使用最后兩項:ngx_http_<module name>_create_loc_conf和ngx_http_<module name >_merge_loc_conf;例如:
~~~
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 */
};
~~~
? ? ?下面針對最后兩項進行說明,以下是以 circle_gif 模塊為例子,該[模塊源碼](http://www.evanmiller.org/nginx/ngx_http_circle_gif_module.c.txt);
#### create_loc_conf 函數
? ? ?該函數是傳入一個?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;
}
~~~
#### merge_loc_conf 函數
? ? ? ? ?Nginx 為不同的數據類型提供了merge 函數,可查閱?[core/ngx_conf_file.h](http://lxr.nginx.org/source/src/core/ngx_conf_file.h);merge_loc_conf?函數定義如下:
~~~
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;
}
~~~
### 模塊的定義
? ? ? 對任何開發模塊,都需要定義一個 ngx_module_t 類型的變量來說明這個模塊本身的信息,它告訴了 Nginx 這個模塊的一些信息。這個變量是? ngx_http_<module name>_module;例如:更多例子可查找文件?[core/ngx_conf_file.h](http://lxr.nginx.org/source/src/core/ngx_conf_file.h);
~~~
ngx_module_t ngx_http_<module name>_module = {
NGX_MODULE_V1,
&ngx_http_<module name>_module_ctx, /* module context */
ngx_http_<module name>_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
};
~~~
### Handler 模塊
? ? ? Handler?模塊必須提供一個真正的處理函數,這個函數負責處理來自客戶端的請求。該函數既可以選擇自己直接生成內容,也可以選擇拒絕處理,并由后續的?Handler 去進行處理,或者是選擇丟給后續的?Filter 模塊進行處理。以下是該函數的原型:
~~~
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
~~~
其中r 是?request 結構http 請求,包含客戶端請求所有的信息,例如:request method, URI, and headers。 該函數處理成功返回NGX_OK,處理發生錯誤返回NGX_ERROR,拒絕處理(留給后續的Handler 進行處理)返回NGX_DECLINE。 返回NGX_OK 也就代表給客戶端的響應已經生成,否則返回NGX_ERROR 就發生錯誤了。
? ? ? Handler 模塊處理過程中做了四件事情:**獲取?location 配置**、**生成合適的響應**、**發送響應的?header 頭部**、**發送響應的?body 包體**。
### 獲取?location?配置
? ? ? 獲取?location?配置 指向調用?ngx_http_get_module_loc_conf?函數即可,該函數傳入的參數是?request 結構和 ?自定義的 module 模塊。例如:circle gif模塊;
~~~
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);
...
}
~~~
### 生成合適的響應
? ? ? ?這里主要是 request 結構,其定義如下:更多可參考文件??[http/ngx_http_request.h](http://lxr.nginx.org/source/src/http/ngx_http_request.h#L316);
~~~
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 ? ? ? ? ? ?? 是?request 請求的路徑,e.g. "/query.cgi".
- args ? ? ? ? ??是請求串參數中問號后面的參數(e.g. "name=john").
- headers_in 包含有用的stuff,例如:cookies 和browser 信息。
### 發送響應的?header?頭部
? ? ?發送響應頭部有函數ngx_http_send_header(r)?實現。響應的header?頭部在?headers_out 結構中,定義如下:更多可參考文件?[http/ngx_http_request.h](http://lxr.nginx.org/source/src/http/ngx_http_request.h#L316);
~~~
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;
~~~
? ? ?例如,一個模塊設置為?Content-Type to "image/gif", Content-Length to 100, and return a 200 OK response,則其實現為:
~~~
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);
~~~
? ? ?假如content_encoding 是 (ngx_table_elt_t*)類型時,則模塊需要為這些類型分配內存,可以調用ngx_list_push 函數,實現如下:
~~~
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);
~~~
### 發送響應的?body?包體
? ? ?到此,該模塊已經產生響應,并把它存儲在內存中。發送包體的步驟是:首先分配響應特殊的緩沖區,然后分配緩沖區鏈接到chain link,然后在?chain link 調用發送函數。
? ? ?1、chain links 是 Nginx ?使 Handler 模塊在緩沖區中產生響應。在 chain 中每個 chain link 有一個指向下一個 link 的指針。首先,模塊聲明緩沖區?buffer 和 chain link:
~~~
ngx_buf_t *b;
ngx_chain_t out;
~~~
2、然后分配緩沖區?buffer,使響應數據指向它:
~~~
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 */
~~~
3、接著,把模塊掛載到 chain link 上:
~~~
out.buf = b;
out.next = NULL;
~~~
4、最后,發送包體:
~~~
return ngx_http_output_filter(r, &out);
~~~
### Handler 模塊掛載
? ? ? Handler 模塊真正的處理函數通過兩種方式掛載到處理過程中:按處理階段掛載;按需掛載。
#### 按處理階段掛載
? ? ? 為了更精細地控制對于客戶端請求的處理過程,Nginx 把這個處理過程劃分成了11個階段。依次列舉如下:
~~~
NGX_HTTP_POST_READ_PHASE:
/* 讀取請求內容階段 */
NGX_HTTP_SERVER_REWRITE_PHASE:
/* Server請求地址重寫階段 */
NGX_HTTP_FIND_CONFIG_PHASE:
/* 配置查找階段: */
NGX_HTTP_REWRITE_PHASE:
/* Location請求地址重寫階段 */
NGX_HTTP_POST_REWRITE_PHASE:
/* 請求地址重寫提交階段 */
NGX_HTTP_PREACCESS_PHASE:
/* 訪問權限檢查準備階段 */
NGX_HTTP_ACCESS_PHASE:
/* 訪問權限檢查階段 */
NGX_HTTP_POST_ACCESS_PHASE:
/* 訪問權限檢查提交階段 */
NGX_HTTP_TRY_FILES_PHASE:
/* 配置項try_files處理階段 */
NGX_HTTP_CONTENT_PHASE:
/* 內容產生階段 */
NGX_HTTP_LOG_PHASE:
/* 日志模塊處理階段 */
~~~
? ? ? 一般情況下,我們自定義的模塊,大多數是掛載在NGX_HTTP_CONTENT_PHASE階段的。掛載的動作一般是在模塊上下文調用的postconfiguration 函數中。注意:有幾個階段是特例,它不調用掛載任何的Handler,也就是你就不用掛載到這幾個階段了:
~~~
NGX_HTTP_FIND_CONFIG_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_TRY_FILES_PHASE
~~~
#### 按需掛載
? ? ? 以這種方式掛載的Handler 也被稱為content handler。當一個請求進來以后,Nginx 從NGX_HTTP_POST_READ_PHASE 階段開始依次執行每個階段中所有 Handler。執行到 ?NGX_HTTP_CONTENT_PHASE 階段時,如果這個location 有一個對應的content handler 模塊,那么就去執行這個content handler 模塊真正的處理函數。否則繼續依次執行NGX_HTTP_CONTENT_PHASE 階段中所有content phase handlers,直到某個函數處理返回NGX_OK 或者NGX_ERROR。但是使用這個方法掛載上去的handler 有一個特點是必須在NGX_HTTP_CONTENT_PHASE 階段才能被執行。如果你想自己的handler 更早的階段被執行,那就不要使用這種掛載方式。
? ? ? 以下是例子:
~~~
circle gif ngx_command_t looks like this:
{ ngx_string("circle_gif"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_circle_gif,
0,
0,
NULL }
~~~
掛載函數:
~~~
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;
}
~~~
### Handler 模塊編寫
Handler 模塊編寫步驟如下:
1. 編寫模塊基本結構:包括模塊的定義,模塊上下文結構,模塊的配置結構等;
1. 實現 handler 的掛載函數;根據模塊的需求選擇正確的掛載方式;
1. 編寫 handler 處理函數;模塊的功能主要通過這個函數來完成;
### Filter 模塊
? ? ? Filter?處理由Handler 模塊產生的響應,即僅處理由服務器發往客戶端的HTTP 響應,并不處理由客戶端發往服務器的 HTTP 請求。Filter 模塊包括過濾頭部(Header Filter)和過濾包體(Body Filter ),Filter 模塊過濾頭部處理HTTP 的頭部(HTTP headers),Filter 包體處理響應內容(response content)(即HTTP 包體),這兩個階段可以對HTTP 響應頭部和內容進行修改。
? ? ? Filter?模塊?HTTP 響應的方法如下:定義在文件?[src/http/ngx_http_core_module.h](http://lxr.nginx.org/source/src/http/ngx_http_core_module.h)
~~~
typedef ngx_int_t (*ngx_http_output_header_filter_pt) (ngx_http_request_t *r);
typedef ngx_int_t (*ngx_http_output_body_filter_pt) (ngx_http_request_t *r, ngx_chain_t *chain);
~~~
其中,參數 r?是當前的請求,chain?是待發送的 HTTP ?響應包體;
? ? ? 所有 HTTP 過濾模塊都需要實現上面的兩個方法,在 HTTP 過濾模塊組成的鏈表中,鏈表元素就是處理方法。HTTP 框架定義了鏈表入口:
~~~
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter;
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter;
~~~
? ? ? 過濾模塊鏈表中通過 next 遍歷,其定義如下:?
~~~
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
~~~
? ? ? 當執行發送 HTTP 頭部或 HTTP 響應包體時,HTTP 框架是從 ?ngx_http_top_header_filter 和?ngx_http_top_body_filter 開始遍歷 HTTP 頭部過濾模塊和 HTTP 包體過來模塊。其源碼實現在文件:[src/http/ngx_http_core_module.c](http://lxr.nginx.org/source/src/http/ngx_http_core_module.c)
~~~
/* 發送 HTTP 響應頭部 */
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
return ngx_http_top_header_filter(r);
}
/* 發送HTTP 響應包體 */
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c;
c = r->connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args);
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
}
return rc;
}
~~~
### Filter 模塊相關結構
? ? ? Filter 模塊是采用鏈表形式的,其基本結構是ngx_chain_t 和?ngx_buf_t;這兩種結構定義如下:
~~~
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
struct ngx_buf_s {
u_char *pos; /* 當前buffer真實內容的起始位置 */
u_char *last; /* 當前buffer真實內容的結束位置 */
off_t file_pos; /* 在文件中真實內容的起始位置 */
off_t file_last; /* 在文件中真實內容的結束位置 */
u_char *start; /* buffer內存的開始分配的位置 */
u_char *end; /* buffer內存的結束分配的位置 */
ngx_buf_tag_t tag; /* buffer屬于哪個模塊的標志 */
ngx_file_t *file; /* buffer所引用的文件 */
/* 用來引用替換過后的buffer,以便當所有buffer輸出以后,
* 這個影子buffer可以被釋放。
*/
ngx_buf_t *shadow;
/* the buf's content could be changed */
unsigned temporary:1;
/*
* the buf's content is in a memory cache or in a read only memory
* and must not be changed
*/
unsigned memory:1;
/* the buf's content is mmap()ed and must not be changed */
unsigned mmap:1;
unsigned recycled:1; /* 內存可以被輸出并回收 */
unsigned in_file:1; /* buffer的內容在文件中 */
/* 馬上全部輸出buffer的內容, gzip模塊里面用得比較多 */
unsigned flush:1;
/* 基本上是一段輸出鏈的最后一個buffer帶的標志,標示可以輸出,
* 有些零長度的buffer也可以置該標志
*/
unsigned sync:1;
/* 所有請求里面最后一塊buffer,包含子請求 */
unsigned last_buf:1;
/* 當前請求輸出鏈的最后一塊buffer */
unsigned last_in_chain:1;
/* shadow鏈里面的最后buffer,可以釋放buffer了 */
unsigned last_shadow:1;
/* 是否是暫存文件 */
unsigned temp_file:1;
/* 統計用,表示使用次數 */
/* STUB */ int num;
};
~~~
### Filter 過濾頭部
header filter 包含三個基本步驟:
1. 決定是否處理響應;
1. 對響應進行處理;
1. 調用下一個?filter;
? ? ? 例如下面的"not modified" header filter:其中?headers_out 結構可參考文件?[http/ngx_http_request.h](http://lxr.nginx.org/source/src/http/ngx_http_request.h#L220);
~~~
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);
}
~~~
### Filter 過濾包體
? ? ? Filter 包體只能在chain link緩沖區buffer?中操作。模塊必須決定是否修改輸入緩沖區,或分配新的緩沖區替換當前緩沖區,或是在當前緩沖區之后還是之前插入新的緩沖區。很多模塊接收多個緩沖區,導致這些模塊在不完整的chain 緩沖區中操作。Filter 包體操作如下:
~~~
static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);
~~~
以下是一個例子:
~~~
/*
* Let's take a simple example.
* Suppose we want to insert the text "<l!-- Served by Nginx -->" to the end of every request.
* First, we need to figure out if the response's final buffer is included in the buffer chain we were given.
* Like I said, there's not a fancy API, so we'll be rolling our own for loop:
*/
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;
}
/*
* Now let's bail out if we don't have that last buffer:
*/
if (!chain_contains_last_buffer)
return ngx_http_next_body_filter(r, in);
/*
* Super, now the last buffer is stored in chain_link.
* Now we allocate a new buffer:
*/
ngx_buf_t *b;
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_ERROR;
}
/*
* And put some data in it:
*/
b->pos = (u_char *) "<!-- Served by Nginx -->";
b->last = b->pos + sizeof("<!-- Served by Nginx -->") - 1;
/*
* And hook the buffer into a new chain link:
*/
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;
/*
* Finally, hook the new chain link to the final chain link we found before:
*/
chain_link->next = added_link;
/*
* And reset the "last_buf" variables to reflect reality:
*/
chain_link->buf->last_buf = 0;
added_link->buf->last_buf = 1;
/*
* And pass along the modified chain to the next output filter:
*/
return ngx_http_next_body_filter(r, in);
/*
* The resulting function takes much more effort than what you'd do with, say, mod_perl ($response->body =~ s/$/<!-- Served by mod_perl -->/),
* but the buffer chain is a very powerful construct, allowing programmers to process data incrementally so that the client gets something as soon as possible.
* However, in my opinion, the buffer chain desperately needs a cleaner interface so that programmers can't leave the chain in an inconsistent state.
* For now, manipulate it at your own risk.
*/
~~~
### Filter 模塊掛載
? ? ? Filters 模塊和Handler 模塊一樣,也是掛載到post-configuration ,如下面代碼所示:
~~~
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;
}
~~~
由于 Filter 模塊是 “CHAIN OF RESPONSIBILITY” 鏈表模式的。Handler 模塊生成響應后,Filter 模塊調用兩個函數:ngx_http_output_filter 和?ngx_http_send_header,其中ngx_http_output_filter 函數是調用全局函數?ngx_http_top_body_filter;ngx_http_send_header 函數是調用全局函數?ngx_http_top_header_filter。
~~~
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
...
return ngx_http_top_header_filter(r);
}
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c;
c = r->connection;
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
}
return rc;
}
~~~
Filter 模塊的執行方式如下圖所示:

### Filter 模塊編寫
Filter 模塊編寫步驟如下
- 編寫基本結構:模塊定義,上下文結構,基本結構;
- 初始化過濾模塊:把本模塊中處理的 HTTP 頭部的?ngx_http_output_header_filter_pt 方法與處理HTTP 包體的ngx_http_output_body_filter_pt 方法插入到過濾模塊鏈表首部;
- 實現處理 HTTP 響應的方法:處理 HTTP 頭部,即 ngx_http_output_header_filter_pt 方法的實現,處理HTTP 包體的方法,即ngx_http_output_body_filter_pt 方法的實現;
- 編譯安裝;
### 開發 Nginx 新模塊
? ? ? 把自己開發的模塊編譯到 Nginx 中需要編寫兩個文件:
1. "config",該文件會被?./configure 包含;
1. "ngx_http_<your module>_module.c",該文件是定義模塊的功能;
? ? ? config 文件的編寫如下:
~~~
/*
* "config" for filter modules:
*/
ngx_addon_name=ngx_http_<your module>_module /* 模塊的名稱 */
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_<your module>_module" /* 保存所有 HTTP 模塊*/
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c" /* 指定新模塊的源碼路徑 */
/*
* "config" for other modules:
*/
ngx_addon_name=ngx_http_<your module>_module
HTTP_MODULES="$HTTP_MODULES ngx_http_<your module>_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c"
~~~
? ? ? 關于?"ngx_http_<your module>_module.c" 文件的編寫,可參考上面的Handler 模塊,同時可參考Nginx 現有的模塊:[src/http/modules/](http://lxr.nginx.org/source/src/http/modules/);例如下面的“Hello World ”代碼:
~~~
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
typedef struct
{
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
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
};
/*
static u_char ngx_hello_default_string[] = "Default String: Hello, world!";
*/
static int ngx_hello_visited_times = 0;
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 */
};
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
};
static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t* my_conf;
u_char ngx_hello_string[1024] = {0};
ngx_uint_t content_length = 0;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (my_conf->hello_string.len == 0 )
{
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string is empty!");
return NGX_DECLINED;
}
if (my_conf->hello_counter == NGX_CONF_UNSET
|| my_conf->hello_counter == 0)
{
ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);
}
else
{
ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data,
++ngx_hello_visited_times);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
content_length = ngx_strlen(ngx_hello_string);
/* we response to 'GET' and 'HEAD' requests only */
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
/* discard request body, since we don't need it here */
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
/* set the 'Content-type' header */
/*
*r->headers_out.content_type.len = sizeof("text/html") - 1;
*r->headers_out.content_type.data = (u_char *)"text/html";
*/
ngx_str_set(&r->headers_out.content_type, "text/html");
/* send the header only, if the request type is http 'HEAD' */
if (r->method == NGX_HTTP_HEAD) {
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
return ngx_http_send_header(r);
}
/* allocate a buffer for your response body */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* attach this buffer to the buffer chain */
out.buf = b;
out.next = NULL;
/* adjust the pointers of the buffer */
b->pos = ngx_hello_string;
b->last = ngx_hello_string + content_length;
b->memory = 1; /* this buffer is in memory */
b->last_buf = 1; /* this is the last buffer in the buffer chain */
/* set the status line */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
/* send the headers of your response */
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
/* send the buffer chain of your response */
return ngx_http_output_filter(r, &out);
}
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_hello_loc_conf_t* local_conf = NULL;
local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (local_conf == NULL)
{
return NULL;
}
ngx_str_null(&local_conf->hello_string);
local_conf->hello_counter = NGX_CONF_UNSET;
return local_conf;
}
/*
static char *ngx_http_hello_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_hello_loc_conf_t* prev = parent;
ngx_http_hello_loc_conf_t* conf = child;
ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, ngx_hello_default_string);
ngx_conf_merge_value(conf->hello_counter, prev->hello_counter, 0);
return NGX_CONF_OK;
}*/
static char *
ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
local_conf = conf;
char* rv = ngx_conf_set_str_slot(cf, cmd, conf);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);
return rv;
}
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
local_conf = conf;
char* rv = NULL;
rv = ngx_conf_set_flag_slot(cf, cmd, conf);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_counter:%d", local_conf->hello_counter);
return rv;
}
static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_hello_handler;
return NGX_OK;
}
~~~
? ? ? 寫好上面的兩個文件后,在編譯 Nginx 時,步驟如下:
~~~
./configure --add-module=path/to/your/new/module/directory
make
make install
~~~
參考資料:
《[Emiller's Guide To Nginx Module Development](http://www.evanmiller.org/nginx-modules-guide.html)》
《[nginx模塊開發篇](http://tengine.taobao.org/book/module_development.html)》
《[https://github.com/simpl/ngx_devel_kit](https://github.com/simpl/ngx_devel_kit)》
- 前言
- Nginx 配置文件
- Nginx 內存池管理
- Nginx 基本數據結構
- Nginx 數組結構 ngx_array_t
- Nginx 鏈表結構 ngx_list_t
- Nginx 隊列雙向鏈表結構 ngx_queue_t
- Nginx 哈希表結構 ngx_hash_t
- Nginx 紅黑樹結構 ngx_rbtree_t
- Nginx 模塊開發
- Nginx 啟動初始化過程
- Nginx 配置解析
- Nginx 中的 upstream 與 subrequest 機制
- Nginx 源碼結構分析
- Nginx 事件模塊
- Nginx 的 epoll 事件驅動模塊
- Nginx 定時器事件
- Nginx 事件驅動模塊連接處理
- Nginx 中 HTTP 模塊初始化
- Nginx 中處理 HTTP 請求
- Nginx 中 upstream 機制的實現
- Nginx 中 upstream 機制的負載均衡