当前位置: 首页 > news >正文

nginx脚本原理if指令实现详解

之前的文章我们探讨了nginx的变量,接着就是脚本原理,也就是复杂变量,理解了前面的实现原理,接下来了解if,break,return,set就要简单多。

指令有不少,没必要全部探讨,了解了其中之一即可,实现基本原理都一样,实现方式大同小异。理解了指令实现原理,我们就可以开发属于自己的配置指令。

我们以if指令为例,配置如下

if($remote=127.0.0.1){ #注:是= 不是==

        return 200 'you request is from local';

}

以此来分析nginx是如何编译(翻译)该指令,并如何执行的。

(题外话,我的源码取自angie,nginx版本为1.25.4)

其脚本基本原理不变:

将指令翻译成一个个执行单元,然后依次执行每个单元

其指令存放在ngx_http_rewrite_loc_conf_t的code数组中,后续是否有指令需要执行也是判断此数组是否为空

if指令的实现源码在 ngx_http_rewrite_module.c中,(此模块在http rewrite阶段实现,为什么在此阶段实现可以自行google或bing,但是确实有必要去了解下

编译:

我们先看if的配置解析函数,一开始就是重新建立了一个loc_conf,至于为什么就是上面黑字提到的。

其中的ngx_http_rewrite_if_condition则是处理和编译if($remote=127.0.0.1)这个条件字符串

大致流程是

找出变量调用ngx_http_rewrite_variable生成其code_t

找到=号后的值,调用ngx_http_rewrite_value生成其code_t

最后生成=号的code_t和if的code_t

1.首先找出表达式中变量 remote和 值 127.0.0.1,并顺带判断表达式的合法性

2.调用ngx_http_rewrite_variable为变量remote生成值计算的code_t,code_t取自上面说的code数组,其执行函数为ngx_http_script_var_code。跟之前的复杂变量不同的是,这里不需要计算变量长度。

3.提取=号后面的常量值变量复杂变量,我们看处理函数ngx_http_rewrite_value的源码

static char *
ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,ngx_str_t *value)
{ngx_int_t                              n;ngx_http_script_compile_t              sc;ngx_http_script_value_code_t          *val;ngx_http_script_complex_value_code_t  *complex;n = ngx_http_script_variables_count(value);//获取变量数量if (n == 0) {//按常量处理,常量值使用val = ngx_http_script_start_code(cf->pool, &lcf->codes,sizeof(ngx_http_script_value_code_t));if (val == NULL) {return NGX_CONF_ERROR;}n = ngx_atoi(value->data, value->len);if (n == NGX_ERROR) {n = 0;}val->code = ngx_http_script_value_code;//执行函数val->value = (uintptr_t) n;val->text_len = (uintptr_t) value->len;//保存常量长度val->text_data = (uintptr_t) value->data;//保存常量值首地址return NGX_CONF_OK;}//下面走复杂变量的编译逻辑,前面文章有详述,这不再解析了complex = ngx_http_script_start_code(cf->pool, &lcf->codes,sizeof(ngx_http_script_complex_value_code_t));if (complex == NULL) {return NGX_CONF_ERROR;}complex->code = ngx_http_script_complex_value_code;complex->lengths = NULL;ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));sc.cf = cf;sc.source = value;sc.lengths = &complex->lengths;sc.values = &lcf->codes;sc.variables = n;sc.complete_lengths = 1;if (ngx_http_script_compile(&sc) != NGX_OK) {return NGX_CONF_ERROR;}return NGX_CONF_OK;
}

函数也比较简单,=号后面的条件是常量还是变量(或复杂变量),如果是常量直接生成ngx_http_script_value_code_t,存放常量的值和长度,执行函数为ngx_http_script_value_code

然后就是为运算符=,生成了一个code_t ,其执行函数为ngx_http_script_equal_code

最后为if生成一个ngx_http_script_if_code_t,其执行函数是ngx_http_script_if_code

到这里的,我们配置示例中的if指令就算编译完成了。

执行:

从上面的编译不知道大家是否能看出或体会一点点"味道",熟悉函数调用的可能会体会到似曾相识的感觉。有一种压栈的感觉,先把参数和其值压栈,再压运算符=,最后再压入if指令。

接下来我们看执行了,我们看ngx_http_rewrite_handler函数

首先是看有没有需要执行的指令,即codes数组是否为空。

如果有,则生成ngx_http_script_engine_t来执行之前编辑好的指令集。

e->sp = ngx_pcalloc(r->pool,
                    rlcf->stack_size * sizeof(ngx_http_variable_value_t));

与前面复杂变量不同的是,这里会为engine_t中的sp分配“栈”空间,栈大小为 rlcf->stack_size(这个大小是固定的,虽然在merge有合并,但是未提供配置,固定是10),生成可以存储10个变量值的空间(类似cpu的sp寄存器)。看到这应该有点相似感觉了吧。

engine_t的ip类似cpu的指令寄存器,sp类似堆栈寄存器,指令执行的结果存放在sp中。前面的复杂变量只用到了ip,因此未做解析。

