Nginx 模組-細節詳探

weixin_34015860發表於2012-04-01

本文主要基於

http://www.codinglabs.org/html/intro-of-nginx-module-development.html

http://www.evanmiller.org/nginx-modules-guide.html#compiling

的學習些的

 

nginx模組要負責三種角色

handler:接收請求+產生Output

filters:處理hander產生的output

load-balancer:負載均衡,選擇一個後端server傳送請求(如果把nginx當做負載均衡伺服器的話,這個角色必須實現)

 


nginx內部流程(非常重要)

圖片講解:

clip_image001

英文講解

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

中文講解:

客戶端傳送http請求 -- nginx根據配置檔案conf中的location來確定由哪個handler處理-- handler執行完request返回output給filter--第一個filter處理output -- 第二個filter處理output--- … -- 生成Response

 


Nginx模組的幾個資料結構

1 Module Configuration Struct(s) 模組配置結構

2 Module Directives 模組命令結構

3 The Module Context模組內容

3.1 create_loc_conf

3.2 merge_loc_conf

4 The Module Definition模組整合

5 Module Installation模組安裝

 

 

1 模組配置結構:

這個結構的命名規則為ngx_http_[module-name]_[main|srv|loc]_conf_t。

main,srv,loc表示這個模組的作用範圍是配置檔案中的main/server/location三種範圍(這個需要記住,後面會經常用到)

例子:

typedef struct {

ngx_str_t ed; //echo模組只有一個引數 比如 echo "hello"

} ngx_http_echo_loc_conf_t; //echo 模組

2 模組命令結構:
例子:

static ngx_command_t ngx_http_echo_commands[] = {

{ ngx_string("echo"), //命令名字

NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, //代表是local配置,帶一個引數

ngx_http_echo, //組裝模組配置結構

NGX_HTTP_LOC_CONF_OFFSET, //上面的組裝模組配置獲取完引數後存放到哪裡?使用這個和下面的offset引數來進行定位

offsetof(ngx_http_echo_loc_conf_t, ed), //同上

NULL // Finally, post is just a pointer to other crap the module might need while it's reading the configuration. It's often NULL.我也沒理解是什麼意思。通常情況下設定為NULL

},

ngx_null_command //必須使用ngx_null_command作為commands的結束標記

};

 

注1:

ngx_http_echo 是組裝模組配置結構的函式指標,有三個引數:

ngx_conf_t *cf 包含這個命令的所有引數

ngx_command_t *cmd 執行這個command命令結構的指標

void *conf 模組訂製的配置結構

這個函式比較不好理解,其功能是把引數傳到命令結構體中,並且把合適的值放入到模組配置結構中。我們稱之為"setup function"。它會在命令執行的時候被呼叫。

nginx已經提供了幾個現成的方法了:

ngx_conf_set_flag_slot

ngx_conf_set_str_slot

ngx_conf_set_num_slot

ngx_conf_set_size_slot

所以你可以這樣定義cmd:

{ ngx_string("add_after_body"),

NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,

ngx_conf_set_str_slot,

NGX_HTTP_LOC_CONF_OFFSET,

offsetof(ngx_http_addition_conf_t, after_body),

NULL },
也可以這樣:

static ngx_command_t ngx_http_echo_commands[] = {

{ ngx_string("echo"),

NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,

ngx_http_echo,

NGX_HTTP_LOC_CONF_OFFSET,

offsetof(ngx_http_echo_loc_conf_t, ed),

NULL },

ngx_null_command

};

static char *

ngx_http_echo(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_echo_handler; //這裡指定了handler,那麼就會使用新的handler進行處理

ngx_conf_set_str_slot(cf,cmd,conf); //這裡還是使用系統的函式

return NGX_CONF_OK;

}

3 模組內容

static ngx_http_module_t ngx_http_circle_gif_module_ctx = {

NULL, /* preconfiguration */

NULL, /* postconfiguration 這裡是放置filter的地方,在filter章節會說*/

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 */

};

模組的內容ngx_http_<module name>_module_ctx是為了定義各種鉤子函式,就是nginx在各個不同的時期將會執行的函式。

一般的location只需要配置create location configuration(在建立location配置的時候執行)和merge location configuration(和server config如何合併,一般包含如果配置有錯誤的話應該丟擲異常)

這兩個函式的例子:(來自https://github.com/evanmiller/nginx_circle_gif/

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中的每個引數進行配置,min_redius和max_redius是nginx_circle_gif模組的配置結構的欄位

conf->max_radius = NGX_CONF_UNSET_UINT;

return 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; //server的loc配置

ngx_http_circle_gif_loc_conf_t *conf = child; // 自己的loca配置

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;

}

