深入理解nginx的动态变量机制【上】

目录

  • 1. 概述
  • 2. 动态变量的分类
    • 2.1 按照变量名的确定性来分类
    • 2.2 按照变量声明的来源分类
    • 2.3 按照是否可以变更分类
    • 2.4 按照是否可以缓存分类
    • 2.5 按照变量的索引方式分类
  • 3. 变量的使用
    • 3.1 声明一个变量
      • 3.1.1 支撑变量声明的nginx关键结构体
      • 3.1.2 在配置文件中声明
      • 3.1.3 在http的核心模块中声明
      • 3.1.4 在模块中声明
      • 3.1.5 是否还有第四种声明变量的方式:
    • 3.2 索引一个变量
    • 3.3 获取变量值

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;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;

  在ngx_http_core_main_conf_t的定义中,首先需要关注的是variables_keys,它会把nginx初始化期间声明的所有变量都注册在里面,在配置文件解析完成并且完成postconfiguration后,就会调用ngx_http_variables_init_vars,将nginx运行过程中将来会使用到的动态变量索引到variables数组中,同时将可哈希的变量添加到variables_hash哈希表中。

3.1.2 在配置文件中声明

   在配置文件的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;
}

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_conf_t *cf)
{ngx_http_variable_t        *cv, *v;ngx_http_core_main_conf_t  *cmcf;cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);/* 初始化variables_keys,variables_keys用来存放所有声明的动态变量列表 */cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,sizeof(ngx_hash_keys_arrays_t));if (cmcf->variables_keys == NULL) {return NGX_ERROR;}cmcf->variables_keys->pool = cf->pool;cmcf->variables_keys->temp_pool = cf->pool;if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)!= NGX_OK){return NGX_ERROR;}if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8,sizeof(ngx_http_variable_t))!= NGX_OK){return NGX_ERROR;}/*ngx_http_core_variables定义了内置动态变量的列表, 通过遍历该列表,向nginx变量管理框架添加所有这些变量。*/for (cv = ngx_http_core_variables; cv->name.len; cv++) {v = ngx_http_add_variable(cf, &cv->name, cv->flags);if (v == NULL) {return NGX_ERROR;}*v = *cv;}return NGX_OK;
}

   在添加完变量后, 这个函数和ngx_http_rewrite_set有一点不一样的地方,ngx_http_rewrite_set是有ngx_http_get_variable_index调用的,而ngx_http_variables_add_core_vars却没有,为什么?因为ngx_http_variables_add_core_vars只是声明变量,至于这个变量在运行过程中用或是不用还不能确定,要等到其他模块或者nginx配置文件有地方引用这个变量的时候才能知道,但是此时因为还没有解析配置文件是不知道的;而rewrite模块中的ngx_http_rewrite_set函数对应的set指令,它不光是声明一个变量,同时也是是对变量进行赋值操作,所以在这里对变量进行了索引操作,变量的索引的具体解析放在3.2节中。

3.1.4 在模块中声明

   在nginx的扩展模块或者自研模块中进行声明其实和http核心模块差不多,我们以ngx_http_slice_module模块为例,关于ngx_http_slice_module模块的解析可以查看[[nginx slice模块的源码分析]],其中slice_range动态变量声明的源码如下:

static ngx_int_t
ngx_http_slice_add_variables(ngx_conf_t *cf)
{ngx_http_variable_t  *var;var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);if (var == NULL) {return NGX_ERROR;}var->get_handler = ngx_http_slice_range_variable;return NGX_OK;
}

   这里声明了一个名字为slice_range的只读变量,当然如果需要声明一个可读写变量,只要在调用ngx_http_add_variable的时候传递NGX_HTTP_VAR_CHANGEABLE即可,并且给ngx_http_variable_t设置set_handler回调函数,代码举例如下:

static ngx_int_t
ngx_http_writable_add_variable(ngx_conf_t *cf)
{ngx_http_variable_t *v;ngx_str_t name   = ngx_string( "writable_variable" );v = ngx_http_add_variable( cf, &name,     NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_CHANGEABLE );if( v == NULL ){return NGX_ERROR;}v->get_handler   = ngx_http_writable_get_variable;v->set_handler   = ngx_http_writable_set_variable;v->data          = 0;return NGX_OK;
}

