最近在阅读nginx源码,为了加深对nginx的理解,打算通过文字的形势记录下在这个过程学到的东西,同时也希望通过这些能让大伙对
nginx模块开发有一个感性的认识,以便以此为入口以后可以开发自己的模块。
这里默认大家已经对nginx的使用有了一个或多或少的认识,所以这里我就不说关于nginx如何使用的废话了,直接开始写hello模块。
首先我们要明确我们的hello模块要干什么,我们打算写这样一个模块,该模块提供一个在location区域的指令,通过该指令我们可以
将指令后面的字符原样输出,比如:
location /say {
say hello;
}
我们访问 /say 就出输出hello的字样。
按照nginx模块命名规范,我们为模块起名为ngx_http_say_module,下面就开始实现该模块
创建文件ngx_http_say_module.c,在该文件中引入编写nginx模块需要的必要的头文件
#include <ngx_config.h>
#incldue <ngx_core.h>
#inclued <ngx_http.h>
自定义结构体
之后我们需要创建一个结构体用来保存我们从配置文件中解析到的指令信息,这样就可以在需要的时候从结构体中拿到我们设置的指令值
typedef struct {
ngx_str_t say; //用这个字段保存say指令值
} ngx_http_loc_conf_t;
模块的指令信息
有了一个保存指令值的结构,现在需要一个用来描述指令的结构,在nginx中每个模块都可以有多个指令,每个指令用一个 ngx_command_t
来表示,整个的模块指令会被放在一个ngx_command_t数组中
static ngx_command_t ngx_http_say_commands[] = {
{
//指令名字
ngx_string("say");
//表示该指令可以出现在location区域并且只能带一个参数
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
//ngxin中的方法,用来将指令值存入我们的ngx_http_say_loc_conf_t结构体中
//如果有必要这里也可以用自己定义的方法
ngx_conf_set_str_slot,
//指明指令配置解析后存放到哪个位置,有三个位置分别是main_conf、srv_conf、loc_conf
//分别代表http全局配置、某个server的配置、某个location的配置
//因为我们的指令存在于location区域,所以我们这里选的是loc_conf
NGX_HTTP_LOC_CONF_OFFSET,
//存放指令值的字段在其自身结构体中的偏移位置
offsetof(ngx_http_say_loc_conf_t,say),
//存放自定义的数据,这里我们不需要,所以设置为null
NULL
},
ngx_null_command
}
模块的上下文信息
模块的上下文提供了一些供nginx在解析配置文件时调用的函数,这些函数在解析配置文件的时候被依次调用
为了简单起见我们这里只注册了两个函数
static ngx_http_module_t ngx_http_say_module_ctx={
NULL, /*preconfiguration*/
ngx_http_say_init, /*postconfiguration*/
NULL, /*create main configuration*/
NULL, /*init main configuration*/
NULL, /*create server configuration*/
NULL, /*merge server configuration*/
ngx_http_say_create_loc_conf, /*create location configuration*/
NULL /*merge location configuration*/
};
ngx_http_say_create_loc_conf函数的作用就是创建我们自定义的数据结构
nginx为了更精确的处理请求,把请求处理过程分了11个阶段,每个指令一般都会运行在某一个阶段
所有的nginx模块都会注册在某几个阶段,然后每个阶段负责执行模块中不同的指令,在这11个阶段中有一个
NGX_HTTP_CONTENT_PHASE阶段,该阶段一般负责内容输出,所以在ngx_http_say_init方法中我们会把模块
注册到该阶段
模块的定义
有了上面的信息后我们还需要向nginx描述我们的模块,在nginx中使用ngx_module_t结构体来描述模块,针对我们的模块具体描述如下
ngx_module_t ngx_http_say_module = {
NGX_MODULE_V1,
&ngx_http_say_module_ctx, /*模块上下文结构,可使用里面的一些函数*/
ngx_http_say_commands, /*模块定义的指令*/
NGX_HTTP_MODULE, /*module type*/ //表示这是一个HTTP模块
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
};
这个结构比较简单,只有两处是我们自己的,其他基本都是固定的
开始编写相关的函数
在nginx的上下文结构中我们注册了两个回调函数,我们先看create_loc函数
static void *ngx_http_say_create_loc_conf(ngx_conf_t *cf){
ngx_http_say_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_say_loc_conf_t));
if (conf == NULL){
return NULL;
}
return conf;
}
就像我们前面说的,该函数仅仅创建一个我们自定义的结构体然后返回
nginx对内存的管理是通过ngx_pool_t结构体实现的,如果我们需要分配内存,都是从该结构体中分配的
在最终的某个时刻,通过销毁该结构体,那么所有从他里面分配的内存都会被释放,这样我们就不必担心
那个内存申请了没有释放。对于如何操作该结构体,nginx提供了一系列的方法,其中ngx_pcalloc()就是用来
申请内存的,所以我们这里用他在申请ngx_http_say_loc_conf_t结构需要的内存。
初始化方法ngx_http_say_init,在该方法里面注册阶段,并绑定该阶段要运行的函数
static ngx_int_t ngx_http_say_init(ngx_conf_t *cf){
ngx_http_handler_pt *h; //真正处理请求的函数
ngx_http_core_main_conf_t *cmcf;
cmcf = ngx_http_conf_get_module_main_conf(cf,ngx_http_core_module);
//函数注册到CONTENT阶段
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if(h == NULL){
return NGX_ERROR;
}
*h = ngx_http_say_handler; //真正处理请求的函数
return NGX_OK;
}
可以看到我们在这个函数中,将ngx_http_say_handler方法注册到了NGX_HTTP_CONTENT_PHASE阶段
当请求处理运行到该阶段后,nginx就会调用我这里注册的方法,然后我们在这个方法中就可以根据指令的值来做各种事情
真正处理请求的函数ngx_http_say_handler
static ngx_int_t ngx_http_say_handler(ngx_http_request_t *r){
ngx_int_t rc;
ngx_buf_t *b; //nginx中用来表示buffer的结构体
ngx_chain_t out; //nginx中数据的输出都是通过链来完成的,这个chain中有一个buf,还有一个指向下一个chain的指针
ngx_http_say_loc_conf_t *my_conf; //我们之前自定义的结构体,在该方法中取出,里面存放了我们的指令值
//存放从my_conf中都到的信息,一个临时buf
u_char* tmp_buf = ngx_pcalloc(r->pool, my_conf->say.len+1);
//取出我们的指令配置信息
my_conf = ngx_http_get_module_loc_conf(r,ngx_http_say_module);
//如果指令不存在,则放弃执行该方法,之后nginx会把处理权交给index、autoindex、static这三个nginx默认处理静态资源的模块
if(my_conf->say.len == 0){
return NGX_DECLINED;
}
//将数据读入到临时buf中,在这里我们为输出数据加了一个换行符
ngx_sprintf(tmp_buf,"%s\n",my_conf->say.data);
ngx_uint_t content_length = ngx_strlen(tmp_buf);
//我们这里不需要处理请求体, 所以我们直接抛弃请求体
rc = ngx_http_discard_request_body(r);
if(rc != NGX_OK){
return rc;
}
//创建一个buf用来标记要输出的数据
b = ngx_pcalloc(r->pool,sizeof(ngx_buf_t));
if (b == NULL){
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
//指定buf数据的开始地址和结束地址
b->pos = tmp_buf;
b->last = tmp_buf + content_length;
b->memory = 1; //标记buf的数据在内存中, 还有一种是b->file 标记数据是否在文件中
b->last_buf = 1; //这是buffer 在整个输出链中是最后一个buffer
//把这个buf放入当前chain节点中
out.buf = b;
out.next = NULL;
//需要设置响应头
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
//发送响应头,输出响应头的入口永远是这个方法,该方法会调用整个header filter链
rc = ngx_http_send_header(r);
if(rc == NGX_ERROR || rc > NGX_OK || r->header_only){
return rc;
}
//最后调用该方法去输出响应体,这个方法是调用body filter的一个入口
return ngx_http_output_filter(r,&out);
}
向nginx注册我们的模块并运行
首先我们需要一个config文件,用来描述我们的模块信息,内容如下:
//告诉nginx我们模块的名字
ngx_addon_name=ngx_http_say_module
//告诉nginx我们增加一个HTTP模块
HTTP_MODULES="$HTTP_MODULES ngx_http_say_module"
//告诉nginx编译哪些文件
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_say_module.c"
将我们的模块文件组织成这样一个目录结构
ngx_http_say_module
|
|--------config
|
|-------ngx_http_say_module.c
将模块编译到nginx中
<nginx_path>/configure --add-module=<path>/ngx_http_say_module
安装并重启后在配置文件中配置如下指令:
location ~ ^/say {
say hello;
}
执行curl http://127.0.0.1/say 输出结果 hello