写一个核心模块
在上一篇中有提到目前ngx共有六种模块,分别是:
NGX_CORE_MODULE
NGX_CONF_MODULE
NGX_HTTP_MODULE
NGX_MAIL_MODULE
NGX_EVENT_MODULE
NGX_STREAM_MODULE
如果按照一种更抽象的方式来划分,除了NGX_CONF_MODULE外,我们基本上可以把ngx模块类型划分为两种,一种是核心类型(NGX_CORE_MODULE),另一种是非核心类型。
核心类型模块主要用来搭建ngx基础功能和为非核心类型模块起支撑作用,比如核心模块src/http/ngx_http.c的主要作用就是支撑(或管理)所有http模块可以正常工作。非核心类型模块主要完成各自的业务功能,比如ngx_http_proxy_module主要完成反向代理工作,而ngx_http_gzip_module模块则完成数据压缩。
不管是核心模块还是非核心模块,他们在整体的架构实现上差别其实并不大,最大的差别基本上有两个方面,一个是指令集的实现(各个模块都有自己的指令集),另一个是上下文的实现(ngx_module_t->ctx字段)。基本上每种模块类型都会有一个特定的上下定义,上下文一般限定了该类型模块在文件解析过程中应该做的事(或能够做的事);指令集则描述了某个具体模块都有哪些功能,比如ngx_http_rewrite_module模块中的return指令可以直接返回数据。
正常情况下,在我们的实际业务场景中,编写一个非核心模块,要比编写一个核心模块的几率大的多得多,不过为了让读者对模块编写有一个更清晰的认识,我们循序渐进,这篇先实现一个核心自定义模块,等下篇再实现一个非核心自定义模块。
1开始之前
上篇有说过,要扩展一个模块,一般需要约定三种数据,一种是ngx_modue_t.ctx字段,另一种是ngx_modue_t.type字段,还有一个是该模块提供的指令集commands[](元素类型为ngx_command_t)。
对于以上三种数据,因为我们明确指出是要实现一个核心模块,所以type的值就是NGX_CORE_MODULE,其它两种则需要根据我们模块要完成的功能而定。
编写这个核心模块的主要目的是让读者熟悉模块的工作机制,所以并不会有任何特殊的业务功能,假设这个模块有如下两个指令:
my_core_simple xxx …;
my_core_block { // anything }
第一个条指令可以接收任意参数,作用是指令解析时打印出指令后的参数;第二个指令的参数是一个参数块,在它里面可以写任意多个参数,比如这样:
my_core_block {
aaa 111 222 333;
bbb 122 33;
}
它的作用是在指令解析时打印出内部参数。
2约定上下文
核心模块在ngx中的上下文定义是这样的:
typedef struct {
ngx_str_t name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;
我们对它的实现如下:
static ngx_core_module_t ngx_my_core_module_ctx = {
ngx_string("my_core"),
ngx_my_core_create_conf,
ngx_my_core_init_conf
};
其中“my_core”表示这个核心模块的名字,随后两个方法则是对*(*create_conf)和*(*ini_conf)方法的实现,他们的具体实现分别如下:
static void*
ngx_my_core_create_conf(ngx_cycle_t *cycle)
{
// 向标准输出打印数据
ngx_log_stderr(0, "start runing ngx_my_core_create_conf()");
// ngx要求该方法不能返回空,否则启动失败
//我们本例不需要这个数据,随便返回一个
return cycle;
}
static char *
ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
// 向标准输出打印数据
ngx_log_stderr(0, "start runing ngx_my_core_init_conf()");
return NULL;
}
第一个方法在开始解析配置文件前执行,该方法一般用来创建一个自定义的结构体,你可以用这个结构体存放一些需要在运行时用到的信息(比如某个指令的配置信息);第二个方法在配置文件解析完毕后执行,可以在这里做一些初始化之类的工作。我们这里实现的及其简单,就是打印了各自的方法名。
3约定指令集
在ngx中每条指令都会用一个结构体来表示,这个结构体的具体定义如下:
typedef struct ngx_command_s ngx_command_t;
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;
};
-
name表示指令的名字。
-
type用来指定该指令可以出现的位置,以及它可以携带介个参数等
-
*(*set) 表示一个函数指针(用来存放函数),当ngx解析到该指令后就会回调这个方法。
-
其它的我们这次用不到,等后续用到再说。
我们的模块共有两条指令,用数组来存放他们,具体如下:
static ngx_command_t ngx_my_core_commands [] = {
{ ngx_string("my_core_simple"),
NGX_MAIN_CONF| NGX_CONF_TAKE1,
ngx_my_core_simple,
0,
0,
NULL },
{ ngx_string("my_core_block"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_my_core_block,
0,
0,
NULL },
ngx_null_command
};
数组总共有三个元素,第一个表示my_core_simple指令的定义。
第二个是my_core_block指令的定义。
最后一个用来表示指令集结尾,是一个宏定义。
NGX_MAIN_CONF表示指令只能出现在主配置这一级别(不能在某个xxx{}块内)。
NGX_CONF_TAKE1表示指令只能带一个参数。
NGX_CONF_BLOCK表示该指令是一个块指令(比如xxx {})。
NGX_CONF_NOARGS表示该指令不能携带参数。
4实现指令的回调函数
每个指令对应的回调函数必须是如下形式:
char * xxx(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
其中入参cf存放了一些当前解析数据的上下文,具体内容我们后续用到的时候再说。
cmd表示当前解析到的指令定义。
conf表示ngx_my_core_create_conf方法(在ngx_my_core_module_ctx中设置的)的返回值。
第一个指令对应的函数实现如下:
static char *
ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *name;
// 取出指令名字
name = cf->args->elts;
// 向标准输出打印指令名字和指令值
ngx_log_stderr(0, "find a directive name[%s] value[%s] method[ngx_my_core_simple]", name->data, name[1].data);
return NGX_CONF_OK;
}
其中cf->args->elts表示解析到的指令串值,是一个数组,比如解析到的指令串是:
my_core_simple 111;
那么该数组的第一个值就是“my_core_simple”,第二个则是“111”。
第二个指令因为是一个块指令,实现上稍微复杂一点,具体如下:
static char *
ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_conf_t save;
ngx_str_t *name;
// 取出指令名字
name = cf->args->elts;
// 向标准输出打印指令名字和指令值
ngx_log_stderr(0, "find a directive block name[%s] method[ngx_my_core_block]", name->data);
// 开始解析my_core_block{}块内的数据
save = *cf;
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
// 块内数据由该方法负责解析(ngx自带的方法)
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
ngx在解析配置文件的时候,默认情况下,每当解析出一个“指令串”(ngx内部叫token)时,会把该串的第一个“单词”当成指令,然后用去模块中匹配指令定义,匹配成功后就会回调对应的指令方法。
指令串的定义是这样的,以“;”或“{”结尾的串,比如:
aa 112 … ;
ss {
bb xx {
以上三种形式中的任何一种都算一个“指令串”,其中第一个“单词”(按空格分隔)就是指令名字。
不过这次我们没有使用ngx的默认行为来解析我们的自定义块指令,因为我们并没有打算把块内的数据当成指令,仅仅把它当成普通字符串,所以我们有了如下两行代码:
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
第一行代替ngx的默认行为,这个方法的形式跟指令对应的回调函数一样,每当解析完我们自定义指令块内的一条“指令串”时就会调用这个方法。
第二个设置的值会传入ngx_my_core_block_handler方法中。
我们写一下ngx_my_core_block_handler方法的实现:
static char *
ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_uint_t num;
ngx_str_t *words;
// 解析到的“指令串”中“单词”的个数
num = cf->args->nelts;
// 解析到的“指令串”
words = cf->args->elts;
// 向标准输出打印解析到的信息
ngx_log_stderr(0, "find a directive string, num of words[%ui] first word[%s] end word[%s]", num, words[0].data, words[num-1].data);
return NGX_CONF_OK;
}
偷了个懒,只打印了每个指令串的单词个数和第一个单词跟最后一个单词的名字。
另外因为我们在解析块内数据时修改了ngx的默认行为,所以在调用ngx_conf_parse()方法之前要先“保留现场”
save = *cf;
等解析完我们自定义的快内数据后再“还原现场”
*cf = save;
因为在解析其它ngx块指令的时候是不需要设置cf->handler的(比如解析http{}时用的就是默认行为)。
5组合三种约定好的数据
上篇文章曾经说过,不管哪种类型模块,都是用ngx_module_t表示的,约定数据都准备好后就需要用这个结构体来真正声明一个模块,具体如下:
ngx_module_t ngx_my_core_module = {
NGX_MODULE_V1,
&ngx_my_core_module_ctx, /* module context */
ngx_my_core_commands, /* module directives */
NGX_CORE_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
};
组合好之后我们的自定义核心模块就算写好了,剩下的就是如何把他安装到ngx并运行了。
6注册并运行模块
模块写好之后就要向ngx中注册了,注册之前我们需要先把模块代码组织成如下结构:
ngx_my_core_module
config
ngx_my_core_module.c
其中ngx_my_core_module是一个目录;ngx_my_core_module.c就是写好的源代码;config是一个文件,里面的数据用来描述要注册的模块信息。
我们的模块config文件有如下信息
ngx_addon_name=ngx_my_core_module
CORE_MODULES="$CORE_MODULES ngx_my_core_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_my_core_module.c"
第一行的信息在执行configure的时候会输出到控制台,可以随便写,但不推荐。
第二行是ngx内部要用的模块变量名,这个名字会放到/objs/ngx_modules.c文件中,所以必须是模块声明的变量名(ngx_my_core_module)。
第三行用来指定模块源码路径。
目录建好之后就可以进到ngx源码根目录中进行注册了,假设我们的模块和nginx源码都在/my目录下,则注册步骤如下:
cd /my/nginx-1.9.4
执行configure命令:
./configure –prefix=/my/nginx --add-module=/my/ngx_my_core_module
编译并安装
make & make install
注册好了就可以“运行”模块了,不过在此之前我们先把自定义的指令都配置好,把如下指令配置nginx.conf中:
my_core_simple 111;
my_core_block {
aaa 222;
bbb 111 222 333 444 555;
}
启动ngx后你就会在控制台看到如下信息:
nginx: start runing ngx_my_core_create_conf()
nginx: find a directive name[my_core_simple] value[111] method[ngx_my_core_simple]
nginx: find a directive block name[my_core_block] method[ngx_my_core_block]
nginx: find a directive string, num of words[2] first word[aaa] end word[222]
nginx: find a directive string, num of words[6] first word[bbb] end word[555]
nginx: start runing ngx_my_core_init_conf()
模块注册好了,并且已经可以“运行”了,只不过这个模块的“运行”就是在启动的时候打印一些信息,实际工作中基本没什么用,不过用来学习模块的编写基本够用了,有兴趣的读者可以试着模仿一个更实用的例子。
7附件
为了便于读者学习,这里贴出完整代码
#include <ngx_config.h>
#include <ngx_core.h>
static void * ngx_my_core_create_conf(ngx_cycle_t *cycle);
static char * ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf);
static char * ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char * ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_my_core_commands[] = {
{ ngx_string("my_core_simple"),
NGX_MAIN_CONF| NGX_CONF_TAKE1,
ngx_my_core_simple,
0,
0,
NULL },
{ ngx_string("my_core_block"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_my_core_block,
0,
0,
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_my_core_module_ctx = {
ngx_string("my_core"),
ngx_my_core_create_conf,
ngx_my_core_init_conf
};
ngx_module_t ngx_my_core_module = {
NGX_MODULE_V1,
&ngx_my_core_module_ctx, /* module context */
ngx_my_core_commands, /* module directives */
NGX_CORE_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 void *
ngx_my_core_create_conf(ngx_cycle_t *cycle)
{
ngx_log_stderr(0, "start runing ngx_my_core_create_conf()");
return cycle;
}
static char *
ngx_my_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
ngx_log_stderr(0, "start runing ngx_my_core_init_conf()");
return NULL;
}
static char *
ngx_my_core_simple(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_str_t *name;
name = cf->args->elts;
ngx_log_stderr(0, "find a directive name[%s] value[%s] method[ngx_my_core_simple]",name->data, name[1].data);
return NGX_CONF_OK;
}
static char *
ngx_my_core_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_conf_t save;
ngx_str_t *name;
name = cf->args->elts;
ngx_log_stderr(0, "find a directive block name[%s] method[ngx_my_core_block]",
name->data);
save = *cf;
cf->handler = ngx_my_core_block_handler;
cf->handler_conf = conf;
rv = ngx_conf_parse(cf, NULL);
*cf = save;
return rv;
}
static char *
ngx_my_core_block_handler (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_uint_t num;
ngx_str_t *words;
num = cf->args->nelts;
words = cf->args->elts;
ngx_log_stderr(0, "find a directive string, num of words[%ui] first word[%s] end word[%s]",num, words[0].data, words[num-1].data);
return NGX_CONF_OK;
}
上一篇: linux上mysql优化三板斧——CPU、内存、文件系统
下一篇: 微信小程序 模块化详细介绍