当前位置:   article > 正文

深入理解nginx的动态变量机制(完整版)

深入理解nginx的动态变量机制(完整版)

1. 概述

   nginx提供了非常强大的动态变量的机制,通过动态变量,我们可以控制nginx的行为(如限流、acl验证、if判断、url rewrite等),可以输出nginx的请求信息和运行状态(如nginx的log中使用的动态变量)。
   nginx中的动态变量都是以$开头后面紧跟变量名的方式来使用的,可以在nginx配置文件中使用,也可以在openresty的lua脚本中使用,更可以在自研nginx模块中使用其他模块声明的变量。
   nginx本身声明了非常多的内置变量,可以供我们使用,当然,nginx的内核框架也不妨碍我们定义自己的动态变量,开放给其他模块(譬如日志模块)使用。
  本文将从源码层面来学习和了解nginx动态变量的使用方法和运行机制,以便对nginx的动态变量机制有一个比较深入的理解。在继续下文之前,本文对分析的范围姑且做一个限定,本文只讨论nginx http模块下的动态变量机制,对于stream模块,mail模块等不在本文讨论范围之内。

2. 动态变量的分类

2.1 按照变量名的确定性来分类

   按照声明变量的时候变量名是否确定来分类,可以分为:

  • 普通变量 : 这种变量变量名提前可知,如:$remote_addr $body_bytes_sent等。
  • 前缀匹配型变量 :这种变量变量名提前不可知,声明的时候只能确定变量名的前缀,如:$http_host, $http_referer等。总共包括http_、 sent_http_ 、 upstream_http_、 cookie_或者
    arg_共5种类型的前缀变量。

2.2 按照变量声明的来源分类

   按照声明变量的来源来分类,可以分为:

  • 核心变量:由ngx_http_core_module和ngx_http_upstream_module定义的变量,如:$connect_host、 $connect_port、$upstream_addr等。
  • 模块变量:由扩展模块声明的内置变量,如:ngx_http_slice_module声明的$slice_range,ngx_http_proxy_module声明的$proxy_host等,还包括其他第三方模块声明的变量。
  • 配置文件变量:由nginx配置文件通过set指令声明的变量。

2.3 按照是否可以变更分类

   按照变量是否可以被引用它的模块变更来分类,可以分为:

  • 可变更变量:不能由其他模块修改,如 $remote_addr $body_bytes_sent等。
  • 不可变更变量:可以由其他模块修改,如通过set指令声明的变量。

nginx本身提供的内置变量决大部分是不可变更的。

2.4 按照是否可以缓存分类

   按照声明的变量是可以缓存,可以分为:

  • 不可缓存变量
  • 可以缓存变量

  这里解释一下,变量值缓存的地方就是在ngx_http_request_t中,也就是说每个变量值从nginx的框架上来说是属于某个ngx_http_request_t实例的,这个可以通过动态变量的获取函数的定义得到明证,如下:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  以上三个版本的动态变量获取函数都是需要将ngx_http_request_t的指针r作为上下文参数传入的。在某个http request在求值完成后将缓存到ngx_http_request_t,当变量被声明为可以缓存,那么下次在该请求的某个处理环节还需要这个变量值的时候就直接从缓存中获取了而不需要再进行求值操作,从而可以优化nginx的处理性能。

2.5 按照变量的索引方式分类

   按照变量的索引方式分类, 可以分为:

  • hash 变量: 通过哈希表用名字进行查找的变量类型
  • 不可hash 变量:不把这个变量 hash 到散列表中

   因为nginx是一个非常注重性能,对内存的使用也极其“抠门”的应用。如果某个模块设计了 一个可选变量提供给其他模块使用,并且要求如果有其他模块使用该变量
就必须通过数组索引的方式再使用(即不能调用 ngx_http_get_variable方法来获取变
量值),这样子可以设置该变量为不可hash标记,那么这个变量就不用浪费散列表的存储空间了。

2.6 变量强弱分类

   按照变量的强弱分类, 可以分为:

  • 强变量:强变量是在配置阶段就会被解析和计算的变量,其取值在 Nginx 启动时就已确定。这些变量的值在运行时不会改变。
  • 弱变量:弱变量是在请求处理阶段才会被解析和计算的变量,其取值可能会随着请求的处理而改变。弱变量的值是在请求处理期间动态计算的,因此可能会因为请求的不同而有所变化。

  强变量和弱变量的特性决定了它们的使用方式和行为。强变量适合在配置阶段使用,而弱变量适合在请求处理阶段使用,以便根据请求的不同动态计算变量的值。

3. 变量的使用

   和强类型编程语言一样,nginx对变量的使用是分为两个阶段的,即变量声明阶段和变量读写阶段。
   变量的声明阶段定义了变量的一些属性,包括名字、是否可缓存标记、是否可修改标记、是否可哈希标记,以及进行变量读写的回调函数等信息。
   变量的读写阶段才会真正给变量分配对应的存储空间用来存储变量值存储,,变量值的内存空间不是预先分配的,这样子就避免了未被使用的变量提前分配内存造成内存的浪费。
  区别于正常的编程语言,nginx变量只能支持字符串类型。
  为了加速变量的读写操作,nginx会将需要用到的变量放到数组里面进行索引,通过数组的下标可以直接对变量的值进行读写操作,避免每次都需要通过hash表按照名字查找而印象性能。

