当前位置: 首页 > 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

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

相关文章:

  • Apache Doris 基础 -- 数据表设计(分层存储)
  • js原型链原理与查找机制
  • 2024年十大数据集成工具和软件应用场景解析
  • 将Typora中图片从指定路径移动到当前文件夹下(准确位置为:XX.md文件所在目录/XX.assets/)
  • 如何正确操作工业高温烤箱
  • 谷粒商城实战(042集群学习-mysql集群-主从同步)
  • ChatGPT原理及其应用场景
  • 【Sa-Token|1】Sa-Token使用教程
  • 【LocalDate】获取两个日期间相差的年数、月数、天数
  • 如何做电子骑缝章?
  • 代理配置SQUID
  • 在树莓派上查看资源使用情况
  • PaddleTS的时序预测模型模块模块
  • Android的布局有哪些?
  • Linux安装并配置Java
  • [译]CSS 居中(Center)方法大合集
  • conda常用的命令
  • Github访问慢解决办法
  • golang 发送GET和POST示例
  • IOS评论框不贴底(ios12新bug)
  • Java编程基础24——递归练习
  • maven工程打包jar以及java jar命令的classpath使用
  • ubuntu 下nginx安装 并支持https协议
  • uva 10370 Above Average
  • Vue2.0 实现互斥
  • 笨办法学C 练习34:动态数组
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 从重复到重用
  • 使用Gradle第一次构建Java程序
  • 网络应用优化——时延与带宽
  • 物联网链路协议
  • 学习使用ExpressJS 4.0中的新Router
  • ​​​​​​​​​​​​​​Γ函数
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #数学建模# 线性规划问题的Matlab求解
  • (4)Elastix图像配准:3D图像
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (LeetCode) T14. Longest Common Prefix
  • (二)构建dubbo分布式平台-平台功能导图
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (九)c52学习之旅-定时器
  • (十三)MipMap
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .Net Core和.Net Standard直观理解
  • .Net MVC4 上传大文件,并保存表单
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .NET多线程执行函数
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka
  • @Transactional 详解
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?