下面看执行,也是一样的如下

    while (*(uintptr_t *) e->ip) {
        code = *(ngx_http_script_code_pt *) e->ip;//取当前指令code_t
        code(e);        //执行指令函数
    }

然后我们逐个来看编译生成的code_t的执行函数

1.执行remote变量的code_t,执行函数为ngx_http_script_var_code,计算(获取)出remote的值

void
ngx_http_script_var_code(ngx_http_script_engine_t *e)
{ngx_http_variable_value_t   *value;ngx_http_script_var_code_t  *code;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script var");code = (ngx_http_script_var_code_t *) e->ip;//取当前code_te->ip += sizeof(ngx_http_script_var_code_t);//ip偏移到下个code_tvalue = ngx_http_get_flushed_variable(e->request, code->index);//计算变量值if (value && !value->not_found) {ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script var: \"%v\"", value);*e->sp = *value; //值结果存放到sp中,e->sp++;        //sp偏移到下个位置return;}*e->sp = ngx_http_variable_null_value;e->sp++;
}

2.执行等号后的常量值的code_t,执行函数为ngx_http_script_value_code

void
ngx_http_script_value_code(ngx_http_script_engine_t *e)
{ngx_http_script_value_code_t  *code;code = (ngx_http_script_value_code_t *) e->ip;//获取当前code_te->ip += sizeof(ngx_http_script_value_code_t);//ip偏移到下个code_te->sp->len = code->text_len;//由于此code_t是常量,其值直接存入sp中e->sp->data = (u_char *) code->text_data;ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script value: \"%v\"", e->sp);e->sp++;//sp偏移到下个位置
}

3.执行等号code_t,执行函数ngx_http_script_equal_code

void ngx_http_script_equal_code(ngx_http_script_engine_t *e)
{ngx_http_variable_value_t  *val, *res;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script equal");e->sp--;    //sp回退val = e->sp;    //取到值res = e->sp - 1;//取变量e->ip += sizeof(uintptr_t);//判断变量和值是否相等if (val->len == res->len&& ngx_strncmp(val->data, res->data, res->len) == 0){*res = ngx_http_variable_true_value;//相等则设置为true值,将remote的值设置为truereturn;}ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script equal: no");*res = ngx_http_variable_null_value;//不等则置为空值
}

4.最后执行if指令的code_t,执行函数ngx_http_script_if_code

void ngx_http_script_if_code(ngx_http_script_engine_t *e)
{ngx_http_script_if_code_t  *code;code = (ngx_http_script_if_code_t *) e->ip;//取if_codengx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script if");e->sp--;//这里为什么还要回退呢,前面的等号运算符的执行回退了一次,执行了值的sp,再回退一次,指                //向了remote的spif (e->sp->len && (e->sp->len != 1 || e->sp->data[0] != '0')) {if (code->loc_conf) {e->request->loc_conf = code->loc_conf;ngx_http_update_location_config(e->request);//这里需要更新location}//第一个值有效,则 当前判断成功,指向下个指令,即if()后,{}里面的指令,在这里就是指向    //return的code_te->ip += sizeof(ngx_http_script_if_code_t);return;}ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script if: false");e->ip += code->next;//
}

整个if的执行就到此结束了,接下来要执行的就是我们if条件成立后,{}内部的指令了。

总结如下:

编译:

1.生成运算符等号前变量的code_t,(运算符前面的必须是变量,源码就是这样实现的),

2.生成运算符后的值code_t,值可以是常量,变量,复杂变量。
3.生成运算符的code_t

4.生成if的code_t

执行:

逐个执行code的函数,最终结果的处理逻辑是由if_code_t执行函数来完成的。

但是欲彻底理解,就如我前面提到的必须,了解这些指令为什么要在rewrite阶段,而不其他阶段,nginx的框架是如此设计的,具体的原因也不是几句话能说清楚的,文章篇幅有限,本文直将if指令的实现,其他的自行google和bing

在此感谢大家的关注和点赞,若有描述不妥或不正确不准确的希望评论区指正,感谢~

相关文章:

nginx脚本原理if指令实现详解

之前的文章我们探讨了nginx的变量,接着就是脚本原理,也就是复杂变量,理解了前面的实现原理,接下来了解if,break,return,set就要简单多。 指令有不少,没必要全部探讨,了解了其中之一…...

数据提取与治理:企业数字化转型的双引擎

在当今数字化浪潮中,企业正面临着前所未有的挑战和机遇。为了在这场变革中立于不败之地,数字化转型成为了企业不可或缺的战略选择。而在数字化转型的众多关键要素中,数据提取与治理技术无疑扮演着至关重要的角色,它们如同双引擎一…...

Java8 新特性 记录【持续更新】

目录 一、Stream 相关 1、findFirst 方法 二、Optional 1、如何构造Optional 2、ifPresent 方法 一、Stream 相关 1、findFirst 方法 Stream的findFirst方法在此流中查找第一个元素作为Optional。 如果流中没有元素,findFirst返回空的Optional。 如果流没…...

Protobuf详解及入门指南

Protobuf详解及入门指南 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!在分布式系统和跨平台通信中,高效、轻量的序列化协议尤为重要。Google的Pro…...

