首页 > 代码库 > NGINX模块开发 之 验证URL参数

NGINX模块开发 之 验证URL参数

  • 作者:邹祁峰
  • 邮箱:Qifeng.zou.job@gmail.com
  • 博客:http://blog.csdn.net/qifengzou
  • 日期:2014.05.26 16:45
  • 转载请注明来自"祁峰"的CSDN博客


    要求在浏览器地址栏中输入"localhost/login?user=qifeng&passwd=123456",并在浏览器上显示验证结果(Success 或 Failed)。以下是在NGINX中添加一个LOGIN模块的整个处理过程。


1 修改配置

  修改配置文件nginx.conf,在其中增加如下配置信息:


图1 修改配置

(注意:将passwd的值"abcd"改为“123456”)


2 编写代码

2.1 创建源码目录

    在NGINX源码目录src下新建ext文件夹,src/ext用于存放所有扩展模块代码,src/ext/login则用于存放LOGIN模块的代码.

    #mkdir -p src/ext/login

    #cd src/ext/login

    #vim ngx_http_login_module.c


2.2 定义配置结构

  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;

代码1 定义配置结构

2.3 设置解析数组

  从图1中可知:配置项有user、passwd和check.解析这些配置项的配置项的解析是通过配置ngx_command_t数组,配置项解析数组如下:(命名规则:ngx_http_模块名_commands)

/* 配置项解析数组:配置指令是通过此数组依次解析的. */
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 配置项解析数组


2.4 设置配置回调

    定义模块配置解析过程中各阶段的处理回调:(命名规则: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配置时的回调 */
};
代码3 配置解析各阶段回调

  绝大多数handler只需要设置最后面回调函数,即:设置ngx_http_xxx_create_loc_conf和ngx_http_xxx_merge_loc_conf.前者用于特定location的内存分配,而后者用来设置默认值以及合并继承过来的配置值,同时往往还负责配置值的合法性验证,如果不合法,则退出后续处理。

2.5 设置模块属性

  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               /* 含多个字段:一般使用此宏赋值 */
};

代码4 设置模块属性

2.6 编写函数代码

  在代码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;
}
代码6 CHECK配置项解析处理

  在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);
}
代码7 合法性验证

3. 编译工程

3.1 编辑编译配置

  完成以上编辑工作后,最后的工作就是将编写的代码加入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"
代码8 编译配置

  注意:以上config文件中的等号(=)前后不能有空格,否则执行./configure --add-module=src/ext/login时,并不能将login模块加入到工程编译环境中.

图2 变量值与等号之间有空格


3.2 加入编译工程

  完成config的编辑后,LOGIN模块还没有加入到编译工程中。NGINX的编译脚本比较复杂,功能也十分强大,在编译之前必须告知NGINX编译脚本到指定的路径去添加LOGIN模块:

    #./configure --with-debug --add-module=src/ext/login

    #make

    #make install


3.3 最终测试结果

  在浏览器地址栏中输入:localhost/login?user=qifeng&passwd=123456,将会返回成功;输入localhost/login?user=zhangsan&passwd=123456,将会返回失败。如下图所示:


图3 验证成功



图4 验证失败