目录
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);
以上三个版本的动态变量获取函数都是需要将ngx_http_request_t的指针作为上下文参数传入的。当变量被声明为可以缓存,那么在某个http request在求值完成后将缓存到ngx_http_request_t,下次在该请求的某个处理环节还需要这个变量的值的时候就直接从缓存中获取了而不需要再进行求值操作,从而可以优化nginx的处理性能。
2.5 按照变量的索引方式分类
按照变量的索引方式分类, 可以分为:
- hash 变量: 通过哈希表用名字进行查找的变量类型
- 不可hash 变量:不把这个变盘 has h 到散列表中
因为nginx非常注重性能,对内存的使用也极其“抠门”的应用。如果某个模块设计了 一个可选变量提供给其他模块使用,并且要求如果有其他模块使用该变量
就必须通过数组索引的方式再使用(即不能调用 ngx_http_get_variable方法来获取变
量值),这样,这个变量就不用浪费散列表的存储空间了。
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;
};
在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