Skip to content

Latest commit

 

History

History
400 lines (306 loc) · 12.3 KB

form-input-nginx-module.md

File metadata and controls

400 lines (306 loc) · 12.3 KB

#form-input-nginx-module

##概述: 这是 Openresty 中一个用于处理 HTTP 请求的 POST 以及 PUT 方法,在协议头 Content-Type 是 application/x-www-form-urlencoded 的情况下,解析请求实体内容并按 nginx 变量存储的模块。

这个模块的开发和编译需要依赖 ngx_devel_kit 模块 (NDK)。

整个模块只有一个 c 文件 ngx_http_form_input_module.c,我们看看他里边的内容:

##指令集: 咱们看看指令结构体的定义:

	
	static ngx_command_t ngx_http_form_input_commands[] = {
	
	    { ngx_string("set_form_input"),
	      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
	      ngx_http_set_form_input_conf_handler,
	      NGX_HTTP_LOC_CONF_OFFSET,
	      0,
	      NULL },
	
	    { ngx_string("set_form_input_multi"),
	      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
	      ngx_http_set_form_input_conf_handler,
	      NGX_HTTP_LOC_CONF_OFFSET,
	      0,
	      NULL },
	
	      ngx_null_command
	};

从代码可以看出:

set_form_input 指令: 可以使用于 http ,server 以及 location 三种上下文中 可以使用1个或者2个参数 指令参数解析函数使用 ngx_http_set_form_input_conf_handler

set_form_input_multi 指令: 可以使用于 http ,server 以及 location 三种上下文中 可以使用1个或者2个参数 指令参数解析函数使用 ngx_http_set_form_input_conf_handler

##指令参数处理:

下面看看 ngx_http_set_form_input_conf_handler 的代码摘要:

static char *
ngx_http_set_form_input_conf_handler(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf)
{
    ndk_set_var_t                            filter;
    ngx_str_t                               *value, s;
    u_char                                  *p;

    ngx_http_form_input_used = 1;

    filter.type = NDK_SET_VAR_MULTI_VALUE;
    filter.size = 1;

    value = cf->args->elts;

    if ((value->len == sizeof("set_form_input_multi") - 1) &&
        ngx_strncmp(value->data, "set_form_input_multi", value->len) == 0)
    {
        dd("use ngx_http_form_input_multi");
        filter.func = (void *) ngx_http_set_form_input_multi;

    } else {
        filter.func = (void *) ngx_http_set_form_input;
    }

    value++;

    if (cf->args->nelts == 2) {
        p = value->data;
        p++;
        s.len = value->len - 1;
        s.data = p;

    } else if (cf->args->nelts == 3) {
        s.len = (value + 1)->len;
        s.data = (value + 1)->data;
    }

    return ndk_set_var_multi_value_core (cf, value,  &s, &filter);
}

函数中,关键点在于构建 filter 这个变量,按 指令名称 设置了不同的 filter.func, 并根据指令实参数量(cf->args->nelts),指定了 value 作为变量名,s 作为变量值,并调用 ndk_set_var_multi_value_core,这是一个 ndk 提供的函数,用于简化开发过程中在 http 请求中变量的的设置的流程。

那么 关键的代码就在于 ngx_http_set_form_input_multi 和 ngx_http_set_form_input 了,我们来看看他们的代码摘要:

static ngx_int_t
ngx_http_set_form_input(ngx_http_request_t *r, ngx_str_t *res,
    ngx_http_variable_value_t *v)
{
    ngx_http_form_input_ctx_t           *ctx;
    ngx_int_t                            rc;

    ngx_str_set(res, "");

    ctx = ngx_http_get_module_ctx(r, ngx_http_form_input_module);

    rc = ngx_http_form_input_arg(r, v->data, v->len, res, 0);

    return rc;
}