3.1.5 是否还有第四种声明变量的方式:

  上面把nginx添加动态变量声明的三种方式进行了详细的说明,那么是不是还有第四中方式呢?我想到的是好像可以通过openresty lua脚本来声明。那openresty是不是可以在openresty中用lua代码来声明变量呢?
  正确的答案是不可以的,openresty只能对已经通过上述三种方式声明过的变量进行读写访问,不能自己声明变量,说白了openresty lua模块自己也是一个nginx中的特殊的模块,因为动态变量的声明只能是在nginx配置文件解析前,而openresty lua模块的执行至少是在配置文件解析之后,而且一般是在http request发生的时候,所以openresty是无法声明nginx动态变量的。
  至于是不是还有其他方式,大家有知道的,也欢迎大家提供一下。

3.2 索引一个变量

3.3 获取变量值

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++笔记:OOP三大特性之多态

前言 本博客中的代码和解释都是在VS2019下的x86程序中进行的,涉及的指针都是 4 字节,如果要其他平台下测试,部分代码需要改动。比如:如果是x64程序,则需要考虑指针是8bytes问题等等。 文章目录 前言一、多态的概念二、…

【C++初阶】系统实现日期类

目录 一.运算符重载实现各个接口 1.小于 (d1)<> 2.等于 (d1d2) 3.小于等于&#xff08;d1<d2&#xff09; 4.大于&#xff08;d1>d2&#xff09; 5.大于等于&#xff08;d1>d2&#xff09; 6.不等于&#xff08;d1!d2&#xff09; 7.日期天数 (1) 算…

mac图片怎么转换格式jpg?四种高效方法助你轻松搞定JPG格式

mac图片怎么转换格式jpg&#xff1f;在数字时代&#xff0c;图片格式的转换成为了我们日常操作中的一项基本技能。特别是在使用Mac操作系统的用户中&#xff0c;如何将图片转换为JPG格式成为了一个热门话题。本文将为你详细介绍四种简单实用的方法&#xff0c;帮助你在Mac上轻松…

测试基础1:伟大航路哟呼(Linux基础、mysql基础)

1 测试流程和方法 软件测试定义&#xff1a; 从方式上看&#xff1a;包含人工测试、自动化测试 从方法上看&#xff1a;运行程序或系统和测定程序或系统的过程 从目的上看&#xff1a;包括找bug和找bug出现的原因 软件测试的原则&#xff1a;功能性、可靠性、易用性、效率性…

一、网络基础知识

1、IP地址和端口号 1.1、IP地址 定义&#xff1a;用于在网络中唯一标识设备的地址。格式&#xff1a;通常由四个数字组成&#xff0c;以点分十进制表示&#xff0c;例如&#xff1a;192.168.0.1。(IPv4)作用&#xff1a;允许网络中的设备相互通信&#xff0c;通过IP地址可以定…

Python 数据可视化之密度散点图 Density Scatter Plot

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 密度散点图&#xff08;Density Scatter Plot&#xff09;&#xff0c;也称为密度点图或核密度估计散点图&#xff0c;是一种数据可视化技术&#xff0c;主要用于展示大量数据点在二维平面上的分布情况…

Swift基础知识:24.Swift可选链

在 Swift 中&#xff0c;可选链&#xff08;Optional Chaining&#xff09;是一种用于调用可选类型属性、方法或下标的安全方式。可选链允许我们在调用链中的任何一个属性、方法或下标返回 nil 时&#xff0c;整个调用链仍然可以继续执行&#xff0c;而不会因为其中的任何一个可…

一样的代码不同项目跳转页面报404的解决办法

今天收到实施反馈的一个问题&#xff0c;点项目名称跳转项目详情页面时&#xff0c;有的页面跳转显示正常&#xff0c;有的页面跳转报404错误。错误如下&#xff1a; 发现报错的项目都有一个共性就是有特殊字符“[ ]” , 解决的办法就是把带有特殊字符的字段 用 encodeURI()…

Java SE 入门到精通—4.抽象类与接口【Java】

抽象类 同接口一样&#xff0c;用来约束子类&#xff0c;限制子类必须拥有某些方法&#xff0c;比普通类多了个抽象方法&#xff0c;用抽象方法该类必为抽象类 概念 没有具体的对象&#xff0c;具体的方法的一个类 abstract关键字声明为抽象类/方法 一个类中有抽象方法则该…