注1 : ngx_conf_merge_uint_value是nginx core中自帶的函式

ngx_conf_merge_<data type>_value

ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);的意思是:

如果設定了conf->min_redius的話使用conf->min_redius

如果沒有設定conf->min_redius的話使用 prev->min_radius

如果兩個都沒有設定的話使用10

更多函式請看 core/ngx_conf_file.h

4 模組整合

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 模組型別,HTTP模組,或者HTTPS*/

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

};

5 模組安裝

模組安裝檔案的編寫依賴於這個模組是handler,filter還是load-balancer的工作角色

下面開始是Handler,filter,load-balancer的編寫和安裝

1 Handler安裝

還記得在模組命令的時候有設定handler的語句嗎?

clcf->handler = ngx_http_echo_handler;

這個語句就是handler的安裝

2 Handler編寫

Handler的執行有四部:

讀入模組配置

處理功能業務

產生HTTP header

產生HTTP body

讀入模組配置

例子:

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

...

就是使用nginx已經有的函式ngx_http_get_module_loc_conf,第一個引數是當前請求,第二個引數是前面寫好的模組

處理功能業務

這個部分是我們要模組處理的實際部分

用需求舉例:

這個模組有個命令是 getRedisInfo 192.168.0.1 //獲取redis的資訊

那麼這個功能業務就是(虛擬碼):

case cmd->opcode

{

"getRedisInfo" :

獲取redis 的資訊

}

這裡應該把所有這個模組設定的命令的業務邏輯都寫好

產生HTTP Header

例子:

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 Body

這個部分是最重要的一步

借用codingLabs的圖講解一下nginx的IO

clip_image002

handler是可以一次產生出一個輸出,也可以產生出多個輸出使用ngx_chain_t的連結串列來進行連線

struct ngx_chain_s {

ngx_buf_t *buf;

ngx_chain_t *next;

};

buf中有pos和last來代表out資料在記憶體中的位置,next是代表下一個ngx_chain_t

下面來說一下只有一個ngx_chain_t的設定

1 申明

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 模組加入連結串列

out.buf = b;

out.next = NULL; //如果有下一個連結串列可以放到這裡

4 返回

return ngx_http_output_filter(r, &out);

 

 

2 Filter的編寫和安裝

Filter作為過濾器又可以細分為兩個過濾器: Header filters 和 body filters

 

Filter的安裝

filter是在模組內容設定的時候加上的

例子:

static ngx_http_module_t ngx_http_chunked_filter_module_ctx = {

NULL, /* preconfiguration */

ngx_http_chunked_filter_init, /* postconfiguration */

...

};

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;

}

注1: ngx_http_top_hreader_filter是什麼意思呢?

當handler生成了response的時候,它呼叫了兩個方法:ngx_http_output_filter和ngx_http_send_header

ngx_http_output_filter會呼叫ngx_http_top_body_filter

ngx_http_send_header會呼叫ngx_top_header_filter

Filter的編寫

Header filters

分為三個部分:

是否操作這個handler的response

操作response

呼叫下一個filter

例子

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; //返回304

r->headers_out.content_type.len = 0; //長度設定為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);

}

Body filters

假設有個需求:在每個request後面插入"<l!-- Served by Nginx -->"

1 要找出最後chain的最後一個buf

ngx_chain_t *chain_link;

int chain_contains_last_buffer = 0;

for ( chain_link = in; chain_link != NULL; chain_link = chain_link->next ) {

if (chain_link->buf->last_buf)

chain_contains_last_buffer = 1;

}

2 建立一個新的buf

ngx_buf_t *b;

b = ngx_calloc_buf(r->pool);

if (b == NULL) {

return NGX_ERROR;

}
3 放資料在新buf上

b->pos = (u_char *) "<!-- Served by Nginx -->";

b->last = b->pos + sizeof("<!-- Served by Nginx -->") - 1;
4 把新buf放入一個新chain_t

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;
5 把新的chain連結到原來的chain_link中

chain_link->next = added_link;

6 重新設定last_buf

chain_link->buf->last_buf = 0;

added_link->buf->last_buf = 1;

7 傳給下一個filter

return ngx_http_next_body_filter(r, in);

 

最後一點是如何寫和編譯nginx模組

必須寫兩個檔案configngx_http_<your module>_module.c

其中config會被./configure包含

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檔案就是你的所有模組程式碼

 

編譯nginx:

./configure --add-module=path/to/your/new/module/directory #這裡是config放置的地方

 

----------------------

作者:yjf512(軒脈刃)

出處:http://www.cnblogs.com/yjf512/

本文版權歸yjf512和cnBlog共有,歡迎轉載,但未經作者同意必須保留此段宣告

相關文章