从函数参数列表可以看出,ngx_http_request_t *r 这个参数的出现就预示着 ngx_http_set_form_input 就是在http请求到来的时候执行的函数, ngx_http_set_form_input 和 ngx_http_set_form_input_multi 函数几乎一样, 都是使用 ngx_http_get_module_ctx 获取请求的上下文,然后调用 ngx_http_form_input_arg, 区别在于 ngx_http_form_input_arg 的最后一个传入的实参的值,我们再看看 ngx_http_form_input_arg 函数的代码摘要:

static ngx_int_t
ngx_http_form_input_arg(ngx_http_request_t *r, u_char *arg_name, size_t arg_len,
    ngx_str_t *value, ngx_flag_t multi)
{
    u_char              *p, *v, *last, *buf;
    ngx_chain_t         *cl;
    size_t               len = 0;
    ngx_array_t         *array = NULL;
    ngx_str_t           *s;
    ngx_buf_t           *b;

    /*
        multi 的真正分支代码,multi 的情况下会创建一个数组存储变量。
    */
    if (multi) {
        array = ngx_array_create(r->pool, 1, sizeof(ngx_str_t));
        if (array == NULL) {
            return NGX_ERROR;
        }
        value->data = (u_char *)array;
        value->len = sizeof(ngx_array_t);

    } else {
        ngx_str_set(value, "");
    }

    /*
        这里从 r->request_body->bufs 中获取 body 内容,并使用 buf 存储起来。
    */
    if (r->request_body->bufs->next != NULL) {
        len = 0;
        for (cl = r->request_body->bufs; cl; cl = cl->next) {
            b = cl->buf;
            len += b->last - b->pos;
        }

        buf = ngx_palloc(r->pool, len);
        if (buf == NULL) {
            return NGX_ERROR;
        }

        p = buf;
        last = p + len;

        for (cl = r->request_body->bufs; cl; cl = cl->next) {
            p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos);
        }
    } else {

        b = r->request_body->bufs->buf;
        if (ngx_buf_size(b) == 0) {
            return NGX_OK;
        }

        buf = b->pos;
        last = b->last;
    }

    /*
        这里对 buf 做解析,把 key 是 arg_name 数据块找到,并把后面的数据块 value 的值复制过来。 
    */
    for (p = buf; p < last; p++) {
        p = ngx_strlcasestrn(p, last - 1, arg_name, arg_len - 1);
        if (p == NULL) {
            return NGX_OK;
        }

        if ((p == buf || *(p - 1) == '&') && *(p + arg_len) == '=') {
            v = p + arg_len + 1;

            p = ngx_strlchr(v, last, '&');
            if (p == NULL) {
                p = last;
            }

            if (multi) {
                s = ngx_array_push(array);
                if (s == NULL) {
                    return NGX_ERROR;
                }
                s->data = v;
                s->len = p - v;

            } else {
                value->data = v;
                value->len = p - v;
                return NGX_OK;
            }
        }
    }

    return NGX_OK;
}

请求处理过程中的变量赋值操作就这样完成了,下面我们来看看模块的其他函数。

##模块定义

static ngx_http_module_t ngx_http_form_input_module_ctx = {
    NULL,                                   /* preconfiguration */
    ngx_http_form_input_init,               /* 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 */
};

ngx_module_t ngx_http_form_input_module = {
    NGX_MODULE_V1,
    &ngx_http_form_input_module_ctx,        /* module context */
    ngx_http_form_input_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 precess */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};

从 ngx_http_form_input_module 的定义可以看到,模块是 NGX_HTTP_MODULE 类型,并且定义了配置文件读取完毕后的处理函数 ngx_http_form_input_init。

##上下文与配置

ngx_http_form_input_module 已经没有配置结构体了,但有一个上下文结构体,在整个 request 处理过程中作为本模块的独立信息存储存在,这个在编写异步过程代码的时候发挥比较重要的作用,结构如下:

typedef struct {
    unsigned          done:1;
    unsigned          waiting_more_body:1;
} ngx_http_form_input_ctx_t;