3.1 声明一个变量

3.1.1 支撑变量声明的nginx关键结构体

  在详细阐述如何进行变量的声明前,有必要对支撑nginx变量机制中的相关结构体进行说明。先来看一下ngx_http_variable_s结构体的定义,它声明了一个nginx动态变量的相关属性:

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_HTTP_VAR_CHANGEABLE  
           NGX_HTTP_VAR_NOCACHEABLE   
           NGX_HTTP_VAR_INDEXED     
           NGX_HTTP_VAR_NOHASH  
           NGX_HTTP_VAR_WEAK  
           NGX_HTTP_VAR_PREFIX 
     等标记
    */
    ngx_uint_t                    flags;

    /* 如果变量被索引到数组中了,它在数组中的序号 */
    ngx_uint_t                    index;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

  在ngx_http_core_main_conf_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_array_t                prefix_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_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

  在ngx_http_core_main_conf_t的定义中,首先需要关注的是variables_keys,它会把nginx初始化期间声明的所有变量都注册在里面,在配置文件解析完成并且完成postconfiguration后,就会调用ngx_http_variables_init_vars函数,将nginx运行过程中将来会使用到的动态变量索引到variables数组中,同时将可哈希的变量添加到variables_hash哈希表中。其次,我们需要关注一下prefix_variables,它是用ngx_http_add_prefix_variable注册进来的,用来支持2.1节中描述的5种前缀匹配型变量。

3.1.2 在配置文件中声明

   在nginx配置文件的http块及其各子块中,可以通过set指令来声明并为一个动态变量设置值,这个是大家日常用得最多的自定义变量的方式。我们可以看看ngx_http_rewrite_module的ngx_http_rewrite_set函数的实现,它负责解析set指令。函数源码如下:

static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
   
    ngx_http_rewrite_loc_conf_t  *lcf = conf;

    ngx_int_t                            index;
    ngx_str_t                           *value;
    ngx_http_variable_t                 *v;
    ngx_http_script_var_code_t          *vcode;
    ngx_http_script_var_handler_code_t  *vhcode;

    value = cf->args->elts;

    /* 判断传入的第一个参数的第一个字符是否为$,如果不是说明不是有效的变量名,则报错 */
    if (value[1].data[0] != '$') {
   
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid variable name \"%V\"", &value[1]);
        return NGX_CONF_ERROR;
    }

    value[1].len--;
    value[1].data++;

    /* 如果变量已经存在,并且变量属性声明为可以缓存,则返回原来已经声明的变量,
       否则则创建一个新的变量。
	 */
    v = ngx_http_add_variable(cf, &value[1],
                              NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);
    if (v == NULL) {
   
        return NGX_CONF_ERROR;
    }

	/* 获取上面添加的变量的数组索引的下标 */
    index = ngx_http_get_variable_index(cf, &value[1]);
    if (index == NGX_ERROR) {
   
        return NGX_CONF_ERROR;
    }

	/* 如果没有设置对应的get_handler,那么对它设置默认值ngx_http_rewrite_var */
    if (v->get_handler == NULL) {
   
        v->get_handler = ngx_http_rewrite_var;
        v->data = index;
    }

    if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
   
        return NGX_CONF_ERROR;
    }

	/* 如果v->set_handler不为空,说明是其他模块声明的变量并且可以允许修改,
	   向动态script引擎中添加对应的动态脚本执行指令代码,
	   该代码用于在rewrite阶段对上面的变量进行赋值。
	*/
    if (v->set_handler) {
   
        vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                   sizeof(ngx_http_script_var_handler_code_t));
        if (vhcode == NULL) {
   
            return NGX_CONF_ERROR;
        }

        vhcode->code = ngx_http_script_var_set_handler_code;
        vhcode->handler = v->set_handler;
        vhcode->data = v->data;

        return NGX_CONF_OK;
    }

	/* 如果v->set_handler为空,意味着是set指令声明的变量,
	   向动态script引擎中添加对应的动态脚本执行指令代码,
	   该代码用于在rewrite阶段对上面的变量进行赋值。
	*/
    vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
                                       sizeof(ngx_http_script_var_code_t));
    if (vcode == NULL) {
   
        return NGX_CONF_ERROR;
    }

    vcode->code = ngx_http_script_set_var_code;
    vcode->index = (uintptr_t) index;

    return NGX_CONF_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

3.1.3 在http的核心模块中声明

   nginx的http内核默认内置了大量的动态变量,在ngx_http_core_module的preconfiguration阶段,会声明这些内置动态变量,对应调用的函数是ngx_http_core_preconfiguration,而ngx_http_core_preconfiguration又转而调用ngx_http_variables_add_core_vars来声明动态变量,声明的动态变量将都放在cmcf->variables_keys中,实现代码如下:

ngx_int_t
ngx_http_variables_add_core_vars(ngx_c
  • 1
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号