nginx 变量

nginx的变量是怎么work的?


现在考虑一个需求,要写一个第三方模块,需要一个变量,对应着每个request,这个变量放在哪里呢?怎么获取值,怎么传递给客户端,传给后端服务器呢?


那么首先需要了解一下nginx中变量的组织结构,nginx变量可以归为两类:

类型1:nginx内部变量

    nginx核心变量:ngx_http_core_variables 变量集

    nginx各模块有自己的变量:比如ngx_http_browser_module 模块的 ngx_http_browsers 变量集


类型2:nginx的配置文件中,我们会用到很多变量。

    比如:log_format  main  '$remote_addr';  中的$remote_addr'

    比如:set $name hello;中的$name 

    比如:limit_conn_zone $binary_remote_addr zone=ip_zone:10m; 中的$binary_remote_addr

    比如:set $memcached_key test; 中的$memcached_key

  

一:先看一下相关的变量结构:

上下文的变量结构,包括索引变量和hash变量以及hash表,这个结构在初始化完毕后就已经确定了:

typedef struct {

    ngx_hash_t                 variables_hash;  //根据variables_keys建立的hash表

    ngx_array_t                variables;      //可以索引到的变量数组

    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; //hash的变量数组

} ngx_http_core_main_conf_t;

每个请求拥有自己的索引变量,索引变量的值缓存在下面的结构中。

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;//变量值是否是有效的
    unsigned    no_cacheable:1;.//是否能够缓存,缓存的变量直接返回data即可
    unsigned    not_found:1;//变量是否存在
    unsigned    escape:1;

    u_char     *data;//变量值
} ngx_variable_value_t;

上下文结构变量中的variables的元素结构,在变量初始化完毕后,是不变的。表示了一个变量的特性如flags以及获取方式如get_handler

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;
    ngx_uint_t                    flags;//变量类型
    ngx_uint_t                    index;
};

flags表示变量的类型,变量类型一共有四种:

NGX_HTTP_VAR_CHANGEABLE:表示此变量不能重复定义,重复定义会报错

NGX_HTTP_VAR_NOCACHEABLE:表示索引在variables中的索引变量不能缓存,每次必须重新获得值

NGX_HTTP_VAR_NOHASH:表示在variables_keys中的变量事不可以hash,初始化赋值为NULL

NGX_HTTP_VAR_INDEXED:表示在variables_keys中的变量是可以通过索引获得的


二:再看一下和变量相关的函数:

函数源码就不展示了,通过下面的解释看源码,难度不会太大。

1)添加变量函数:

添加变量函数在变量进行初始化的时候使用,其中variables_keys是针对一个上下文的,而variables最后实质 是针对每个请求的

ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);//添加变量到variables_keys
ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);//添加变量到variables

注:通过ngx_http_get_variable_index添加到variables的变量,必须已经添加到variables_keys中去了,除非是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的变量。

2)获取变量函数:

ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index);
ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r,ngx_uint_t index);
ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r,ngx_str_t *name, ngx_uint_t key);

注:

点1:索引了变量可以通过 ngx_http_get_indexed_variable或ngx_http_get_flushed_variable来获取

        其中ngx_http_get_flushed_variable获取索引变量中可以缓存的变量

        ngx_http_get_indexed_variable 可以获取索引变量中的变量

        每个请求特有的变量都是索引变量或者特殊变量

点2:ngx_http_get_variable 可以获取所有hash过的变量

         这个函数首先去上下文的hash表里面找,如果找到了并且发现变量是被index了,那么就调用ngx_http_get_flushed_variable,如果此变量可以缓存,返回变量值。不可以缓存,则继续调用ngx_http_get_indexed_variable 获取值。如果变量没有被index,那么就直接调用gethandler获取值。

         如果此变量没有在hash表中找到,则判断是不是特殊变量,比如http_*等,然后通过handler去获取值。

        

三:接下来看一下变量是如何初始化的:

nginx在ngx_http_block函数中完成了对所有变量初始化函数的调用,看一下ngx_http_block变量的三个阶段的相关代码。


static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ......

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_loc_conf) {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

    pcf = *cf;
    cf->ctx = ctx;
     

    //阶段1:preconfiguration
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    /* parse inside the http{} block */
    //阶段2:ngx_conf_parse
    cf->module_type = NGX_HTTP_MODULE;
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
    rv = ngx_conf_parse(cf, NULL);

    if (rv != NGX_CONF_OK) {
        goto failed;
    }

    /*
     * init http{} main_conf's, merge the server{}s' srv_conf's
     * and its location{}s' loc_conf's
     */

    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        /* init http{} main_conf's */

        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }


    /* create location trees */

    for (s = 0; s < cmcf->servers.nelts; s++) {

        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];

        if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }


    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
    //阶段3:ngx_http_variables_init_vars
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    /*
     * http{}'s cf->ctx was needed while the configuration merging
     * and in postconfiguration process
     */

    *cf = pcf;


    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


    /* optimize the lists of ports, addresses and server names */

    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;

}