有如下理解意思:

  • done: 表示该请求是否已经在本模块处理完成了,他会在 ngx_http_form_input_post_read 函数中设置为1。
  • waiting_more_body:表示该请求是否需要更多 body 的读取,它在 ngx_http_form_input_handler 中,调用读取请求实体的函数并且在函数返回NGX_AGAIN之后设置为1。
/* register a new rewrite phase handler */
static ngx_int_t
ngx_http_form_input_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);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);

    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_form_input_handler;

    return NGX_OK;
}

ngx_http_form_input_init 向 rewrite 阶段的处理函数列表添加了 ngx_http_form_input_handler。

ngx_http_form_input_handler 函数会在请求到来的时候,在rewrite 阶段调用,代码摘要如下:

static ngx_int_t
ngx_http_form_input_handler(ngx_http_request_t *r)
{
    ngx_http_form_input_ctx_t       *ctx;
    ngx_str_t                        value;
    ngx_int_t                        rc;

    /*
        获取 上下文 于 ctx
    */
    ctx = ngx_http_get_module_ctx(r, ngx_http_form_input_module);

    /*
        只处理 POST 和 PUT 方法的请求
    */
    if (r->method != NGX_HTTP_POST && r->method != NGX_HTTP_PUT) {
        return NGX_DECLINED;
    }

    /*
        协议头没定义 content-type 的请求也不处理。
    */
    if (r->headers_in.content_type == NULL
        || r->headers_in.content_type->value.data == NULL)
    {
        return NGX_DECLINED;
    }

    /*
        协议头 content-type 不是 "application/x-www-form-urlencoded" 的请求也不处理。
    */
    value = r->headers_in.content_type->value;
    if (value.len < form_urlencoded_type_len
        || ngx_strncasecmp(value.data, (u_char *) form_urlencoded_type,
                           form_urlencoded_type_len) != 0)
    {
        return NGX_DECLINED;
    }

    /*
        创建上下文 ngx_http_form_input_ctx_t,并保存于 r 的 ngx_http_form_input_module 块。
    */
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_form_input_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }
    ngx_http_set_ctx(r, ctx, ngx_http_form_input_module);

    /*
        发送网络读取命令,使用 ngx_http_form_input_post_read 处理。
    */
    rc = ngx_http_read_client_request_body(r, ngx_http_form_input_post_read);
    if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return rc;
    }

    if (rc == NGX_AGAIN) {
        ctx->waiting_more_body = 1;

        return NGX_DONE;
    }


    return NGX_DECLINED;
}

这样我们看看 ngx_http_form_input_post_read 干了什么事情。

static void
ngx_http_form_input_post_read(ngx_http_request_t *r)
{
    ngx_http_form_input_ctx_t     *ctx;
    ctx = ngx_http_get_module_ctx(r, ngx_http_form_input_module);

    ctx->done = 1;
    r->main->count--;

    if (ctx->waiting_more_body) {
        ctx->waiting_more_body = 0;

        ngx_http_core_run_phases(r);
    }
}

可见这个函数也不做啥,判断有新数据可以读取,即进入这个函数就调用 ngx_http_core_run_phases 继续下一个 本阶段或者下阶段的处理函数。

#总结

模块具体流程如下:

配置文件读取阶段处理两个指令 set_form_input 和 set_form_input_multi,并设置 ngx_http_set_form_input_multi 作为变量函数。 配置文件读取结束阶段往 rewrite 阶段处理函数列表添加了模块自己定义的 ngx_http_form_input_handler。 请求到来阶段,在 rewrite 阶段执行 ngx_http_form_input_handler,用于判断请求是否需要本模块处理。 在 rewrite 阶段的下一处理回调函数中会有通过 ndk_set_var_multi_value_core 函数设置的回调,依据 ngx_http_set_form_input 或者 ngx_http_set_form_input_multi 读取 body 内容并解析赋值于指定的 nginx 变量中。