要求在浏览器地址栏中输入"localhost/login?user=qifeng&passwd=123456",并在浏览器上显示验证结果(成功 或 失败)。以下是在NGINX中添加一个LOGIN模块的整个处理过程。
根据需求修改配置文件nginx.conf,在http{...}的server{...}中增加location配置信息:
图1 修改配置
(注意:将passwd的值"abcd"改为“123456”)
在NGINX源码目录src下新建ext文件夹,src/ext用于存放所有扩展模块代码,src/ext/login则用于存放LOGIN模块的代码.
#mkdir -p src/ext/login
#cd src/ext/login
#vim ngx_http_login_module.c
LOGIN模块主要实现的是对用户(user)和密码(password)的验证,因此,配置信息结构中需要包含user字段和password字段,故其结构定义如下:(命名规则:ngx_http_模块名_(main|srv|loc)conf_t)
/* 配置项结构体:用于存放配置项和对应值 */ typedef struct { ngx_str_t user; ngx_str_t passwd; }ngx_http_login_loc_conf_t;
/* 配置项解析数组:配置指令是通过此数组依次解析的. */ static ngx_command_t ngx_http_login_commands[] = { { ngx_string("user"), /* 配置项名 */ NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, /* 所属模块和配置值个数 */ ngx_conf_set_str_slot, /* 解析该配置项的回调 */ NGX_HTTP_LOC_CONF_OFFSET, /* 配置存储位置 */ offsetof(ngx_http_login_loc_conf_t, name), /* 配置值赋给的变量 */ NULL }, { ngx_string("passwd"), NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_login_loc_conf_t, passwd), NULL }, { ngx_string("check"), NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, ngx_http_login_check, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command };
代码2 配置项解析数组
定义模块配置解析过程中各阶段的处理回调:(命名规则:ngx_http_模块名_module_ctx)
static ngx_http_module_t ngx_http_login_module_ctx = { NULL, /* 读入配置前的回调 */ NULL, /* 读入配置后的回调 */ NULL, /* 创建main配置时的回调 */ NULL, /* 初始化main配置时的回调 */ NULL, /* 创建srv配置时的回调 */ NULL, /* 合并srv配置时的回调 */ ngx_http_login_create_loc_conf, /* 创建loc配置时的回调 - LOGIN模块的配置在location中,因此需要注册此函数 */ NULL /* 合并loc配置时的回调 */ };
NGINX中包含了很多模块,所有模块都是通过ngx_module_t类型,但每个模块拥有不同的属性。通过设置各模块不同的属性来控制各模块的行为。LOGIN模块的模块属性设置如下:
ngx_module_t ngx_http_login_module = { NGX_MODULE_V1, /* 含多个字段:一般使用此宏赋值 */ &ngx_http_login_module_ctx, /* 当前模块上下文回调 */ ngx_http_login_commands, /* 配置指令解析数组 */ NGX_HTTP_MODULE, /* 模块类型 */ NULL, /* 初始化master时的回调 */ NULL, /* 初始化module时的回调 */ NULL, /* 初始化工作进程时的回调 */ NULL, /* 初始化线程时的回调 */ NULL, /* 退出线程时的回调 */ NULL, /* 退出工作进程时的回调 */ NULL, /* 退出master时的回调 */ NGX_MODULE_V1_PADDING /* 含多个字段:一般使用此宏赋值 */ };
在代码3中的定义配置解析各阶段的回调设置了创建loc配置时的回调ngx_http_login_create_loc_conf(),其主要功能是为location配置分配内存空间,实现代码如下:
/****************************************************************************** **函数名称: ngx_http_login_create_loc_conf **功 能: 为LOGIN的配置结构分配内存空间 **输入参数: ** cf: 配置信息对象 **输出参数: NONE **返 回: 存储LOGIN配置的内存地址 **实现描述: **注意事项: **作 者: # Qifeng.zou # 2014.05.26 # ******************************************************************************/ static void *ngx_http_login_create_loc_conf(ngx_conf_t *cf) { ngx_http_login_loc_conf_t *llcf = NULL; llcf = ngx_palloc(cf->pool, sizeof(ngx_http_login_loc_conf_t)); if(NULL == llcf) { return NGX_CONF_ERROR; } memset(llcf, 0, sizeof(ngx_http_login_loc_conf_t)); return lcf; }
代码5 创建loc配置时的回调
在代码1中的定义配置项解析数组中设置了配置项check的回调ngx_http_login_check(),其主要功能解析配置项check,并设置解析后的处理函数,实现代码如下:
/****************************************************************************** **函数名称: ngx_http_login_check **功 能: 配置项check的解析处理回调 **输入参数: ** cf: 配置信息对象 ** cmd: 当前正在解析的配置项解析数组 ** conf: 自定义配置结构体ngx_http_login_loc_conf_t的地址 **输出参数: NONE **返 回: NGX_CONF_OK:Success Other:Failed **实现描述: **注意事项: **作 者: # Qifeng.zou # 2014.05.26 # ******************************************************************************/ static int ngx_http_login_check(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf = NULL; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_login_check_handler; ngx_conf_set_str_slot(cf, cmd, conf); return NGX_CONF_OK; }
在ngx_http_login_check()中对配置项check进行了解析处理,同时设置了解析后的处理函数:ngx_http_login_check_handler(),其主要功能是检测URL中的参数user和passwd的合法性,并返回最终的验证结果。实现代码如下:
/****************************************************************************** **函数名称: ngx_http_login_check_handler **功 能: 验证user和passwd的合法性 **输入参数: ** r: HTTP请求. **输出参数: NONE **返 回: 0:Success !0:Failed **实现描述: ** 1.必须是GET或HEAD请求 ** 2.获取LOGIN配置信息 ** 3.提取URL参数 ** 4.验证URL参数合法性 ** 5.发送应答数据 **注意事项: **作 者: # Qifeng.zou # 2014.05.26 # ******************************************************************************/ static int ngx_http_login_check_handler(ngx_http_request_t *r) { ngx_int_t ret = 0; ngx_str_t user, passwd, repmsg; ngx_http_login_loc_conf_t *llcf = NULL; /* 1. 必须是GET或HEAD请求 */ if(!(r->method &(NGX_HTTP_GET | NGX_HTTP_HEAD))) { return NGX_HTTP_NOT_ALLOWED; } ret = ngx_http_discard_request_body(r); /* 丢弃请求报文体 */ if(NGX_OK != ret) { return ret; } /* 2. 获取LOGIN配置信息 */ llcf = ngx_http_get_module_loc_conf(r, ngx_http_login_module); /* 3. 提取URL参数 在网址输入栏输入"localhost/query?user=qifeng&passwd=123456" 则:r->args = "user=qifeng&passwd=123456",因此提取网页参数只需对r->args进行解析即可. */ query_string(r, &user, "user"); /* 函数:query_string()的功能是从字串r->args中找到对应的参数及值(请自己实现) */ query_string(r, &passwd, "passwd"); /* 4. 验证URL参数合法性 */ if((user.len == llcf->user.len) && (0 == strcmp(llcf->user.data, user.data) && (passwd.len == llcf->passwd.len) && (0 == strcmp(llcf->user.data, passwd.data)) { ngx_str_set(&repmsg, "Success"); } else { ngx_str_set(&repmsg, "Failed"); } /* 5. 发送应答数据 */ return ngx_http_send_rep(r, &repmsg); } /****************************************************************************** **函数名称: ngx_http_send_rep **功 能: 发送应答数据 **输入参数: ** r: Http request. ** repmsg: 应答消息 **输出参数: NONE **返 回: 0:Success !0:Failed **实现描述: ** 1.发送应答头 ** 2.发送应答体 **注意事项: **作 者: # Qifeng.zou # 2014.05.26 # ******************************************************************************/ static int ngx_http_send_rep(ngx_http_request_t *r, const ngx_str_t *repmsg) { ngx_int_t ret = 0; ngx_buf_t *b = NULL; ngx_chain_t out; ngx_str_t type = ngx_string("text/plain"); /* 1.发送应答头 */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_type = type; r->headers_out.content_length_n = repmsg->len; ret = ngx_http_send_header(r); if((NGX_ERROR == ret) || (ret > NGX_OK) || (r->header_only)) { return ret; } /* 2.发送应答体 */ b = ngx_create_temp_buf(r->pool, repmsg->len); if(NULL == b) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_memcpy(b->pos, repmsg->data, repmsg->len); b->last = b->pos + repmsg->len; b->last_buf = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
Apache, IIS等其他服务器的第三方模块是通过动态链接库的方式加入到程序中,而NGINX的第三方模块需要加入NGINX源码工程一同编译。
NGINX提供了一种简单的方式将第三方开发的模块编译到NGINX中。首先,将源代码全部放入一个目录下,同时在该目录下创建一个名为config的文件(config的配置格式在4.1节描述);其次,在configure脚本执行时加入参数--add-module=PATH(PATH为第三方模块的源码路径)。
完成以上编辑工作后,最后的工作就是将编写的代码加入NGINX工程。其处理步骤如下:
#cd src/ext/login
#vim config
在config文件中输入如下内容:
ngx_addon_name=ngx_http_login_module HTTP_MODULES="$HTTP_MODULES ngx_http_login_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_login_module.c"
图2 变量值与等号之间有空格
#./configure --with-debug --add-module=src/ext/login
#make
#make install
输入“localhost/login?user=qifeng&passwd=123456”将会返回成功。如下图所示: