Notice: 这篇文章写得较烂,做好心理准备。It's a mess, and you've been warned.

Nginx 核心模块和第三方模块提供了很多变量。通过这些变量的值,我们可以得知请求相关 的数据 ($request_uri, $arg_name, $remote_addr)、请求状态 ($bytes_sent)或 者根据请求的属性调用不同的处理逻辑 (if ($uri == "xxx")) 等等。

变量让 Nginx 配置有了更大的灵活性,这篇主要分析一下 Nginx 是如何实现变量特性的。

变量概念

  • Variable - a storage location and an associated symbolic name which contains some known or unknown quantity or information, a value.

    • The variable name is the usual way to reference the stored value. An name referencing a variable can be used to access the variable in order to read out the value or alter the value or edit the attributes of the variable.

    • The separation of name and content allows the name to be used independently of the exact information it represents.

    • Scope, type

  • Variable Interpolation - also known as variable substitution or variable expansion). It is the process of evaluating an expression or string literal containing one or more variables, yielding a result in which the variables are replaced with their correspoding values in memory.

agentzh's nginx tutorials 对如何使用变量进行了详细的介绍。从中归纳摘录要点如下:

Nginx 变量

  • Variable type:

    • Nginx variables can only be of one single type, that is, the string type.
  • Variable scope:

    • The scope of Nginx variables is the entire configuration, though, each request does have its own version of all those variables' storage. That is, the variable value is bound to a request, but not bound to its subrequest.

    • Normally, subrequest has its own copy of variable values. But subrequests initiated by certain modules do share variable storages with their parent requests. Such variables created by module ngx_auth_request.

    • Some built-in variables are insensitive to the context of the subrequests. They always act on the main request even when they are used in a subrequest. Such as $request_method.

  • Variable category:

    • Built-in variables - Most of them are read-only, like $uri, arg_XXX. Some of them are also writable, like $args.

    • User-defined variables - the ones created by set. They are all readable and writable.

  • Variable behavior underground: 这是 Nginx 变量和各种图灵完备语言中变量有区别的 地方。除了 nginx_rewrite_module 提供的 set 指令 (or more?) 定义的变量需要进 行显式赋值外,其它变量的值都是在被引用到时根据当前请求信息赋与的。

    • For some variables, the special code executed for reading the variable is called get handler and the code for writing to the variable is called set handler.

      • Take $args_XXX as an example: Nginx never tires to parse all the URL arguments beforehead, but rather scans the whole URL query string for a particular argument by get handler every time that argument is requested by reading the corresponding $arg_XXX.
    • And some variables called indexed variables, with value storages by Nginx at configure time.

    • Some Nginx variables choose to use their value storage as a data cache when the get handler is configured. Then the get handler only need to be run once.

      • Such as ngx_map module or ngx_geo module. And still, cache lifetime is bound to a specific request.
  • Variable lazy evaluation - The technique that postpones the value computation off to the point where the value is actually needed is called "lazy evaluation" in the computing world.

    • It is the "get handler" that performs the value computation and related assignment. And the "get handler" will not run at all unless the corresponding user variable is actually beging read. Therefore, for those requests that never access that variable, there cannot be any (useless) computation involved.
  • Special value "Invalid" and "Not Found"

    • A unassigned user variable takes the special value of "invalid".

    • When the current URL query string does not have the XXX argument, the built-in variable $arg_XXX takes the special value of "not found".

    • Both values are different from an empty string value ("").

变量定义

先介绍一下这一节涉及到的主要数据结构:

  • 变量跟踪结构体 - 维护 Nginx 各模块支持的 和 配置文件中用到的变量信息结构体。

    typedef struct {
        ...
        ngx_hash_t              variables_hash;
        ngx_array_t             variables;  /* array of ngx_http_variable_t */
        ..
        ngx_hash_keys_arrays_t  *variables_keys;
        ...
    } ngx_http_core_main_conf_t;
    
  • 变量信息结构体 - 存储变量属性、变量 set_handlerget_handler 和这两个函 数用到的参数值。

    struct ngx_http_variable_s {
        ngx_str_t                     name;
        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;
    };
    

如同 C 或者 Java 语言一样,Nginx 要求能够在配置文件中使用的变量,需要事先进行定 义。变量的定义是由模块代码完成的 (内建变量),或者由配置指令完成 (用户变量),在书 写配置文件时,只能使用已经定义过的变量。

变量的定义由提供变量的模块或者用户配置指令项 (如 set, geo 等) 完成:

  • 核心模块 ngx_http_core_module 提供的变量使用 ngx_http_core_variables 描述, 由 preconfiguration 回调函数 ngx_http_variables_add_core_vars 进行定义:

    ----------http/ngx_http_variables.c:1825-----
    ngx_int_t
    ngx_http_variables_add_core_vars(ngx_conf_t *cf)
    {
        ...
        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));
        ...
        cmcf->variables_keys->pool = cf->pool;
        cmcf->variables_keys->temp_pool = cf->pool;
        ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
    
        for (v = ngx_http_core_variables; v->name.len; v++) {
            rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v,
                                  NGX_HASH_READONLY_KEY);
            ...
        }
        ...
    }
    
  • 非核心模块比如 ngx_http_fastcgi_module 提供的变量使用 ngx_http_fastcgi_vars 描述,由 preconfiguration 回调函数 ngx_http_fastcgi_add_variables 进行定义:

    ----------http/modules/ngx_http_fastcgi_module.c:1882-----------
    static ngx_int_t
    ngx_http_fastcgi_add_variables(ngx_conf_t *cf)
    {
        ngx_http_variable_t *var, *v;
    
        for (v = ngx_http_fastcgi_vars; v->name.len; v++) {
            var = ngx_http_add_variable(cf, &v->name, v->flags);
            ...
            var->get_handler = v->get_handler;
            var->data = v->data;
        }
    
        return NGX_OK;
    }
    
  • ngx_http_rewrite_module 提供的 set 指令定义的自定义变量由其配置解析函数 ngx_http_rewrite_set 进行定义:

    ----------http/modules/ngx_http_rewrite_module.c:846---------
    static char *
    ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ...
        value = cf->args->elts;
        ...
        value[1].len--;
        value[1].data++;
    
        v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
        ...
        index = ngx_http_get_variable_index(cf, &value[1]);
        ...
    }
    

上述三种类型变量信息都被直接 (ngx_hash_add_key) 或间接 (ngx_http_add_variable) 存储到了 cmcf->variables_keys 中。

然而,Nginx 各个模块定义的变量可能只有部分出现到了配置文件 (nginx.conf) 中,也就 是说,在对配置文件中的 http {} 作用域进行解析的过程中,出现在配置项中的变量才 是在 Nginx 运行过程中被使用到的变量。Nginx 调用 ngx_http_get_variable_index 在 数组 cmcf->variables 中给配置项中出现的变量依次分配 ngx_http_variable_t 类型 的空间,并将对应的数组索引返回给引用此变量的配置项的解析回调函数。

引用此变量的配置项解析回调函数是如何存储这个数组索引,并且怎么在运行时,怎么通过 此索引找到此变量并获取它对应的值,下节再议。 / TODO /

    ----------http/ngx_http_variables.c:332-------------
    ngx_int_t
    ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
    {
        ...
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
        ...
        v = ngx_array_push(&cmcf->variables);
        ...
        v->name.len = name->len;
        v->name.data = ngx_pnalloc(cf->pool, name->len);
        ...
        v->set_handler = NULL;
        v->get_handler = NULL;
        v->data = 0;
        v->flags = 0;
        v->index = cmcf->variables.nelts - 1;

        return cmcf->variables.nelts - 1;
    }

配置文件 http {} 作用域解析完毕后,所有被用到过的变量在 cmcf->variables 中 都占有了一席之地。http {} 作用域解析完成后,Nginx 调用函数 ngx_http_variables_init_vars 对变量进行处理:

  • http {} 作用域用引用到的变量信息从 cmcf->variables_keys 中拷贝到 cmcf->variables

    -------------http/ngx_http_variables.c:1882------------
    v = cmcf->variables.elts;
    key = cmcf->variables_keys->keys.elts;
    
    for (i = 0; i < cmcf->variables.nelts; i++) {
    
        for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
    
            av = key[n].value;
    
            if (av->get_handler
                && v[i].name.len == key[n].key.len
                && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
                    == 0)
            {
                v[i].get_handler = av->get_handler;
                v[i].data = av->data;
    
                av->flags |= NGX_HTTP_VAR_INDEXED;
                v[i].flags = av->flags;
    
                av->index = i;
    
                goto next;
            }
        }
    
        if (ngx_strncmp(v[i].name.data, "http_", 5) == 0) {
            v[i].get_handler = ngx_http_variable_unknown_header_in;
            v[i].data = (uintptr_t) &v[i].name;
    
            continue;
        }
        ...
        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                      "unknown \"%V\" variable", &v[i].name);
        return NGX_ERROR;
        ...
    }
    
  • cmcf->variables_keys 中的变量信息创建 hashtable cmcf->variables_hash。 hashtable 的值 (ngx_hash_elt_t::value) 指向 ngx_http_variable_t 类型的各变量 定义时填充的变量信息结构体。

    hash.hash = &cmcf->variables_hash;
    hash.key = ngx_hash_key;
    hash.max_size = cmcf->variables_hash_max_size;
    hash.bucket_size = cmcf->variables_hash_bucket_size;
    ...
    if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
                      cmcf->variables_keys->keys.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
    
    cmcf->variables_keys = NULL;
    

总结下来,cmcf->variables_keys 只是在解析时使用的临时存储,在 http {} 作用域 配置解析完成后,cmcf->variables_keys 中的变量信息会被 ngx_http_variables_init_vars 转存到 cmcf->variablescmcf->variables_hash 中。

ngx_http_variables_init_vars 执行过程中,对不同类的变量进行的操作总结如下:

        类型                          操作
    ------------------------------------------------------------------------
    在配置文件中引用                添加 NGX_HTTP_VAR_INDEXED 标识,并记录其
    并定义有 get_handler             `cmcf->variables` 数组中的索引值到
                                    ngx_http_variable_t::index . 同时将预
                                    定义的变量信息拷贝至 `cmcf->variables`

    在配置文件中引用                设置 `ngx_http_variable_unknown_header_in`
    并且变量名以 `http_`             `get_handler`.
    开始的

    在配置文件中引用                设置 `ngx_http_variable_unknown_header_out`
    并且变量名以 `send_http_`        `get_handler`.
    开始的

    在配置文件中引用                设置 `ngx_http_upstream_header_variable`
    并且变量名以 `upstream_http_`    `get_handler`.
    开始的

    在配置文件中引用                设置 `ngx_http_variable_cookie`
    并且变量名以 `cookie_`           `get_handler`.
    开始的

    在配置文件中引用                设置 `ngx_http_variable_argument`
    并且变量名以 `arg_`              `get_handler`.
    开始的

    所有含有                        不会被添加到 `cmcf->variables_hash` 
    NGX_HTTP_VAR_NOHASH
    标识的变量

变量定义的过程大致如此。那么,在运行过程中,Nginx 是如何取出变量对应的数据的呢?

获取变量的值

脚本引擎

什么是引擎?从机械工程上说,是把能量转化成机械运动的设备。在计算科学领域,引擎一 般是指将输入数据转换成其它格式或形式的输出的软件模块。

Nginx 将包含变量的配置项参数转换成一系列脚本,并在合适的时机,通过脚本引擎运行这 些脚本,得到参数的最终值。换句话说,脚本引擎负责将参数中的变量在合适的时机取值并 和参数中的固定字符串拼接成最终字符串。

对参数的脚本化工作,也在配置项解析过程中完成。为了表达具体,选用下面的一条配置语 句进行分析。此配置项使 Nginx 在拿到请求对应的 Host 信息后,比如,"example.com", 经脚本引擎处理拼接成 "example.com/access.log" 作用后续 access log 的目标文件名。

    access_log  ${host}/access.log;

参数脚本化

脚本化相关数据结构

  • 脚本化辅助结构体 - 作为脚本化包含变量的参数时的统一输入。

    typedef struct {
        ...
        ngx_str_t               *source; /* 配置文件中的原始参数字符串 */
    
        ngx_array_t             **flushes;
        ngx_array_t             **lengths; /* 存放用于获取变量对应的值长度的脚本 */
        ngx_array_t             **values;  /* 存放用于获取变量对应的值的脚本 */
    
        ngx_uint_t              variables; /* 原始参数字符中出现的变量个数 */
        ...
    } ngx_http_script_compile_t;
    
  • ngx_http_script_copy_code_t - 负责将配置参数项中的固定字符串原封不动的拷贝到 最终字符串。

  • ngx_http_script_var_code_t - 负责将配置参数项中的变量取值后添加到最终字符串。

脚本引擎相关函数

  • ngx_http_script_variables_count - 根据 $ 出现在的次数统计出一个配置项参数 中出现了多少个变量。

  • ngx_http_script_compile - 将包含变量的参数脚本化,以便需要对参数具体化时调用 脚本进行求值。

  • ngx_http_script_done - 添加结束标志之类的收尾工作。

  • ngx_http_script_run -

  • ngx_http_script_add_var_code - 为变量创建取值需要的脚本。在实际变量取值过程 中,为了确定包含变量的参数在参数取值后需要的内存块大小,Nginx 将取值过程分成两个 脚本,一个负责计算变量的值长度,另一个负责取出对应的值。

    ------------------http/ngx_http_script.c:655---------------
    static ngx_int_t
    ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
    {
        ...
        ngx_http_script_var_code_t *code;
    
        index = ngx_http_get_variable_index(sc->sf, name);
        ...
        /*  lengths 内存块中开辟 sizeof(ngx_http_script_var_code_t) 字节
           的空间存放用于获取变量对应的值长度的脚本
         */
        code = ngx_http_script_add_code(*sc->lengths,
                                        sizeof(ngx_http_script_var_code_t), NULL);
        ...
        /* ngx_http_script_copy_var_len_code 用于获取 index 对应的变量取值后
           的长度 */
        code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code;
        code->index = (uintptr_t) index;
    
        /*  values 内存块中开辟 sizeof(ngx_http_script_var_code_t) 字节
           的空间存放用于获取变量对应值的脚本 */
        code = ngx_http_script_add_code(*sc->values,
                                        sizeof(ngx_http_script_var_code_t),
                                        &sc->main);
        ...
        /* ngx_http_script_copy_var_code 用于获取 index 对应的变量取值 */
        code->code = ngx_http_script_copy_var_code;
        code->index = (uintptr_t) index;
    
        return NGX_OK;
    }
    
  • ngx_http_script_add_copy_code - 处理参数中的固定字符串。这些字符串要和变量的 值拼接出最终参数值。

    ---------------http/ngx_http_script.c:576---------------
    static ngx_int_t
    ngx_http_script_add_copy_code(ngx_http_script_compile *sc, ngx_str_t *value,
        ngx_uint_t last)
    {
        ...
        ngx_http_script_copy_code_t *code;
        ...
        /* 从 lengths 内存块中开辟 sizeof(ngx_http_script_copy_code_t) 字节
           的空间用于存放固定字符串的长度 */
        code = ngx_http_script_add_code(*sc->lengths,
                                        sizeof(ngx_http_script_copy_code_t), NULL);
        ...
        /* 被调用时返回 len */
        code->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
        code->len = len;
    
        /* 固定字符串和用于获取此固定字符串的脚本需要的存储空间 */
        size = (sizeof(ngx_http_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
                & ~(sizeof(uintptr_t) - 1);
    
        /* 从 values 内存块中开辟 size 字节的空间用于存储固定字符串和操作脚
           本 */
        code = ngx_http_script_add_code(*sc->values, size, &sc->main);
        ...
        /* 被调用时将其后的固定字符串返回 */
        code->code = ngx_http_script_copy_code;
        code->len = len;
    
        /* 将固定字符串暂存入 values 中 */
        p = ngx_cpymem((u_char *) code + sizeof(ngx_http_script_copy_code_t),
                        value->data, value->len);
        ...
        return NGX_OK;
    }
    

继续上节虚构的样例配置指令 access_log ${host}access.log 从配置指令解析描述一下 变量从定义到使用的整个过程。

当配置解析代码碰到 access_log 指令后,会调用配置项回调函数 ngx_http_log_set_log 解析配置项参数。

    ----------http/modules/ngx_http_log_module.c:842-------------
    static char *
    ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ...
        value = cf->args->elts;
        ...
        n = ngx_http_script_variables_count(&value[1]);

        if (n == 0) {
            ...
        } else {
            if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
                return NGX_CONF_ERROR;
            }

            log->script = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_script_t));
            ...
            sc.cf = cf;
            sc.source = &value[1];
            sc.lengths = &log->script->lengths;
            sc.values = &log->script->values;
            sc.variables = n;
            sc.complete_lengths = 1;
            sc.complete_values = 1;

            if (ngx_http_script_compile(&sc) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
        ...
    }

对上述代码的补充说明:

  • ngx_conf_full_name 在 "$host/access.log" 前添加 Nginx 的 prefix 路径。函数成 功返回后,value[1] 的值将变成 "/usr/local/nginx/conf/$host/access.log" (假设 我们使用默认选项,将 Nginx 安装到了 /usr/local/nginx 下)。

  • 基本每个包含有变量参数的配置指令项的处理模式都大同小异:

    1. 对于能够接收变量参数的指令,先定义和配置指令相关的用于存储处理后的变量处 理脚本的结构体 (ngx_http_log_script_t)。每个变量参数对应一个此类型对象。
    2. 解析时判断参数是否含有变量 (ngx_http_script_variables_count)
    3. 如果参数含有变量,将此参数和变量处理脚本存储结构体通过脚本编译器的输入结 构 ngx_http_script_compile_t 交由 ngx_http_script_compile 进行处理。

ngx_http_script_compile 函数对参数项进行整理,存入 ngx_http_log_script_t 变 量中:

    -------------------http/ngx_http_script.c:229--------------------
    ngx_int_t
    ngx_http_script_compile(ngx_http_script_compile_t *sc)
    {
        ...
        if (ngx_http_script_init_arrays(sc) != NGX_OK) {
            return NGX_ERROR;
        }

        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] == '{') {
                    bracket = 1;
                    ...
                    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;
                }
                ...
                sc->variables++;

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

                continue;
            }
            ...
            name.data = &sc->source->data[i];

            while (i < sc->source->len) {
                if (sc->source->data[i] == '$') {
                    break;
                }
                ...
                i++;
                name.len++;
            }

            sc->size += name.len;
            if (ngx_http_scipt_add_copy_code(sc, &name, (i == sc->source->len))
                != NGX_OK)
            {
                return NGX_ERROR;
            }
        }

        return ngx_http_script_done(sc);
        ...
    }

对上面代码的补充说明:

  • Nginx 允许变量变使用 {},以便和非变量名的字符串进行分隔。

  • 变量名可以包含 [a-zA-Z_] 中的任意字符。

  • 解析代码识别出一个完整的变量名后,调用 ngx_http_script_add_var_code 对其进行 脚本化。解析代码将参数中的其它固定字符交由 ngx_http_script_add_copy_code 脚本 化。

ngx_http_script_compile 完成后 (回到 ngx_http_log_set_log 函数), 我们虚构的 例子对应的 ngx_http_log_script_t 的内存示意图如下:

/ TODO charts /

参数求值

Nginx 启动时对变量的准备工作算是全部完成了。剩下的问题,就是在什么样的时机, Nginx 如何对包含变量的参数进行求取的。

先来看一下,变量求值使用的主要结构体:

  • 脚本引擎 - 对包含变量的参数进行求值时用于记录状态的结构体。
    typedef struct {
        u_char                      *ip;  /* 即将被调用的脚本地址 */
        u_char                      *pos; /* 存储最终值的可用空间 */
        ngx_http_variable_value_t   *sp;
        ...
        ngx_http_request_t          *request; /* 请求上下文 */
    } ngx_http_script_engine_t;
    

变量求值使用的函数:

  • ngx_http_script_run - 通过脚本引擎运行预先设定的脚本从当前请求上下文中获取最 终字符串值。 其实现代码在下面进行分析。

  • ngx_http_script_copy_var_len_code - 根据请求上下文从 cmcf->variable 中找到 对应的变量信息,调用其 get_handler,获取变量当前值。然后,返回变量值的长度。

    ---------------http/ngx_http_script.c:700--------------
    size_t
    ngx_http_script_code_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;
    }
    
  • ngx_http_script_copy_var_code - 根据请求上下文从 cmcf->variable 中找到 对应的变量信息,调用其 get_handler,获取变量当前值。然后,返回变量值。

    ---------------http/ngx_http_script.c:724--------------
    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);
                ...
            }
        }
    }
    
  • ngx_http_script_copy_len_code - 计算需要从脚本存储中拷贝出的固定字符长度

    ---------------http/ngx_http_script.c:619--------------
    size_t
    ngx_http_script_copy_len_code(ngx_http_script_engine_t *e)
    {
        ...
        code = (ngx_http_script_copy_code_t *) e->ip;
    
        e->ip += sizeof(ngx_http_script_copy_code_t);
    
        return code->len;
    }
    
  • ngx_http_script_copy_code - 从脚本存储中拷贝固定字符到目的内存。

    ---------------http/ngx_http_script.c:632--------------
    void
    ngx_http_script_copy_code(ngx_http_script_engine_t *e)
    {
        ...
        code = (ngx_http_script_copy_code_t *) e->ip;
    
        p = e->pos;
    
        if (!e->skip) {
            e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_script_copy_code_t),
                              code->len);
        }
    
        e->ip += sizeof(ngx_http_script_copy_code_t)
              + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));
    }
    

access_log 指令由 ngx_http_log_module 提供,用于设定 Access Log 的目标文件和 Log 的格式。同时,ngx_http_log_module 提供位于 NGX_HTTP_LOG_PHASE phase 的回 调函数 ngx_http_log_handler

当一个请求处理主要逻辑处理完成后,Nginx 调用 ngx_http_log_handler 记录此请求的 相关 Log。

    -----------------http/modules/ngx_http_log_module.c:212----------
    ngx_int_t
    ngx_http_log_handler(ngx_http_request_t *r)
    {
        ...
        log = lcf->logs->elts;
        for (l = 0; l < lcf->logs->nelts; l++) {
            ...
            ngx_http_log_write(r, &log[l], line, p - line);
        }
        ...
    }

    -----------------http/modules/ngx_http_log_module.c:309-----------
    static void
    ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf,
        size_t len)
    {
        ...
        if (log->script == NULL) {
            name = log->file->name.data;
            n = ngx_write_fd(log->file->fd, buf, len);

        } else {
            name = NULL;
            n = ngx_http_log_script_write(r, log->script, &name, buf, len);
        }
        ...
    }

    -----------------http/modules/ngx_http_log_module.c:360-----------
    static ssize_t
    ngx_http_log_script_write(ngx_http_request_t *r, ngx_http_log_script_t *script,
        u_char **name, u_char *buf, size_t len)
    {
        ...
        ngx_str_t   log;
        ...
        if (ngx_http_script_run(r, &log, script->lengths->elts, 1,
                                script->values->elts)
            == NULL)
        {
            /* simulate successfull logging */
            return len;
        }

        log.data[log.len - 1] = '\0';
        ...
    }

