了解Nginx的变量机制就主要有三方面,第一点是变量获取的接口,第二点是对配置文件中变量的解析,第三点是在使用变量时的调用过程,我们来一点点看(这里只举一个变量的例子):

我们来看模块ngx_http_proxy_module中的变量$host的使用过程

 

首先第一点,变量的声明与接口调用:

见文件ngx_http_variables.c中有声明static ngx_http_variable_t  ngx_http_core_variables[]即http处理相关的核心变量,另外每一个模块也会定义一些自己用的变量。先来看一些ngx_http_variable_t的定义:



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;/*索引,但多是0*/ }; 



 

我们来看其中的一项:

{ ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 },

这项是 $host 变量的定义,只指定了name和get_hander,那我们就直接来看一下这个handler:

static ngx_int_t
ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{ //在调用这个handler时需要传递http request对象,以及v用来存放结果,data即在变量定义时中预留的。
    ngx_http_core_srv_conf_t  *cscf;

    //如果HTTP请求头中包含Host头或者URL中包含Host部分(看接受到http请求之后的解析过程即可了解server的由来),
    //则设置为该值
    if (r->headers_in.server.len) {
        v->len = r->headers_in.server.len;
        v->data = r->headers_in.server.data;

    } else {
        cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
     
        //否则用配置中的值,不知此值从何处获取
        v->len = cscf->server_name.len;
        v->data = cscf->server_name.data;
    }

    //另外设置一些属性,有效以及值不可缓存
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;

    return NGX_OK;
}

 

 第一点基本上就到这里,继续来看第二点变量的解析过程,即当在配置文件中使用变量时,nginx启动时会如何解析该变量以及做哪些处理方便以后使用?这里我们假设在ngx_http_proxy_module模块中配置proxy_pass命令时使用变量$host,然后来看其处理过程。

假设Nginx有如下配置;

location / {
           proxy_cache cache_one;
           proxy_cache_valid 200 5d;
           proxy_cache_key $host$uri;

           resolver 8.8.8.8;
           proxy_pass http://$host;
           proxy_set_header Host $host;
}

 

我们来看ngx_http_proxy_module.c文件中对命令proxy_pass的处理即可。类似变量定义,很多模块都会定义该模块使用的命令,这些命令是可以在配置文件中使用的,类似上面配置中的proxy_pass就是ngx_http_proxy_module模块的一个命令,在ngx_http_proxy_module模块中定义了一些命令,如:

static ngx_command_t  ngx_http_proxy_commands[] = {
    { ngx_string("proxy_pass"),
      NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
      ngx_http_proxy_pass,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
  
      .
      .
      .
}

 这里就引用了proxy_pass这个命令的定义。关于命令定义不做详细解释了,这里主要看这个命令解析的handler即ngx_http_proxy_pass, 该handler是nginx在解析配置文件时如果遇到该命令就会调用对该命令的配置进行解析和预处理,我们来看一下这个handler:

static char *
ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_proxy_loc_conf_t *plcf = conf;

    size_t                      add;
    u_short                     port;
    ngx_str_t                  *value, *url;
    ngx_url_t                   u;
    ngx_uint_t                  n;
    ngx_http_core_loc_conf_t   *clcf;
    ngx_http_script_compile_t   sc;

    if (plcf->upstream.upstream || plcf->proxy_lengths) {
        return "is duplicate";
    }

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    //这里指定了该模块的http请求处理的handler
    clcf->handler = ngx_http_proxy_handler;

    //clcf->name.data应该是location配置中的path部分,即上面nginx配置中的“/”,
    // 那么这里if判断成功,auto_redirect变量会被置1,这个变量的意思应该是指uri匹配
    //location的path即“/”剩余的部分全部加到代理新的URL后面(例子后面验证后再补)
    if (clcf->name.data[clcf->name.len - 1] == '/') {
        clcf->auto_redirect = 1;
    }

    //nginx会将proxy_pass http://$host; 这一行按照空格解析成数组放在elts中
    value = cf->args->elts;

    //如proxy_pass http://$host; 则value[1]即是http://$host

    url = &value[1];

    //该函数是解析url中分成几个部分,变量即是分隔符,而且也是一部分,此处为1个
    n = ngx_http_script_variables_count(url);

    if (n) {//如果存在变量,走此处
        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
        sc.cf = cf;
        sc.source = url;
        sc.lengths = &plcf->proxy_lengths; 
        //plcf->proxy_lengths即用来存储被解析后的proxy_pass路径后的各个部分的长度计算接口
        sc.values = &plcf->proxy_values;
        //proxy_values用来存储被解析后的proxy_pass璐姐后的各个部分值的计算接口
        sc.variables = n;//变量数
        sc.complete_lengths = 1;
        sc.complete_values = 1;
        //编译proxy_pass配置的url
        if (ngx_http_script_compile(&sc) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
        
       //解析成功,返回
       return NGX_CONF_OK;
    }

 

变量的主要处理过程即ngx_http_script_compile方法,我们来看一下这个方法:

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;

    //主要是根据变量的长度来创建sc.lengths和sc.values数组,其实是proxy_lengths和proxy_values;
    if (ngx_http_script_init_arrays(sc) != NGX_OK) {
        return NGX_ERROR;
    }
    //source即配置的url,开始来解析
    for (i = 0; i < sc->source->len; /* void */ ) {
        name.len = 0;//name用来存储一部分值
        //遇见一个变量
        if (sc->source->data[i] == '$') {
            if (++i == sc->source->len) {
                goto invalid_variable;
            }

            if (sc->source->data[i] == '{') {
            //我们这里$后面没有{
            } else {
                bracket = 0;
                //name的开始位置,是从$后面的第一个字符开始的,因为上面++i
                name.data = &sc->source->data[i];
            }

             //这个for循环是找到这个变量name的结束位置
            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
                ch = sc->source->data[i];
                if (ch == '}' && bracket) {
                //不会有}                    
                }

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

                break;
            }

            if (name.len == 0) {
                goto invalid_variable;
            }

            //变量的个数加1
            sc->variables++;
            //将该变量加到之前创建的数组中
            if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
                return NGX_ERROR;
            }

            continue;
        }

        if (sc->source->data[i] == '?' && sc->compile_args) {
            //该配置中也没有该情况            
            continue;
        }

        //走到此处说明这又是一个新的部分的开始,但是不是变量,而是部分URL,如http://
        name.data = &sc->source->data[i];

        //如上,也是找到这个部分name结束的位置
        while (i < sc->source->len) {
            if (sc->source->data[i] == '$') {
                break;
            }
           if (sc->source->data[i] == '?') {
           }

            i++;
            name.len++;
        }

        sc->size += name.len;
         //将该部分添加到数组中
        if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }

    //编译处理结束
    return ngx_http_script_done(sc);

