7.1 ngx_command_t 数组
commands 数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾是用ngx_numm_command表示。Nginx在解析配置文件中的一个配置项时首先会遍历所有的模块,对于每一个模块而言,即通过遍历commands数组进行,另外,在数组中检查到ngx_numm_command时,会停止使用当前模块解析该配置项。每一个ngx_command_t结构体定义个如下配置:
typedef struct ngx_command_s ngx_command_t;struct ngx_command_s {//配置项名称,如"gzip"ngx_str_t name;//配置项类型,type将制定配置项可以出现的位置,//例如:出现在server{}活location{}中,等等ngx_uint_t type;//出现了name中指定的配置项后,将会调用set方法处理配置项的参数char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//在配置文件中的偏移量ngx_uint_t conf;//通常用于使用预设的解析方法解析配置项,这是配置模块的一个优秀的设计,//需要与conf配合使用ngx_uint_t offset;//配置项读取后的处理方法,必须是ngx_conf_post_t结构的指针void *post;};
ngx_null_command只是一个空的ngx_command_s:
#define ngx_null_command {ngx_null_string, 0, NULL, 0, 0, NULL}
也就是说,对于我们在nginx.cong 中编写的配置项mytest来说,nginx首先会遍历所有的模块(modules),而对于每个模块,会遍历他所对应的ngx_command_t数组,试图找到关于我们配置项目mytest的解析方式。
static ngx_command_t ngx_http_mytest_commands[] ={{ngx_string("mytest"),NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,ngx_http_mytest,NGX_HTTP_LOC_CONF_OFFSET,0,NULL},ngx_null_command};
7.2 command中用于处理配置项参数的set方法
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ngx_http_core_loc_conf_t *clcf;//首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据//结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个//http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);//http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果//请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们//实现的ngx_http_mytest_handler方法处理这个请求clcf->handler = ngx_http_mytest_handler;return NGX_CONF_OK;}
关于ngx_http_conf_get_module_loc_conf 的定义可以参考:
http://lxr.nginx.org/source/src/http/ngx_http_config.h#0065
本质: 就是设置
ngx_http_mytest_handler, 匹配项被选中的时候, 应该如何解析。
7.3 定义ngx_http_module_t接口
这部分的代码, 是用于http框架的, 相当于http框架的回掉函数, 由于这里并不需要框架做任何操作。
static ngx_http_module_t ngx_http_mytest_module_ctx ={NULL, /* preconfiguration */NULL, /* postconfiguration */NULL, /* create main configuration */NULL, /* init main configuration */NULL, /* create server configuration */NULL, /* merge server configuration */NULL, /* create location configuration */NULL /* merge location configuration */};
7.4 定义处理“mytest”command的handler
我们这里需要定义配置项被匹配之后的处理方法.
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){//必须是GET或者HEAD方法,否则返回405 Not Allowedif (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))){return NGX_HTTP_NOT_ALLOWED;}//丢弃请求中的包体ngx_int_t rc = ngx_http_discard_request_body(r);if (rc != NGX_OK){return rc;}//设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏//ngx_string,它可以把ngx_str_t的data和len成员都设置好ngx_str_t type = ngx_string("text/plain");//返回的包体内容ngx_str_t response = ngx_string("Hello World!");//设置返回状态码r->headers_out.status = NGX_HTTP_OK;//响应包是有包体内容的,所以需要设置Content-Length长度r->headers_out.content_length_n = response.len;//设置Content-Typer->headers_out.content_type = type;//发送http头部rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only){return rc;}//构造ngx_buf_t结构准备发送包体ngx_buf_t *b;b = ngx_create_temp_buf(r->pool, response.len);if (b == NULL){return NGX_HTTP_INTERNAL_SERVER_ERROR;}//将Hello World拷贝到ngx_buf_t指向的内存中ngx_memcpy(b->pos, response.data, response.len);//注意,一定要设置好last指针b->last = b->pos + response.len;//声明这是最后一块缓冲区b->last_buf = 1;//构造发送时的ngx_chain_t结构体ngx_chain_t out;//赋值ngx_buf_tout.buf = b;//设置next为NULLout.next = NULL;//最后一步发送包体,http框架会调ngx_http_finalize_request方法//结束请求return ngx_http_output_filter(r, &out);}
7.5 定义ngx_module_t中的mytest模块
定义HTTP模块方式很简单,例如:
ngx_module_t ngx_http_mytest_module;
其中ngx_module_t是一个Nginx模块的数据结构,下面来分析一下Nginx模块中的所有成员:
typedef struct ngx_module_s ngx_module_t;struct ngx_module_s {/*下面的ctx_index、index、spare0,spare1,spare2,spare3,version变量不需要再定义时候赋值,可以用Nginx准备好的宏NGX_MODULE_V1来定义,已经定义好了这7个值#define NGX_MODULE_V1 0,0,0,0,0,0,1对于一类模块(由下面的type成员决定类别)而言,ctx_index表示当前模块在这类模块中的序号。这个成员常常是由管理这类模块的一个Nginx核心模块设置的,对于所有的HTTP模块而言,ctx_index是由核心模块ngx_http_module设置的。ctx_index非常重要,Nginx的模块化设计非常依赖于各个模块的顺序,他们既用于表达优先级,也用于表明每个模块的位置,借以帮助Nginx框架快速获得某个模块的数据.*/ngx_uint_t ctx_index;/*index表示当前模块在ngx_module数组中的序号。注意,ctx_index表示的是当前模块在一类模块中的序号,而index表示当前模块在所有模块中的序号,同样很关键.Nginx启动时会根据ngx_modules数组设置各模块的index值,例如:ngx_max_module = 0;for(i = 0; ngx_module[i]; i++) {ngx_modules[i]->index = ngx_max_module++;}*/ngx_uint_t index;//spare系列的保留变量,暂未使用ngx_uint_t spare0;ngx_uint_t spare1;ngx_uint_t spare2;ngx_uint_t spare3;//模块的版本,便于将来的拓展,目前只有一种,默认为1ngx_uint_t version;/*ctx用于指向一类模块的上下文结构体,为什么需要ctx呢?因为前面说过,Nginx模块有许多种类,不同类模块之间的功能差别很大。例如,事件类型的模块主要处理I/O事件的相关功能,HTTP类型的模块主要处理HTTP应用层的功能。这样每个模块都有了自己的特性,而ctx会指向特定类型模块的公共接口。例如,在HTTP模块中,ctx需要指向ngx_http_module_t结构体*/void *ctx;/*commands处理nginx.conf中的配置项*/ngx_command_t *commands;/*type表示该模块的类型,它与ctx指针是紧密相关的。在官方Nginx中,它的取值范围有以下5种:NGX_HTTP_MODULENGX_CORE_MODULENGX_CONF_MODULENGX_EVENT_MODULENGX_MAIL_MODULE*/ngx_uint_t type;/*在Nginx的启动、停止过程中,以下7个函数指针表示7个执行点分别用调用这7种方法。对于任意一个方法而言,如果不需要Nginx再某个时刻执行它,那么简单地把它设为NULL空指针即可。*//*虽然从字面上理解应当在master进程启动的时,回调init_master,但到目前为止,框架代码从来不会调动它,所以设置为NULL*/ngx_int_t (*init_master)(ngx_log_t *log);/*init_module回调方法在初始化所有模块时候被调用。在master/worker模式下,这个阶段将在启动worker子进程前完成。*/ngx_int_t (*init_module)(ngx_cycle_t *cycle);/*init_process 回调方法在正常服务前被调用。在master/worker模式下,多个worker子进程已经产生。在每个worker进程的初始化过程会调用所有模块的init_process函数*/ngx_int_t (*init_process)(ngx_cycle_t *cycle);/*由于Nginx暂时不支持多线程模式,所以init_thread在框架中没有被调用过,设置为NULL*/ngx_int_t (*init_thread)(ngx_cycle_t *cycle);/*同上、exit_thread也不支持,设为NULL*/void (*exit_thread)(ngx_cycle_t *cycle);/*exit_process回调方法在服务停止前被调用。在/master/worker模式下,worker进程会在退出前调用*/void (*exit_process)(ngx_cycle_t *cycle);/*exit_master回调方法将在master进程退出前被调用*/void (*exit_master)(ngx_cycle_t *cycle);/*一下8个spare_hook变量也是保留字段,目前没有使用,但可用Nginx提供的NGX_MODULE_V1_PADDING宏来填充#define NGX_MODULE_V1_PADDING 0,0,0,0,0,0,0,0*/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;};
所以在定义一个HTTP模块的时候,务必把type字段设置为NGX_HTTP_MODULE.
对于下列回调方法:init_module、init_process、exit_process、exit_master
调用他们的是Nginx框架代码。换句话说,这4个回调方法与HTTP框架无关。
即使nginx.conf中没有设置http{…}这种开启HTTP功能的配置项,这些
回调方法仍然会被调用。因此,通常开发HTTP模块时候都把他们设置为NULL。
这样Nginx不作为web服务器使用时,不会执行HTTP模块的任何代码。
定义HTTP时候,最重要的是要设置ctx和commands这两个成员。
对于HTTP类型模块来说,ngxmodule中的ctx指针必须指向ngx_http_module_t接口。
所以最终我们定义个ngx_module_t 模块如下:
ngx_module_t ngx_http_mytest_module ={NGX_MODULE_V1,&ngx_http_mytest_module_ctx, /* module context */ngx_http_mytest_commands, /* module directives */NGX_HTTP_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};
这样, mytest 模块在编译的时候, 就可以被加入到ngx_modules的全局数组中了。
7.6 完整代码 ngx_http_mytest_module.c
所以全部的编码工作完毕,最终的代码应该如下:
#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h>#include <sys/types.h>#include <unistd.h>//定义处理用户请求hello world handlerstatic ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){//必须是GET或者HEAD方法,否则返回405 Not Allowedif (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))){return NGX_HTTP_NOT_ALLOWED;}//丢弃请求中的包体ngx_int_t rc = ngx_http_discard_request_body(r);if (rc != NGX_OK){return rc;}//设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏//ngx_string,它可以把ngx_str_t的data和len成员都设置好ngx_str_t type = ngx_string("text/plain");//返回的包体内容ngx_str_t response = ngx_string("Hello World!");//设置返回状态码r->headers_out.status = NGX_HTTP_OK;//响应包是有包体内容的,所以需要设置Content-Length长度r->headers_out.content_length_n = response.len;//设置Content-Typer->headers_out.content_type = type;//发送http头部rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only){return rc;}//构造ngx_buf_t结构准备发送包体ngx_buf_t *b;b = ngx_create_temp_buf(r->pool, response.len);if (b == NULL){return NGX_HTTP_INTERNAL_SERVER_ERROR;}//将Hello World拷贝到ngx_buf_t指向的内存中ngx_memcpy(b->pos, response.data, response.len);//注意,一定要设置好last指针b->last = b->pos + response.len;//声明这是最后一块缓冲区b->last_buf = 1;//构造发送时的ngx_chain_t结构体ngx_chain_t out;//赋值ngx_buf_tout.buf = b;//设置next为NULLout.next = NULL;//最后一步发送包体,http框架会调用ngx_http_finalize_request方法//结束请求return ngx_http_output_filter(r, &out);}//定义command用于处理配置项参数的set方法static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ngx_http_core_loc_conf_t *clcf;//首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据//结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个//http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);//http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果//请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们//实现的ngx_http_mytest_handler方法处理这个请求clcf->handler = ngx_http_mytest_handler;return NGX_CONF_OK;}//定义ngx mytest 配置匹配的commandstatic ngx_command_t ngx_http_mytest_commands[] ={{ngx_string("mytest"),NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,ngx_http_mytest,NGX_HTTP_LOC_CONF_OFFSET,0,NULL},ngx_null_command};//定义ngx_http_module_t接口static ngx_http_module_t ngx_http_mytest_module_ctx ={NULL, /* preconfiguration */NULL, /* postconfiguration */NULL, /* create main configuration */NULL, /* init main configuration */NULL, /* create server configuration */NULL, /* merge server configuration */NULL, /* create location configuration */NULL /* merge location configuration */};//定义mytest模块ngx_module_t ngx_http_mytest_module ={NGX_MODULE_V1,&ngx_http_mytest_module_ctx, /* module context */ngx_http_mytest_commands, /* module directives */NGX_HTTP_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};
7.7 配置文件 config
但是之后我们还需要给该模块相同目录下提供一个配置文件”config”表示在nginx编译的时候的一些编译选项。
ngx_addon_name=ngx_http_mytest_moduleHTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
所以我们的模块编写完毕了,当前目录应该有两个文件
lsconfig ngx_http_mytest_module.c
7.8 重新编译Nginx 并添加自定义模块
进入Nginx源码目录
执行
./configure --prefix=/usr/local/nginx --add-module=/home/ace/openSource_test/nginx_module_http_test
—add-module为刚才自定义模块源码的目录
makesudo make install
测试自定义模块
修改nginx.conf文件
server {listen 8777;server_name localhost;location / {mytest;#我们自定义模块的名称}}
重新启动nginx
打开浏览器输入地址和端口