[Java基本语法] 逻辑控制与方法

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏:🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 🧀线程与…...

新手教学系列-​​​​​​基础知识(SSH使用)

基础知识(SSH使用) 什么是ssh Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境[1]。SSH通过在网络中创建安全隧道来实现SSH客户端与服务器之间的连接[2]。虽然任何网络服务都可以通过SSH实现安全传输,SS…...

如何通过细节处理,让展馆展示效果倍增?

如今,企业展览馆已经成为企业树立形象、产品推广的重要平台,他们利用当下流行的多媒体技术,精心设计出一个展览馆,不仅能够吸引观众的眼球,还能够让企业品牌形象更加突出,不过想要制作出一个优质的企业展览…...

汽车IVI中控开发入门及进阶(二十九):i.MX6

前言: i.MX 6双/6Quad处理器集成多媒体应用处理器,是不断增长的多媒体产品系列的一部分,提供高性能处理,并针对最低功耗进行了优化。 i.MX 6Dual/6Quad处理器采用先进的quad-ArmCortex-A9内核,运行速度高达800 MHz,包括2D和3D图形处理器、1080p视频处理和集成电源管理。…...

2024-Pop!_OS新版本,新桌面环境的消息

原文:A Blog to Satisfy Your Monthly COSMIC Fix(es) - System76 Blog Pop!_OS开发团队正在为他们的发行版开发一个定制桌面。这个新的桌面环境被称为COSMIC,是用Rust语言编写的,超快的COSMIC应用商店几乎已经实现!alpha版本只剩下一些次要…...

三分钟了解链动3+1模式

在电商领域的营销策略中,链动31模式以其独特的魅力和优势,吸引了众多商家的目光。下面,我们将对这一模式进行深度剖析,并探讨其相较于链动21模式的优势所在。 一、身份设置与奖励机制 链动31模式在身份设置上分为三种&#xff1…...

加密excel(Python)

文章目录 一、EXCEL加密 一、EXCEL加密 import randomfrom win32com.client import Dispatchdef random_password(length20):默认返回20位随机密码key ""characters "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"for i in range(l…...

解决Unity-2020 安卓异形屏黑边

背景 Unity 2020.3.17 版本开发的游戏,打apk包,发现两个问题 如图下午所示,实体白色导航栏,阻挡了整个安卓UI界面,难看还影响美观。 安卓系统 12-13 版本手机,异形屏。一侧安全区黑边遮挡,占空间…...

python-给你比个五彩斑斓的❤️

import numpy as np import matplotlib.pyplot as plt from matplotlib.collections import LineCollectiont np.linspace(0, 2 * np.pi, 1000) x 16 * np.sin(t)**3 y 13 * np.cos(t) - 5 * np.cos(2 * t) - 2 * np.cos(3 * t) - np.cos(4 * t)# 创建一个颜色序列 colors …...

【Go】使用Go语言实现AES CBC No Padding加密和解密

冷雨悄悄停吧 天真的心因为你 那管多风雨天仍和你一起 告诉你我其实多么的想你 其实我我真的爱着你 🎵 蒋明周《真的爱着你》 引言 高级加密标准(AES)是一种广泛使用的加密算法。它可以工作在多种模式下,最…...

安装VS Code 提示This User Installer is not meant to be run as an Administrator问题

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 在vs code官网(https://code.visualstudio.com/)下载安装包,显示如下提示信息: This User Installer is not meant to be run as an Administrator.…...

keepalived服务详解与实验 基于centos8

目录 keepalivedHA简介常用的高可用软件keepalived简介 keepalived常用模块keepalived功能简介keepalived常用文件keepalived配置文件详解keepalived实验1-上手环境准备安装服务主配置文件修改启动服务效果查看 keepalived脑裂1. 脑裂现象简介2. 脑裂的原因3. 脑裂的预防和解决…...

vue技巧(十)全局配置使用(打包后可修改配置文件)

1、背景 vue打包目前主流用的有webpack和vite两种,默认用的webpack。(二者的区别大家可以各自上网查,我没用过vite,所以不过多介绍)vue通过webpack打包后,源码会被压缩,但一些关键配置可…...

计算机网络 —— 运输层(运输层概述)

计算机网络 —— 运输层(运输层概述) 运输层运输层端口号复用分用复用(Multiplexing)分用(Demultiplexing) 常用端口号页面响应流程 我们今天进入到运输层的学习: 运输层 我们之前学习的物理层…...

BKP备份寄存器RTC实时时钟

BKP备份寄存器&RTC实时时钟 VDDA和VSSA是内部模拟部分的电路 VDD和VSS_1、2、3是内部数字电路的供电。系统以VDD开头的电源都是主电源。在正常使用STM32时,全部需要接到3.3v电源上。 VBAT备用电池供电引脚,如使用STM32内部的BKP和RTC,引…...

基于协同过滤算法的电影推荐

基于协同过滤算法的电影推荐 电影推荐系统使用了基于**协同过滤(Collaborative Filtering)的算法来生成推荐。具体来说,使用了基于用户的协同过滤(User-Based Collaborative Filtering)**算法,步骤如下&am…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

【Oracle APEX开发小技巧12】

有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...