统计前端传过来的Req的非空属性个数的工具类

背景 日常开发中&#xff0c;我们通常会根据前端传过来的实体类的属性个数去做逻辑判断&#xff0c;下面的是判断属性个数的工具类。 工具类 public static Integer nonNullFieldCount(Req req) {if (req null) {return 0;}int nonNullFieldCount 0;Field[] fields req.ge…

【Django】Django自定义后台表单——对一个关联外键对象同时添加多个内容

以官方文档为例&#xff1a; 一个投票问题包含多个选项&#xff0c;基本的表单设计只能一个选项一个选项添加&#xff0c;效率较低&#xff0c;如何在表单设计中一次性添加多个关联选项&#xff1f; 示例代码&#xff1a; from django.contrib import adminfrom .models impo…

Java中的关键字有哪些?它们各自的作用是什么?请详细说明?Java中的访问修饰符有哪些?它们的访问权限是怎样的?

1、Java中的关键字有哪些&#xff1f;它们各自的作用是什么&#xff1f;请详细说明&#xff1f; Java中的关键字是预先定义好的&#xff0c;具有特殊含义的标识符&#xff0c;用于表示数据类型、程序结构或控制流程等。以下是Java中的一些常用关键字及其作用&#xff1a; abs…

【软件架构】02-复杂度来源

1、性能 1&#xff09;单机 受限于主机的CPU、网络、磁盘读写速度等影响 在多线程的互斥性、并发中的同步数据状态等&#xff1b; 扩展&#xff1a;硬件资源、增大线程池 2&#xff09;集群 微服务化拆分&#xff0c;导致调用链过长&#xff0c;网络传输的消耗过多。 集…

嵌入式Qt 计算器核心算法_3

一.后缀表达式实现算数运算思路 二.算法实现 #include "QCalculatorDec.h"QCalculatorDec::QCalculatorDec() {m_exp "";m_result ""; }QCalculatorDec::~QCalculatorDec() {}bool QCalculatorDec::isDigitOrDot(QChar c) {return ((0 < c)…

基于SpringBoot的景区旅游管理系统

项目介绍 本期给大家介绍一个 景区旅游管理 系统.。主要模块有首页&#xff0c;旅游路线&#xff0c;旅行攻略&#xff0c;在线预定。管理员可以登录管理后台对用户进行管理&#xff0c;可以添加酒店&#xff0c;景区&#xff0c;攻略&#xff0c;路线等信息。整体完成度比较高…

一文搞懂match、match_phrase与match_phrase_prefix的检索过程

一、在开始之前&#xff0c;完成数据准备&#xff1a; # 创建映射 PUT /tehero_index {"settings": {"index": {"number_of_shards": 1,"number_of_replicas": 1}},"mappings": {"_doc": {"dynamic": …

探索气膜球幕影院:未来的电影体验

气膜球幕影院作为一种新兴的电影放映方式&#xff0c;正逐渐成为人们关注的焦点。它采用了充气式膜结构&#xff0c;可以为观众带来 360 度全景的观影体验&#xff0c;让人仿佛置身于电影之中。本文将介绍气膜球幕影院的特点、技术原理以及未来的发展前景。 传说在古代&#x…

Linux系统运维命令:使用 tail,grep组合命令(包括wc,sort,awk,sed等),可以方便的查阅和操作正在改变的日志文件的具体内容

一、命令介绍 1、tail命令 tail命令是Linux系统中常用的命令之一&#xff0c;用于查看文件的末尾内容。它具有许多有用的选项&#xff0c;可以帮助用户轻松地查找并显示文件中的信息。 它默认显示文件的最后10行&#xff0c;但可以通过各种选项来定制输出的行数、字节数等。ta…

十四、图像几何形状绘制

项目功能实现&#xff1a;矩形、圆形、椭圆等几何形状绘制&#xff0c;并与原图进行相应比例融合 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 drawing.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class DRAWING { public:void…

Python笔记-super().init(root)的作用

假设我们有一个名为Animal的父类&#xff0c;它有一个属性color&#xff0c;在其构造函数__init__中被初始化&#xff1a; class Animal:def __init__(self, color):self.color color现在&#xff0c;我们想创建一个Animal的子类&#xff0c;名为Dog。Dog类有自己的属性name&…