1、阶段1,对各模块进行preconfiguration:

而正是这些preconfiguration将上面类型1中的变量都注册到 variables_keys 中去了,并会根据variables_keys形成变量的hash表()

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

在调用各个模块的preconfiguration时候,会将当前模块的模块变量注册到ngx_http_core_main_conf_t结构的variables_keys变量数组中去。


有变量注册的模块有:

ngx_http_core_module:ngx_http_core_preconfiguration

ngx_http_browser_module:ngx_http_browser_add_variable

ngx_http_fastcgi_module:ngx_http_fastcgi_add_variables

ngx_http_geoip_module:ngx_http_geoip_add_variables

ngx_http_gzip_filter_module:ngx_http_gzip_add_variables

ngx_http_perl_module:ngx_http_perl_preconfiguration

ngx_http_proxy_module:ngx_http_proxy_add_variables

ngx_http_upstream_module:ngx_http_upstream_add_variables

ngx_http_userid_filter_module:ngx_http_userid_add_variables

除了ngx_http_core_module,其余模块都是通过调用ngx_http_add_variable函数,这个函数封装了底层的函数是 ngx_hash_add_key ,这个就是所有模块直接或者间接调用往variables_keys添加hash变量的。ngx_http_core_module直接调用ngx_hash_add_key添加hash变量。

那么当自己开发模块或者功能时候,需要自己定义变量的话,可以在自己的模块的preconfiguration中进行,
或者到ngx_http_core_variables中定义。在此阶段定义的变量,才能实现hash。


2、阶段2,解析配置文件ngx_conf_parse

解析配置文件的时候,会遇到类型二中描述的变量。这些变量都是随着特定cmd出现的,cmd都对应着相应的handler。一边解析conf文件,一边执行handler,

每个带有变量的handler都会调用 函数:ngx_http_get_variable_index。这个函数将会为 variables赋值。

其实这个行为就是将配置文件中的变量,全都可以通过索引快速访问。


注意:当然在阶段1中的函数里或者其他地方调用ngx_http_get_variable_index,都会将变量放到了variables中。

当然在阶段2中的函数也可以调用ngx_http_add_variable,将变量放到variables_keys中去。

也就是variables_keys是包含variables中的所有变量,除非variables变量是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的。


3、阶段3,调用函数ngx_http_variables_init_vars,这个函数主要是检查http{}上下文的中的ngx_http_core_main_conf_t->variables是否存在于variables_keys中,并且 将variables_keys赋值到variables。对于没有包含在variables_keys变量中的variables变量,如果是以"http_","sent_http_","upstream_http_","cookie_","arg_"这几个字符串开头的,则不会报错,因为这个或存在于header或者cookie或者请求行中,并且可以通过通用函数获得。否则会报错,因为不知道这个变量存在哪里,怎么获取值。

check完变量,将根据variables_keys这个变量数组建立变量hash表即variables_hash。hash表在此成型,不可更改了。为嘛建立hash表?这不快吗!!!具体nginx的hash表,完全需要单独一篇文章介绍,在此篇幅有限。


经历这三个阶段之后,ngx_http_core_main_conf_t变量的所有准备工作完全ok了。


四:接下来看一下每个请求是怎么拥有自己的变量的。

每个请求在初始化的时候,即调用函数ngx_http_init_request,会初始化自己的变量数组,

而这个变量数组是和请求上下文中ngx_http_core_main_conf_t->variables变量数组大小一样,并且后续也是保持一一对应的,

只是r->variables缓存了这次请求中所需要的索引变量的值,如果值无效,则可以通过对应的ngx_http_core_main_conf_t->variables的gethandler来获取值。

说白了,也就是r->variables缓存了这次请求所需的变量值,而不用实时求值了,无非就是一个效率的提高。

r_.variavbles结构:

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;//变量值是否是有效的
    unsigned    no_cacheable:1;.//是否能够缓存,缓存的变量直接返回data即可
    unsigned    not_found:1;//变量是否存在
    unsigned    escape:1;

    u_char     *data;//变量值
} ngx_variable_value_t;

对应着ngx_http_core_main_conf_t->variables结构:

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;
    ngx_uint_t                    flags;//变量类型
    ngx_uint_t                    index;
};

下面的这个结构是不变的,确定的。

而上面的结构式跟随request改变的。


the end ————

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值