在Nginx上做二次开发大部分时候都是做的模块(module)的开发,今天就来介绍一下Nginx中的module开发的基本步骤,先来认识一下Nginx中的module。
一个基本的module框架(叫框架可能不太准确,这里的意思是需要编写的地方)由以下3个部分组成:
首先是必须要实现的一个command结构体
struct ngx_command_s {
ngx_str_t name;//这个name就是你在需要使用这个module时,在conf文件配置指令的名称
ngx_uint_t type;//type可以是一串指令的并集,它的功能是用来指定这个module应该出现在配置文件中的哪个地方,以及这个配置指令接受几个参数等
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//这个回调函数的功能是当conf文件开始解析这个配置指令时,就会调用这个回调
ngx_uint_t conf;//conf字段指定该配置项存储的位置到底在哪个内存池下面,有三个选项NGX_HTTP_MAIN_CONF_OFFSET,NGX_HTTP_SRV_CONF_OFFSET,NGX_HTTP_LOC_CONF_OFFSET
ngx_uint_t offset;//offset一般会调用一个offsetof()函数,用于指定配置的指令数据的存放位置距离conf指针偏移值具体为多少
void *post;//该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为0即可
};
然后需要实现这个module的上下文,上下文的概念可能有点抽象,在这里可以理解成就是注册这个module在Nginx运行的各个阶段需要被调用的相应函数就可以了,上下文的结构体如下:
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);//调用该函数创建本模块位于http block的配置信息存储结构。该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);//调用该函数初始化本模块位于http block的配置信息存储结构。该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。
void *(*create_srv_conf)(ngx_conf_t *cf);//调用该函数创建本模块位于http server block的配置信息存储结构,每个server block会创建一个。该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);//因为有些配置指令既可以出现在http block,也可以出现在http server block中。那么遇到这种情况,每个server都会有自己存储结构来存储该server的配置,但是在这种情况下http block中的配置与server block中的配置信息发生冲突的时候,就需要调用此函数进行合并,该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。当然为了安全起见还是建议提供。该函数执行成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。
void *(*create_loc_conf)(ngx_conf_t *cf);//调用该函数创建本模块位于location block的配置信息存储结构。每个在配置中指明的location创建一个。该函数执行成功,返回创建的配置对象。失败的话,返回NULL。
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);//与merge_srv_conf类似,这个也是进行配置值合并的地方。该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。
} ngx_http_module_t;
这两个部分写好后就是最关键的部分了,需要实现一个module声明的结构体,该结构体如下:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
ngx_uint_t ctx_index;//后面会根据这个ctx_index来调用前面create_.._conf创建的上下文
ngx_uint_t index;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t abi_compatibility;
ngx_uint_t major_version;
ngx_uint_t minor_version;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
ngx_int_t (*init_master)(ngx_log_t *log);//init 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);
uintptr_t spare_hook0;//提供一些hook点
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;
};
以上三个基本块完成后,需要完成一个config文件,这个config文件的作用是当你添加这个模块到configure中时,在configure阶段就会扫描这个config文件,然后读取里面的一些配置命令,如auto . have,auto .feature等等,下面简单说下auto have和auto feature
auto have一般用于作为一个开关的配置命令,使用auto have 就会在代码中define一个你编写的变量
auto feature一般用于环境检查,auto feature会将你编写的一段代码添加到模块代码中,然后编译,如果能通过则会返回一个yes。
一个简单的config文件就如下
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"//如果这个模块的实现有多个源文件,那么都在NGX_ADDON_SRCS这个变量里