对上面代码的补充说明:

  • 这就是我们一直提到的“时机”,在变量最终被使用时,对其进行求值。配置解析时生成的 脚本就是为了这个时刻准备的。

来看一下,ngx_http_script_run 是如何将配置解析时由 ngx_http_script_compile 生成的脚本转化成最终结果值的。

    ----------------http/ngx_http_script.c:382--------------
    u_char *
    ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
        void *code_lenghts, size_t len, void *code_values)
    {
        ...
        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 = code_lengths;
        e.request = r;
        e.flushed = 1;

        while (*(uintptr *) e.ip) {
            lcode = *(ngx_http_script_len_code_pt *) e.ip;
            len += locde(&e);
        }

        value->len = len;
        value->data = ngx_pnalloc(r->pool, len);
        ...
        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;
    }

对上述代码的补充说明:

  • e.pos 指向包含变量的参数的最终值存储空间。

  • 先计算出整个参数求值后需要占用的空间大小,然后逐个依次调用脚本。生成最终值,并 存储到 e.pos 指向的空间中。

  • r->variables 指向 ngx_http_variable_value_t 类型的数组,存储请求相关的变量 信息。数组元素个数和 cmcf->variables 数组保持一致。

脚本引擎进行参数求值的大致思路到现在就算清楚了,我们现在知道了一个包含变量的参数 在运行时是如何被转化为最终字符串的。但是获取我们虚构的例子中变量值的细节还不清 楚。下面详细分析一下变量本身的属性是怎么影响变量求值,以及 get_handler 是如何 发挥作用的。

变量的值

描述文字暂时搁浅,先将线索列举如下:

* ------------------------------------------------------------------------ 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 ngx_variable_value_t ngx_http_variable_value_t;


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

    e.flushed = 1;

    --------ngx_http_script_copy_var_len_code-------------------------------
    if (e->flushed) {
        value = ngx_http_get_indexed_variable(e->request, code->index);

    } else {
        value = ngx_http_get_flushed_variable(e->request, code->index);
    }

    --------ngx_http_get_indexed_variable-----------------------------------
    if (r->variables[index].not_found || r->variables[index].valid)
        return &r->variables[index];
    }

    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;

    --------ngx_http_get_flushed_variable-----------------------------------
    v = &r->variables[index];

    if (v->valid) {
        if (!v->no_cacheable) {
            return v;
        }

        v->valid = 0;
        v->not_found = 0;
    }

    return ngx_http_get_indexed_variable(r, index);


    --------ngx_http_variable_host----the get_handler-----------------------
    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;

对上面代码的几点总结:

  • r->variables 是变量值及属性的临时存储区域。由变量的属性 (NGX_HTTP_VAR_NOCACHEABLE) 决定此存储区域的值能否被多次读取。对于 noncacheable 的变量,每次取值时都需要调用 其 get_handler.

  • 变量的属性: NGX_HTTP_VAR_CHANGEABLE, NGX_HTTP_VAR_NOCACHEABLE, NGX_HTTP_VAR_INDEXED, NGX_HTTP_VAR_NOHASH.

其它变量

/ TODO / geo try_files if set arg_ 将所有变量全部列出来,并描述其相关属性。

Comments

不要轻轻地离开我,请留下点什么...

comments powered by Disqus

Published

Category

Nginx

Tags

Contact