编写一个Nginx的模块最少需要两个文件。config配置文件和C语言代码文件。config配置文件的文件名为”config”,C语言代码文件一般叫做ngx_http_模块的名字_module.c,我们实现的功能是在在Nginx的配置文件中的http块中的server块中添加一个location /echo的配置块,在Nginx执行时用到配置是我们将指令后面的内容发送给客户端。
C语言文件的编写
下面开始介绍一个http模块的书写过程,首先我们新建一个文件”mytest”,在mytest新建一个ngx_http_echo_module.c的文件。
所有的功能都是从http_module_t、ngx_command_t、ngx_http_module_t三个数据结构中展示出来的。
Module模块信息
所有Nginx在执行的时候用到的module模块中所有的操作都是从http_module_t去找对应的执行的方法的。结构体一般命名为ngx_http_模块名_module。下面是结构体
structngx_module_s { ngx_uint_t ctx_index; ngx_uint_t index; ngx_uint_t spare0; ngx_uint_t spare1; ngx_uint_t spare2; ngx_uint_t spare3; ngx_uint_t version; void *ctx; ngx_command_t *commands; ngx_uint_t type; ngx_int_t (*init_master)(ngx_log_t *log); //初始化master 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); //退出master 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; };
以上结构体中带有spare的成员都是保留字段。
ctx_index: 当前模块在同类模块中的位置。
index:当前模块在ngx_modules数组中的序号。在Nginx执行modules中事件顺序中,第几个被执行的。
version : Nginx模块版本,目前只有一种,暂定为1
对于整个结构体,前七项我们一般用用宏定义NGX_MODULE_V1
#defineNGX_MODULE_V1 0, 0, 0, 0, 0, 0,1
#defineNGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0,0, 0
commands:配置文件中当前配置的每一个配置保存方法和配置对应的操作函数。
type:当前模块的类型,取值如下NGX_HTTP_MODULE,NGX_CORE_MODULE,
NGX_CONF_MODULE, NGX_EVENT_MODULE,NGX_MAIL_MODULE
我所写的代码是:
ngx_module_tngx_http_echo_module = {
NGX_MODULE_V1, &ngx_http_echo_module_ctx, ngx_http_echo_commands, NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING };
ngx_http_module_t主要用于创建合并配置信息,
创建:就是当Nginx解析配置文件时候,出现了我们指定的配置时候,我们需要创建一个保存配置信息的结构体,这样我们引出另外一个需要我们自定义的结构体。
合并:就是当Nginx解析配置文件时候,中同一个配置出现多次的进行处理。
自定义结构体:结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分别用于表示同一模块在配置文件三层block中的配置信息。这里我们的echo模块只需要运行在loc层级下,需要存储一个字符串参数(如果一个有多个配置内容需要顶一个多个成员,变量的类型可以自己定义。)。因此结构体定义如下
typedef struct { ngx_str_t ed; }ngx_http_echo_loc_conf_t;
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;
postconfiguration:完成配置文件解析后调用
create_main_conf:当需要创建数据结构用户存储main级别的全局配置项时候调用
init_main_conf:初始化main级别配置项
create_srv_conf:当需要创建数据结构用户存储server级别的全局配置项时候调用
merge_srv_conf: 出现多个server的处理方法。
create_loc_conf:当需要创建数据结构用户存储location级别的全局配置项时候调用
merge_loc_conf: 出现多个location的处理方法。
下面是我具体的代码:
typedef struct { ngx_str_t ed; }ngx_http_echo_loc_conf_t;
static void * ngx_http_echo_create_loc_conf(ngx_conf_t*cf) { ngx_http_echo_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_echo_loc_conf_t)); if(NULL == conf) { return NGX_CONF_ERROR; } conf->ed.len = 0; conf->ed.data = NULL; return conf; }
static char * ngx_http_echo_merge_loc_conf(ngx_conf_t*cf, void *parent, void *child) { ngx_http_echo_loc_conf_t *prev = parent; ngx_http_echo_loc_conf_t *conf = child; ngx_conf_merge_str_value(conf->ed,prev->ed, ""); return NGX_CONF_OK; }
ngx_http_module_t
ngx_http_module_t用来定义Nginx可以接受的模块(配置文件中可以出现的配置命名)和可以出现的位置。
structngx_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:指令的名称,name是一个字符串,表示可以在Nginx配置文件中出现配置项的名字。
type:配置项类型(有几个参数或者可以在什么地方出现等),可以取的值。
type类型 |
值 |
解释 |
参数限制 |
NGX_CONF_NOARGS |
配置项不带任何参数 |
NGX_CONF_TAKE1 |
配置项必须带一个参数 |
|
NGX_CONF_TAKE2 |
配置项必须带两个参数 |
|
NGX_CONF_TAKE3 |
配置项必须带三个参数 |
|
NGX_CONF_TAKE4 |
配置项必须带四个参数 |
|
NGX_CONF_TAKE5 |
配置项必须带五个参数 |
|
NGX_CONF_TAKE6 |
配置项必须带六个参数 |
|
NGX_CONF_TAKE7 |
配置项必须带七个参数 |
|
NGX_CONF_TAKE12 |
配置项可以带1或2个参数 |
|
NGX_CONF_TAKE13 |
配置项可以带1或4个参数 |
|
NGX_CONF_TAKE23 |
配置项可以带2或3个参数 |
|
NGX_CONF_TAKE123 |
配置项可以带1~3个参数 |
|
NGX_CONF_TAKE1234 |
配置项可以带1~4个参数 |
|
NGX_CONF_ARGS_NUMBER |
保留 |
|
NGX_CONF_BLOCK |
配置项定义额新的{}块 |
|
NGX_CONF_FLAG |
配置项只能带一个参数,并且值为on或者off |
|
NGX_CONF_ANY |
不验证配置项携带的参数 |
|
NGX_CONF_1MORE |
配置项的参数必须大于1个 |
|
NGX_CONF_2MORE |
配置项的参数鼻血大于2个 |
|
NGX_CONF_MULTI |
不大理解, |
|
配置项在Nginx配置文件可以出现的位置 |
NGX_MAIN_CONF |
配置项可以出现在全局变量中 |
NGX_EVENT_CONF |
配置项可以出现在events{}中 |
|
NGX_MAIL_MAIN_CONF |
配置项可以出现在mail{}块内或者imap{}块内 |
|
NGX_MAIL_SRV_CONF |
配置项可以出现在server{}块内,然后该server块必须属于mail{}块或者imap{}块内 |
|
NGX_HTTP_MAIN_CONF |
配置项可以出现在http{}块内 |
|
NGX_HTTP_SRV_CONF |
配置项可以出现在 server{}块内,然而该块必须属于http{}块 |
|
NGX_HTTP_LOC_CONF |
配置项可以出现在location{}块内,然而该块必须属于http{}块 |
|
NGX_HTTP_UPS_CONF |
配置项可以出现在upstream{}块内,然而该upstream{}块必须属于http{}块 |
|
NGX_HTTP_SIF_CONF NGX_HTTP_LIF_CONF NGX_HTTP_LMT_CONF |
不解释 |
set: 是一个函数指针,用于指定一个参数转化函数,这个函数一般是将配置文件中相关指令的参数转化成需要的格式并存入配置结构体。我们可以选用Nginx提供的一些函数,也可以自己写一个函数,使用Nginx提供的函数需要与offset成员一起配置
set函数测参数
ngx_conf_t*cf:配置
ngx_command_t *cmd:ngx_command_t数组
void *conf: 当出现配置项需要调用的功能函数,即使此配置项对应的功能。
Nginx提供的解析配置文件的指令
函数原型 |
解释 |
ngx_conf_set_flag_slot |
配置项只有一个参数,参数的值是off和on |
ngx_conf_set_str_slot |
配置项只有一个参数,参数的值是一个字符串 |
ngx_conf_set_str_array_slot |
配置项可以出现多次,每次出现带一个参数。 |
ngx_conf_set_keyval_slot |
配置项后面是一个键值对,两个参数 |
ngx_conf_set_num_slot |
配置项只能有一参数,为数字 |
ngx_conf_set_size_slot |
配置项后带一个参数,表示空间大小。一个数字跟上K,M,G或k、m、g |
ngx_conf_set_msec_slot ngx_conf_set_sec_slot |
配置项后带一个参数,表示时间。可以单m(秒)、h(小时)、d(天)、w(星期)、M(月)、y,不同的是msec是毫秒,sec是秒 |
ngx_conf_set_sec_slot ngx_conf_set_enum_slot ngx_conf_set_bitmask_slot ngx_conf_set_access_slot ngx_conf_set_path_slot |
|
ngx_conf_set_off_slot |
配置项必须带一个参数,表示空间上的偏移量。 |
下面是我自己写的set函数
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; //指令对应的处理函数 ngx_conf_set_str_slot(cf, cmd, conf); return NGX_CONF_OK; }
conf 用于指定Nginx相应配置文件内存其实地址,就是我们要使用的配置块在配置文件中的那个块内,在type中没有出现NGX_DIRECT_CONF和NGX_MAIN_CONF时才有效,一般可以通过内置常量指定,
NGX_HTTP_MAIN_CONF_OFFSET |
需要访问的块是在main中创建, |
NGX_HTTP_SRV_CONF_OFFSET |
需要访问的块是在server中创建 |
NGX_HTTP_LOC_CONF_OFFSET |
需要访问的块是location中创建 |
其实使用上面的那些值,只要看一下ngx_http_module_t就可以了,看看使用了那些creat函数创建了配置。
offset 表示需要将使用set函数转换的值要保存在自定义结构中那个成员的位置
post书上说有很多用处,是一个回调函数。
下面是我的ngx_command_t结构体,由于在使用的时候,一个配置块中可以有多个指令,所以在使用ngx_command_t是以数组形式出现,必须以ngx_null_command表示数组定义的结束。
static ngx_command_tngx_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 };
编写Handler
Handler就是指令对应的处理函数,有可能在一个模块中有多个指令,但是只需要一个Handler方法。我是在set函数(ngx_http_echo)中中指定的Handler处理函数,Handler可以说是模块中真正是实现我们功能的代码函数,它主要有以下四项职责:
读入模块配置。
处理功能业务。
产生HTTP header,发送头部
产生HTTP body
调用发送filter模块
下面是我的实现代码
static ngx_int_tngx_http_echo_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; ngx_http_echo_loc_conf_t *elcf; elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST))) { return NGX_HTTP_NOT_ALLOWED; } r->headers_out.content_type.len = sizeof("text/html"); r->headers_out.content_type.data = (u_char *)("text/html"); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = elcf->ed.len; if(NGX_HTTP_HEAD == r->method){ rc = ngx_http_send_header(r); if(NGX_OK != rc) { return rc; } } b= ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if(NULL == b) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed toallocate response buffer."); return NGX_HTTP_INTERNAL_SERVER_ERROR; } out.buf = b; out.next = NULL; b->pos = elcf->ed.data; b->last = elcf->ed.data + elcf->ed.len; b->memory = 1; b->last_buf =1; rc = ngx_http_send_header(r); if(NGX_OK != rc){ return rc; } return ngx_http_output_filter(r, &out); }
C语言文件ngx_http_echo_module.c的所有代码如下:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> typedef struct { ngx_str_t ed; }ngx_http_echo_loc_conf_t; static char *ngx_http_echo(ngx_conf_t *cf,ngx_command_t *cmd, void *conf); static void*ngx_http_echo_create_loc_conf(ngx_conf_t *cf); static char*ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_http_echo_handler(ngx_http_request_t*r); static ngx_command_tngx_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 ngx_http_module_tngx_http_echo_module_ctx = { NULL, NULL, NULL, NULL, NULL, NULL, ngx_http_echo_create_loc_conf, ngx_http_echo_merge_loc_conf, }; ngx_module_t ngx_http_echo_module = { NGX_MODULE_V1, &ngx_http_echo_module_ctx, ngx_http_echo_commands, NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING }; 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; ngx_conf_set_str_slot(cf, cmd, conf); return NGX_CONF_OK; } static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf) { ngx_http_echo_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t)); if(NULL == conf) { return NGX_CONF_ERROR; } conf->ed.len = 0; conf->ed.data = NULL; return conf; } static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_echo_loc_conf_t *prev = parent; ngx_http_echo_loc_conf_t *conf = child; ngx_conf_merge_str_value(conf->ed, prev->ed, ""); return NGX_CONF_OK; } static ngx_int_tngx_http_echo_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; ngx_http_echo_loc_conf_t *elcf; elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST))) { return NGX_HTTP_NOT_ALLOWED; } r->headers_out.content_type.len = sizeof("text/html"); r->headers_out.content_type.data = (u_char *)("text/html"); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = elcf->ed.len; if(NGX_HTTP_HEAD == r->method){ rc = ngx_http_send_header(r); if(NGX_OK != rc) { return rc; } } b= ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if(NULL == b) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed toallocate response buffer."); return NGX_HTTP_INTERNAL_SERVER_ERROR; } out.buf = b; out.next = NULL; b->pos = elcf->ed.data; b->last = elcf->ed.data + elcf->ed.len; b->memory = 1; b->last_buf =1; rc = ngx_http_send_header(r); if(NGX_OK != rc){ return rc; } return ngx_http_output_filter(r, &out); }
config文件的编写
Nginx不支持像Apache那样的动态链接模块,所以安装模块需要在config的时候将编译模块的命令写入到Nginx的makefile中,然后进行重新编译。安装模块的步骤如下:
1. config的书写
在mytest目录中,新建一个名字为config的文件,文件内容如下:
ngx_addon_name=模块完整名称 HTTP_MODULES="$HTTP_MODULES模块完整名称" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代码文件名"
ngx_addon_name=ngx_http_echo_module HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS$ngx_addon_dir/ngx_http_echo_module.c"
./configure --prefix=/usr/local/nginx --add-module=/home/yefeng/ngxdev/ngx_http_echo make make install
运行结果
1. 修改Nginx的配置文件
在http块中的server块中添加如下内容。
location /echo { echo "hello world!"; }
2. 运行Nginx,使用curi查看运行的结果。
[root@reagemytest]# curl -i http://localhost/echo HTTP/1.1200 OK Server:nginx/1.5.2 Date:Wed, 17 Jul 2013 13:00:54 GMT Content-Type:text/html Content-Length:12 Connection:keep-alive helloworld! [root@reage mytest]#
blog: http://blog.csdn.net/rentiansheng/article/details/9360419
website: http://www.rhttp.cn