• Nginx变量的实现机制


    Nginx有两种定义变量的方式,一种是在配置文件中使用set指令(由rewrite模块提供支持),另一种是在模块内定义变量。

    变量相关结构体:

    struct ngx_http_variable_s {
      ngx_str_t name;        /* must be first to build the hash */
      ngx_http_set_variable_pt set_handler;
      ngx_http_get_variable_pt get_handler;
      uintptr_t data;      // get和set的回调参数
      ngx_uint_t flags;    // 变量属性
      ngx_uint_t index;    // 变量索引
    };
    
    typedef struct {
    
      unsigned len:28;        // 变量长度
    
      unsigned valid:1;
      unsigned no_cacheable:1;
      unsigned not_found:1;
      unsigned escape:1;
    
      u_char *data;          // 变量值(只支持字符串类型) 
    } ngx_variable_value_t;
    
    
    typedef struct {
      ngx_array_t servers;        /* ngx_http_core_srv_conf_t */
    
      ngx_http_phase_engine_t phase_engine;
    
      ngx_hash_t headers_in_hash;
    
      ngx_hash_t variables_hash;
    
      ngx_array_t variables;       /* ngx_http_variable_t */
      ngx_uint_t ncaptures;
    
      ngx_uint_t server_names_hash_max_size;
      ngx_uint_t server_names_hash_bucket_size;
    
      ngx_uint_t variables_hash_max_size;
      ngx_uint_t variables_hash_bucket_size;
    
      ngx_hash_keys_arrays_t *variables_keys;
    
      ngx_array_t *ports;
    
      ngx_uint_t try_files;        /* unsigned try_files:1 */
    
      ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1]; 
    } ngx_http_core_main_conf_t;

    在Nginx中的所有变量都是与HTTP相关的,并且基本上是同时保存在两个数据结构中:

    一个是hash表(cmcf->variables_hash);另一个是数组(cmcf->variables)。

    一些特殊的变量,比如arg_xxx、cookie_xxx,这些变量的名字是不确定的,而且它们是只读的,这些变量没有hash,只有索引。

    解析配置阶段:

    1、HTTP核心模块提供的内置变量

    ngx_http_core_module模块在preconfiguration阶段的回调:ngx_http_core_preconfiguration

    该函数只有一句话,调用ngx_http_variables_add_core_vars;

    ngx_http_core_variables 数组 列出了所有的http core模块内置变量。

    ngx_http_variables_add_core_vars 函数会把ngx_http_core_variables 数组中列出的内置变量加入到cmcf->variables_keys表中。

    ngx_int_t
    ngx_http_variables_add_core_vars(ngx_conf_t *cf) 
    {
        ngx_int_t                   rc;  
        ngx_http_variable_t        *cv, *v;
        ngx_http_core_main_conf_t  *cmcf;
    
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    
        cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
                                           sizeof(ngx_hash_keys_arrays_t));
        if (cmcf->variables_keys == NULL) {
            return NGX_ERROR;
        }    
    
        cmcf->variables_keys->pool = cf->pool;
        cmcf->variables_keys->temp_pool = cf->pool;
    
        if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
            != NGX_OK)
        {    
            return NGX_ERROR;
        }    
    
        for (cv = ngx_http_core_variables; cv->name.len; cv++) {
            v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
            if (v == NULL) {
                return NGX_ERROR;
            }    
    
            *v = *cv; 
    
            rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v,
                                  NGX_HASH_READONLY_KEY);
    
            if (rc == NGX_OK) {
                continue;
            }    
    
            if (rc == NGX_BUSY) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "conflicting variable name "%V"", &v->name);
            }    
    
            return NGX_ERROR;
        }    
    
        return NGX_OK;
    }

    2、解析其它模块阶段:

    除了core模块外,其它模块也会把自身支持的内部变量添加到cmcf->variables_keys内。

    以proxy模块为例,在preconfiguration阶段的回调函数如下:

    static ngx_int_t
    ngx_http_proxy_add_variables(ngx_conf_t *cf) 
    {
        ngx_http_variable_t  *var, *v;
    
        for (v = ngx_http_proxy_vars; v->name.len; v++) {
            var = ngx_http_add_variable(cf, &v->name, v->flags);
            if (var == NULL) {
                return NGX_ERROR;
            }    
    
            var->get_handler = v->get_handler;
            var->data = v->data;
        }    
    
        return NGX_OK;
    }

    这里会通过ngx_http_add_variable函数将模块内定义的变量添加到cmcf->variables_keys。

    注意,该函数仅是将变量的"name"添加进全局的hash key链表中,变量的get/set回调以及data字段,需要在这个函数返回之后进行显式的设置(由模块自己设置)。其中,data字段指向存放变量值的地方,通常是ngx_http_request_t变量r中的某个字段。

    ngx_http_variable_t *
    ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
    {
        ngx_int_t                   rc;
        ngx_uint_t                  i;
        ngx_hash_key_t             *key;
        ngx_http_variable_t        *v;
        ngx_http_core_main_conf_t  *cmcf;
    
        if (name->len == 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid variable name "$"");
            return NULL;
        }
    
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    
        key = cmcf->variables_keys->keys.elts;
        for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
            if (name->len != key[i].key.len
                || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
            {
                continue;
            }
    
            v = key[i].value;
    
            if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "the duplicate "%V" variable", name);
                return NULL;
            }
    
            return v;
        }
    
        v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
        if (v == NULL) {
            return NULL;
        }
    
        v->name.len = name->len;
        v->name.data = ngx_pnalloc(cf->pool, name->len);
        if (v->name.data == NULL) {
            return NULL;
        }
    
        ngx_strlow(v->name.data, name->data, name->len);
    
        v->set_handler = NULL;
        v->get_handler = NULL;
        v->data = 0;
        v->flags = flags;
        v->index = 0;
    
        rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
    
        if (rc == NGX_ERROR) {
            return NULL;
        }
    
        if (rc == NGX_BUSY) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "conflicting variable name "%V"", name);
            return NULL;
        }
    
        return v;
    }

    ngx_http_add_variable函数flags参数的几个取值:

    a) NGX_HTTP_VAR_CHANGEABLE,表示该变量可重复添加,后添加的会覆盖前面的同名变量;

    b) NGX_HTTP_VAR_NOCACHEABLE,表示该变量可不可缓存,即每次获取变量值的时候,都需要重新调用get handler;

    用户在nginx.conf使用set指令定义的变量也会被写到cmcf->variables_keys内。

    并且,会将set定义的变量写入到cmcf->variables中,这是通过ngx_http_get_variable_index实现的:

    ngx_int_t
    ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
    {
        ngx_uint_t                  i;   
        ngx_http_variable_t        *v;  
        ngx_http_core_main_conf_t  *cmcf;
    
        if (name->len == 0) { 
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid variable name "$"");
            return NGX_ERROR;
        }    
    
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    
        v = cmcf->variables.elts;
    
        if (v == NULL) {
            if (ngx_array_init(&cmcf->variables, cf->pool, 4,
                               sizeof(ngx_http_variable_t))
                != NGX_OK)
            {    
                return NGX_ERROR;
            }    
    
        } else {
            for (i = 0; i < cmcf->variables.nelts; i++) {
                if (name->len != v[i].name.len
                    || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
                {    
                    continue;
                }    
    
                return i;
            }    
        }    
    
        v = ngx_array_push(&cmcf->variables);
        if (v == NULL) {
            return NGX_ERROR;
        }    
    
        v->name.len = name->len;
        v->name.data = ngx_pnalloc(cf->pool, name->len);
        if (v->name.data == NULL) {
            return NGX_ERROR;
        }    
    
        ngx_strlow(v->name.data, name->data, name->len);
    
        v->set_handler = NULL;
        v->get_handler = NULL;
        v->data = 0;
        v->flags = 0;
        v->index = cmcf->variables.nelts - 1;
    
        return v->index;
    }

    这个函数是在cmcf->variables数组中查找指定变量,并返回该变量在数组中的索引。如果查找的变量不存在,就在数组末尾追加一个。

    另外,有些变量虽然没有出现在nginx.conf中,但也可以加入到cmcf->variables中,这部分要由模块自己实现。

    3、初始化变量

    在解析完HTTP所有模块之后,会调用ngx_http_variables_init_vars来初始化所有的变量。

    Nginx定义的变量很多(存在于cmcf->variables_keys中),但真正会用到的变量可能很少(存在于cmcf->variables中)。

    ngx_http_variables_init_vars函数的意义在于:

    a)遍历cmcf->variables中所有已使用的变量,初始化其get_handler和data两个成员,并指定变量索引(即在数组中的下标);

    b)从cmcf->variables_keys表来初始化cmcf->variables_hash表。

    这个函数其实还有合法性检查的作用,它会遍历cmcf->variables,并从cmcf->variables_keys表来查找对应的变量进行赋值。

     

    请求处理阶段:

    为每个请求分配一个数组,存放所有已使用的变量(cmcf->variables数组的个数决定)。

    ngx_http_request_t *
    ngx_http_create_request(ngx_connection_t *c)
    {
    
    ....
    
      cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    
      r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts * sizeof(ngx_http_variable_value_t));
      if (r->variables == NULL) {
        ngx_destroy_pool(r->pool);
        return NULL;
    
      }
    
    ....
    }

    注意,这里只是分配好了存放变量的空间,并没有真的去为变量赋值;

    Nginx采用的是Lazy Evaluation技术,只有真正去读它的值时,nginx才会临时执行一段代码先给它赋值,然后将结果返回。

    从上面可以看出,Nginx的变量是为每个请求都保存一个副本,所以同一个变量在不同的请求中的值是可以不同的,这有点类似于请求的上下文request->ctx;

    如何获取变量值

    之前说过Nginx变量采用的是lazy evalution技术,如果我们在代码中不人为获取某个变量的值,这个变量其实是空的,这样做可以避免去解析不会被使用的变量。

    现在来看看ngx_http_variable_t结构中get handler和set handler的函数原型:

    typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
    typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);

    参数r是当前请求,v是变量值(ngx_variable_value_t结构),data是传入回调函数的参数,它本身位于ngx_http_variable_t结构中。

    get handler的意义就是填充参数v,使其获得相应的值(保存在v->data中)。

    set handler只在使用set配置指令构造脚本引擎时才会用到,例如:set $var $request_uri。配置解析完成之后,set handler就没有用了。

    根据索引获取变量,前提是变量已经存在于cmcf->variables中。

    ngx_http_variable_value_t *
    ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index)
    {
        ngx_http_variable_t        *v;  
        ngx_http_core_main_conf_t  *cmcf;
    
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    
        if (cmcf->variables.nelts <= index) {
            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                          "unknown variable index: %d", index);
            return NULL;
        }    
    
        if (r->variables[index].not_found || r->variables[index].valid) {
            return &r->variables[index];
        }    
    
        v = cmcf->variables.elts;
    
        if (v[index].get_handler(r, &r->variables[index], v[index].data)
            == NGX_OK)
        {    
            if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
                r->variables[index].no_cacheable = 1; 
            }    
    
            return &r->variables[index];
        }    
    
        r->variables[index].valid = 0; 
        r->variables[index].not_found = 1; 
    
        return NULL;
    }

    获取变量的其它2种方式:

    ngx_http_variable_value_t * ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)

    ngx_http_variable_value_t * ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index)

    其中:

    ngx_http_get_flushed_variable与ngx_http_get_indexed_variable类似,不过它会处理NGX_HTTP_VAR_NOCACHEABLE这个标记。

    ngx_http_get_variable则是用来获取没有索引的变量,变量存在于cmcf->variables_hash中。

  • 相关阅读:
    角色转变
    参加Google™ Code Jam 中国编程挑战赛(1)
    开始学习研究Infragistics NetAdvantage For ASP.NET
    建立资料库
    参加Google™ Code Jam 中国编程挑战赛(2)
    极度郁闷,上网时间被限定!
    在Win7下Visual Studio如何使用IIS进行调试
    jQuery 第二课:操作包装集元素
    将DataTable导出到Excel
    CSS选择符
  • 原文地址:https://www.cnblogs.com/chenny7/p/3580322.html
Copyright © 2020-2023  润新知