• 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;
    }
    
    
  • 相关阅读:
    Javascript正则表达式详解转载
    转载Sqlserver2005 存储过程分页
    转载手把手教你用C#打包应用程序
    学习内容
    用C#实现将HTML文件转换为CHM文件(转)
    C# Windows服务添加安装程序
    sql 2008评估期已过有关如何升级,企业试用版到期,升级为企业版+sql2008破解
    iis不能启动是什么原因?错误提示:“提示服务器没有及时相应启动或控制请求”
    .NET 获取数据库中所有表名的方法
    如何获取Dynamics当前登录的用户的GUID,进而获取用户的信息
  • 原文地址:https://www.cnblogs.com/atskyline/p/8732833.html
Copyright © 2020-2023  润新知