Ngnix handler模块编写
Nginx handler模块编写
在编写完的模块编译成功后,将会被加载进objs目录下ngx_modules.c文件中的ngx_modules[]数组中。此数组中的模块将被顺序执行。
实现模块功能
统计每个客户端的访问次数,并创建页面展示
module.c
定义ngx_module变量
我们将module的名字命名为ngx_http_pagecount_module
,这个名字在编译后将被加载到ngx_modules.c中的ngx_modules[]数组中。
首先,在使用初始化ngx_module_t
这个结构体类型之前,我们看看这个结构体里有什么,才知道需要如何初始化。
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
char *name;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t version;
const char *signature;
void *ctx; //上下文,描述此模块为什么类型的模块
ngx_command_t *commands; //用于描述在配置文件中如何配置该模块
ngx_uint_t type; //与ctx相对应
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;
}
在这个结构体中,前七个参数为版本等相关信息,都是比较固定的写法,可以直接使用NGX_MODULE_V1
来填充。NGX_MODULE_V1
在Nginx中的定义如下所示:
#define NGX_MODULE_V1 \
NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX, \
NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE
这其中有七个字段,分别对应了ngx_module_t
中的ctx_index
、index
、name
、spare0
、spare1
、version
、signature
。
由于我们写的模块为http相关的模块,所以ctx为ngx_http_module_t
类型的值,因此在配置文件中,我们的模块应用于http{}的模块下。ngx_http_module_t
类型的定义如下:
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;
其中有八个函数指针,当在conf文件中定义http module时,这个模块将被定义,其中三组函数分别对应自身模块、server模块和location模块。在加载配置文件时,每遇到一层,调用响应的函数。
如果不使用相关的函数功能,可以将这八个函数全部写为空。
static ngx_http_module_t ngx_pagecount_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
接下来是command字段,同样,command类型的定义如下所示:
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;
};
因此我们同样可以定义我们的command:
//注意此处count_commands为一个数组,表示此模块可以定义多个命令来操作
static ngx_command_t count_commands[] = {
//处理第一条操作命令:count
{
ngx_string("count"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, //count命令在location命令中且不带任何参数(带3个参数的宏定义为NGX_CONF_TAKE3,以此类推)
ngx_http_count_module_set, //设置当遇到"count"命令时执行哪个函数
NGX_HTTP_LOC_CONF_OFFSET, //偏移位置
0, NULL
},
ngx_null_command //数组结尾标志
};
接下来的type字段,定义为http的type:NGX_HTTP_MODULE
。
然后是一组函数,七个函数可以全部设置为NULL
,然后结尾再以NGX_MODULE_V1_PADDING
填充。于是整个ngx_http_pagecount_module
的定义如下:
ngx_module_t ngx_http_pagecount_module = {
NGX_MODULE_V1, //nginx自带的版本信息
ngx_pagecount_module_ctx,
//command
count_commands,
NGX_HTTP_MODULE, //
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
实现ngx_http_count_module_set
此函数为ngx_http_pagecount_module
定义中的的函数,函数定义如下:
static char *ngx_http_count_module_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
//获取module location的配置文件
//ngx_http_core_module为ngx最核心的模块,对http功能的实现
ngx_http_core_loc_conft *corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
//将handler设置为目标函数
corecf->handler = ngx_http_count_module_handler;
return NGX_CONF_OK;
}
实现ngx_http_count_module_handler
该函数用来处理业务功能相关实现,取出IP地址,并使用数组存储,再组织成HTML页面并发送。
首先我们定义一个包含ip地址和访问次数的结构体,并将其定义为数组:
typedef struct {
int count; //访问次数
struct in_addr addr; //ip
} ngx_pv_table;
ngx_pv_table pv_table[256];
我们再定义一个用来组织http页面的函数:
static int ngx_encode_http_page(char *html){
sprint(html, "<h1>hello world</h1>");
strcat(html, "<h2>");
int i = 0;
for (i = 0; i < 256; i++){
if (pv_table[i].count != 0){
char str[INET_ADDRSTRLEN] = {0};
char buffer[128] = {0};
sprintf(buffer, "req from : %s, count: %d<br/>",
inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)), pv_table[i].count);
strcat(html, buffer);
}
}
return 0;
接下来我们就开始编写handler了:
static ngx_init_t ngx_http_count_module_handler(ngx_http_request_t *r){
u_char html[WEBPAGE_LENGTH] = {0};
int len = sizeof(html);
//取出ip地址
struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connetion->sockaddr;
//使用数组存储
//取得ip地址点分十进制的最后一位
int idx = client_addr->sin_addr.s_addr >> 24;
pv_table[idx].count++;
memcpy(&pv_table[idx].addr, &client_addr->sin_addr, sizeof(client_addr->sin_addr));
//组织html页面(或者其他操作)
ngx_encode_http_page((char*)html);
//send
//header
r->headers_out.status = 200;
ngx_str_set(&r->headers_out.content_type, "text/html");
ngx_http_send_header(r);
//body
//使用ngx_chain_t
//struct ngx_chain_s {
// ngx_buf_t *buf;
// ngx_chain_t *next;
//};
//
//注意,此处chain中的buffer为ngx_buf_s这个结构体:
//struct ngx_buf_s {
// u_char *pos;
// u_char *last;
// off_t file_pos;
// off_t file_last;
//
// u_char *start; /* start of buffer */
// u_char *end; /* end of buffer */
// ngx_buf_tag_t tag;
// ngx_file_t *file;
// 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;
// unsigned flush:1;
// unsigned sync:1;
// unsigned last_buf:1;
// unsigned last_in_chain:1;
//
// unsigned last_shadow:1;
// unsigned temp_file:1;
//
// /* STUB */ int num;
//};
//
//在这个结构体中,我们需要对pos、last、memory和last_buf进行初始化
ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_bug_t));
ngx_chain_t out;
out.buf = b;
out.next = NULL;
b->pos = html;
b->last = html + len;
b->memory = 1;
b->last_buf = 1;
//通过过滤器将out发送出去
return ngx_http_output_filter(r, &out);
}
使用模块
在使用模块时,我们可以在该模块同目录下添加config文件,并写下:
ngx_addon_name=ngx_http_pagecount_module //此处的名字要和定义的ngx_module_t结构体变量的名字相同
HTTP_MODULES="$HTTP_MODULES ngx_http_pagecount_modules" //将模块加入http模块集中
NGX_ADDON_SRS="$NGX_ADDON_SRCS $ngx_addon_dir/pagecount_module.c" //配置模块相对应的文件位置,其中ngx_addon_dir为编译时--add-module中制定的路径
在编译时需要带上 --add-module=ngx_addon_dir
参数