nginx开发_ngx_http_script源码解析

功能简介

nginx中有很多配置项支持以变量的形式存在,在运行时根据实时值进行处理。例如如下配置:

location / {
    sub_filter '<a href="http://127.0.0.1:8080/'  '<a href="https://$host/';
}

使用到了host变量,每个请求的host变量内容不同,运行的结果也不同。

解析带有变量的配置项,并在运行时求值,就是用通过ngx_http_script.c文件实现的。

使用方法

使用方式相对简单,需要了解2个结构体和2个函数。

typedef struct {
    ngx_conf_t                 *cf;
    ngx_str_t                  *value;
    ngx_http_complex_value_t   *complex_value;
	//...
} ngx_http_compile_complex_value_t; //代码中常用ccv作为变量名

typedef struct {
    //...
} ngx_http_complex_value_t;

//编译解析带变量的配置,常在配置阶段调用
//ccv->cf和cc->value(带变量配置的字符串)为入参
//ccv->complex_value 为出参,常保存在xxx_conf_t中。
ngx_int_t ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv);

//将带变量的配置项求值,常在运行阶段调用
//r,val为入参,val通过ngx_http_compile_complex_value()获得
//value为出参,即求值后的具体值。
ngx_int_t ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, ngx_str_t *value);

使用示例

static char *
ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //...
    ccv.cf = cf;
    ccv.value = &value[1];
    ccv.complex_value = &pair->match;//编译解析后的变量记录在match中
    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
    //...
}

static ngx_int_t
ngx_http_sub_header_filter(ngx_http_request_t *r)
{
    //...
    m = &matches[j].match; //m为具体的取值
    if (ngx_http_complex_value(r, &pairs[i].match, m) != NGX_OK) {
    	return NGX_ERROR;
    }
    //...
}

数据结构

整体上功能比较简单,但在ngx_http_script中设计得比较复杂,个人觉得存在过设计和部分设计冗余的问题。我主要记录核心的东西。

typedef struct {
    ngx_conf_t                 *cf;
    ngx_str_t                  *value;
    ngx_http_complex_value_t   *complex_value;
} ngx_http_compile_complex_value_t;

typedef struct {
    ngx_str_t                   value;  //编译前的值,与ccv->value一致
    ngx_uint_t                 *flushes;
    void                       *lengths;
    void                       *values;
    //这3个指针是核心,分别指向了计算变量和长度的函数指针。flushes记录了变量的索引。
} ngx_http_complex_value_t;

typedef struct {
    u_char                     *ip;
    u_char                     *pos;
    //....
} ngx_http_script_engine_t;
//用于求值执行的结构体
//ip是一些列求值函数,常来自cv->values
//pos是存放求值结果的指针

typedef struct {
    ngx_conf_t                 *cf;
    ngx_str_t                  *source; ////编译前的值,与ccv->value一致
    ngx_array_t               **flushes;
    ngx_array_t               **lengths;
    ngx_array_t               **values;
    //...
} ngx_http_script_compile_t;
//用于编译解释时的结构体,几个核心指针与cv中的一致。

这个文件中涉及的数据结构还有很多其他的字段,都只在比较少的场景用使用到。抓住以上几个核心字段,代码理解起来就不困难了。

内部算法

算法没有高深的东西,就是把字符串变成一堆函数指针,再求值时依次执行函数。

解析过程中可以关注对变量的解析方式,核心代码如下:

主要处理了$var${var}的场景。关于?做参数的使用方式,实际运用中比较少。为了看方便,我注释掉部分分支的处理

ngx_int_t
ngx_http_script_compile(ngx_http_script_compile_t *sc)
{
    u_char       ch;
    ngx_str_t    name;
    ngx_uint_t   i, bracket;

    //...
    for (i = 0; i < sc->source->len; /* void */ ) {
        name.len = 0;
        if (sc->source->data[i] == '$') {
            if (++i == sc->source->len) {
                goto invalid_variable;
            }
            if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
                //...
            }

            if (sc->source->data[i] == '{') {
                bracket = 1;
                if (++i == sc->source->len) {
                    goto invalid_variable;
                }
                name.data = &sc->source->data[i];
            } else {
                bracket = 0;
                name.data = &sc->source->data[i];
            }

            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
                ch = sc->source->data[i];
                if (ch == '}' && bracket) {
                    i++;
                    bracket = 0;
                    break;
                }

                if ((ch >= 'A' && ch <= 'Z')
                    || (ch >= 'a' && ch <= 'z')
                    || (ch >= '0' && ch <= '9')
                    || ch == '_')
                {
                    continue;
                }
                break;
            }

            if (bracket) {
                //...
                return NGX_ERROR;
            }
            if (name.len == 0) {
                goto invalid_variable;
            }

            sc->variables++;

            if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
                return NGX_ERROR;
            }
            continue;
        }
		//...
    }
    return ngx_http_script_done(sc);

invalid_variable:
    ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
    return NGX_ERROR;
}

求值部分带代码就更简单了,合适就是执行一圈cv->lengts和cv->values

ngx_int_t
ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val,
    ngx_str_t *value)
{
    size_t                        len;
    ngx_http_script_code_pt       code;
    ngx_http_script_len_code_pt   lcode;
    ngx_http_script_engine_t      e;

    //...
    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
    e.ip = val->lengths;
    e.request = r;
    e.flushed = 1;
    len = 0;
    while (*(uintptr_t *) e.ip) {
        lcode = *(ngx_http_script_len_code_pt *) e.ip;
        len += lcode(&e);
    }

    value->len = len;
    value->data = ngx_pnalloc(r->pool, len);
    if (value->data == NULL) {
        return NGX_ERROR;
    }
    e.ip = val->values;
    e.pos = value->data;
    e.buf = *value;
    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_script_code_pt *) e.ip;
        code((ngx_http_script_engine_t *) &e);
    }
    *value = e.buf;

    return NGX_OK;
}

具体执行的函数一般以xxx_var_code和xxx_var_len_code命名,我贴一段变量的代码

void
ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
{
    u_char                      *p;
    ngx_http_variable_value_t   *value;
    ngx_http_script_var_code_t  *code;

    code = (ngx_http_script_var_code_t *) e->ip;
    e->ip += sizeof(ngx_http_script_var_code_t);
    if (!e->skip) {
        if (e->flushed) {
            value = ngx_http_get_indexed_variable(e->request, code->index);
        } else {
            value = ngx_http_get_flushed_variable(e->request, code->index);
        }
        if (value && !value->not_found) {
            p = e->pos;
            e->pos = ngx_copy(p, value->data, value->len);
            //...
        }
    }
}

size_t
ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
{
    ngx_http_variable_value_t   *value;
    ngx_http_script_var_code_t  *code;

    code = (ngx_http_script_var_code_t *) e->ip;
    e->ip += sizeof(ngx_http_script_var_code_t);
    if (e->flushed) {
        value = ngx_http_get_indexed_variable(e->request, code->index);
    } else {
        value = ngx_http_get_flushed_variable(e->request, code->index);
    }
    if (value && !value->not_found) {
        return value->len;
    }
    return 0;
}

posted @ 2018-04-07 11:57  atskyline  阅读(1557)  评论(0编辑  收藏  举报