invalid_variable:

    ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");

    return NGX_ERROR;
}

 

 继续深入的看一下调用ngx_http_script_add_var_code(sc, &name)是怎样将新的变量放到相关的数组中的:

static ngx_int_t
ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
{
    ngx_int_t                    index, *p;
    ngx_http_script_var_code_t  *code;

    //这个是找到该name在cmcf->variables即变量数组中的偏移,如果没有该name,会在其中为该变量单独创建一个
    index = ngx_http_get_variable_index(sc->cf, name);

    if (index == NGX_ERROR) {
        return NGX_ERROR;
    }
     //flushes未设置
    if (sc->flushes) {
        p = ngx_array_push(*sc->flushes);
        if (p == NULL) {
            return NGX_ERROR;
        }

        *p = index;
    }
     //即在数组sc->lengths新添一个成员,并将新成员返回
    code = ngx_http_script_add_code(*sc->lengths,
                                    sizeof(ngx_http_script_var_code_t), NULL);
    if (code == NULL) {
        return NGX_ERROR;
    }
     //为新成员赋值,值是一个调用接口,可以获取这个变量对应结果字符串的长度
    code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code;
    code->index = (uintptr_t) index;
     //又在sc->values新添了一个成员
    code = ngx_http_script_add_code(*sc->values,
                                    sizeof(ngx_http_script_var_code_t),
                                    &sc->main);
    if (code == NULL) {
        return NGX_ERROR;
    }
     //为该成员赋值,后面也是一个回调方法,可以获取变量对应的内容
    code->code = ngx_http_script_copy_var_code;
    code->index = (uintptr_t) index;

    return NGX_OK;
}

 

这里可以看到在ngx_http_script_add_var_code中会将解析的变量创建一个节点,一个是计算长度的节点,一个是计算结果的节点,分别为他们指定了调用接口ngx_http_script_copy_var_len_code来计算变量值的长度和ngx_http_script_copy_var_code来获取变量。当在使用变量时就会调用该接口来计算变量的长度以及获取其值。 

 

下面我直接来看一下ngx_http_script_copy_var_len_code和ngx_http_script_copy_var_code的实现:

 

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) {
        //根据index获取变量的值,如果以前未计算过,则调用变量的hander获取变量值
        value = ngx_http_get_indexed_variable(e->request, code->index);
    } else {
        //与上面不同的是如果变量已经计算,但是该变量的值是不可缓存的,则清除值,调用ngx_http_get_indexed_variable()获取变量的值
        value = ngx_http_get_flushed_variable(e->request, code->index);
    }
    if (value && !value->not_found) {
        return value->len;
    }
    return 0;
}


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指定的缓存区中,并更新e->pos至新的位置。
            e->pos = ngx_copy(p, value->data, value->len);
        }
    }
}


proxy_pass http://$host; ,当nginx向原始服务器请求资源时,会根据配置的proxy_pass生成新的请求URL并请求对应的资源回复给客户,我们来看生成新的URL的地方:


static ngx_int_t
ngx_http_proxy_eval(ngx_http_request_t *r, ngx_http_proxy_ctx_t *ctx,
    ngx_http_proxy_loc_conf_t *plcf)
{
    u_char               *p;
    size_t                add;
    u_short               port;
    ngx_str_t             proxy;
    ngx_url_t             url;
    ngx_http_upstream_t  *u;

    //根据配置的proxy_pass生成新的URL,存储在proxy中
    if (ngx_http_script_run(r, &proxy, plcf->proxy_lengths->elts, 0,
                            plcf->proxy_values->elts)
        == NULL)
    {
        return NGX_ERROR;
    }

    if (proxy.len > 7
        && ngx_strncasecmp(proxy.data, (u_char *) "http://", 7) == 0)
    {
        add = 7;
        port = 80;

#if (NGX_HTTP_SSL)

    } else if (proxy.len > 8
               && ngx_strncasecmp(proxy.data, (u_char *) "https://", 8) == 0)
    {
        add = 8;
        port = 443;
        r->upstream->ssl = 1;

#endif

    } else {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid URL prefix in \"%V\"", &proxy);
        return NGX_ERROR;
    }

    u = r->upstream;
    u->schema.len = add;
    u->schema.data = proxy.data;

    ngx_memzero(&url, sizeof(ngx_url_t));

    url.url.len = proxy.len - add;
    url.url.data = proxy.data + add;
    url.default_port = port;
    url.uri_part = 1;
    url.no_resolve = 1;

    //½âÎöÇëÇóURL£¿
    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
        if (url.err) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "%s in upstream \"%V\"", url.err, &url.url);
        }

        return NGX_ERROR;
    }

    //Èç¹û°üº¬uri²¿·ÖÇÒÈ«²¿Îª²ÎÊý£¬ÔòÖØп½±´Ò»·Ý£¬²¢ÔÚÆäÇ°Ìí¼ÓÉÏ/
    if (url.uri.len) {
        if (url.uri.data[0] == '?') {
            p = ngx_pnalloc(r->pool, url.uri.len + 1);
            if (p == NULL) {
                return NGX_ERROR;
            }

            *p++ = '/';
            ngx_memcpy(p, url.uri.data, url.uri.len);

            url.uri.len++;
            url.uri.data = p - 1;
        }
    }

    ctx->vars.key_start = u->schema;

		//ÉèÖÃctx->varsµÄhost-headerºÍportÒÔ¼°uri
    ngx_http_proxy_set_vars(&url, &ctx->vars);

    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
    if (u->resolved == NULL) {
        return NGX_ERROR;
    }

    //url.addrsÓ¦¸Ã±íʾÒѽâÎöhostµÃµ½µÄIPµØÖ·£¬´Ë´¦Ó¦¸Ãδ½âÎö
    if (url.addrs && url.addrs[0].sockaddr) {
        u->resolved->sockaddr = url.addrs[0].sockaddr;
        u->resolved->socklen = url.addrs[0].socklen;
        u->resolved->naddrs = 1;
        u->resolved->host = url.addrs[0].name;

    } else {
        u->resolved->host = url.host;
        u->resolved->port = (in_port_t) (url.no_port ? port : url.port);
        u->resolved->no_port = url.no_port;
    }

    return NGX_OK;
}


 

我们这里主要来看 ngx_http_script_run的实现:


u_char *
ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
    void *code_lengths, size_t len, void *code_values)
{
    ngx_uint_t                    i;
    ngx_http_script_code_pt       code;
    ngx_http_script_len_code_pt   lcode;
    ngx_http_script_engine_t      e;
    ngx_http_core_main_conf_t    *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    for (i = 0; i < cmcf->variables.nelts; i++) {
        if (r->variables[i].no_cacheable) {
            r->variables[i].valid = 0;
            r->variables[i].not_found = 0;
        }
    }

    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));

    e.ip = code_lengths;
    e.request = r;
    e.flushed = 1;

    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 NULL;
    }
    e.ip = code_values;
    e.pos = value->data;
    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_script_code_pt *) e.ip;
        code((ngx_http_script_engine_t *) &e);
    }

    return e.pos;
}