首页 > 系统相关 >nginx脚本原理(复杂变量)详解

nginx脚本原理(复杂变量)详解

时间:2024-06-12 16:04:20浏览次数:22  
标签:code http 变量 script value nginx 详解 ngx

本文将结合实际的源码来探讨nginx的脚本实现原理,并会在最后对此进行总结。本次只展示复杂变量,对于其if等指令后续文章再来探讨。

nginx的脚本支持使其具备了强大的灵活性,我们可以使用简单的脚本指令配置,进行灵活的功能定制。欲了解此功能,必先了解其变量的实现原理.(nginx变量),先看3个配置示例:

示例1:

location / {

     if ($host = “127.0.0.1”) {

         return 200 'you request is from local';

    }

}

示例2:

map $host $proxy{

127.0.0.1  127.0.0.1:8080;

default 127.0.0.1:80;

}

...

location / {

proxy_pass $proxy;

}

示例3:

location / {

proxy_pass  '127.0.0.1:$my_port'

}

我们可以根据一些参数的值,来走指定的业务。

下面我们来看http_proxy_module源码为例来解析。

下面先说说nginx的脚本(复杂变量)实现的基本原理,以http为例。

其主要源码在http/ngx_http_script.c中

基本原理:

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

是不是很简单就一句话,但是深究起来还是很复杂的,但是首先要记住这个基本的原理。每个执行单元就好比我们的一条cpu执行指令一样。执行单元其本质就是一个函数,就是依次调用每个函数而已。

我们以proxy_pass ‘127.0.0.1:$my_port’ 这个配置为例

前面是常量字符串,加一个变量$my_port,那么我们最终需要做的就是将常量+变量计算出来的值相加后,得到代理的完整地址。

然后我们结合源码来解释。

先看几个结构体

ngx_http_script_engine_t,看字面意思是脚本引擎,我们可以将其看做是cpu

typedef struct {
    ngx_http_script_code_pt     code;//执行函数(函数指针)
    uintptr_t                   len;                //值长度,变量值的长度
} ngx_http_script_copy_code_t;

字面意思是脚本拷贝指令,其就是一个执行单元,结构体以code_t结尾的基本都是执行单元。

此结构体计算的是常量,len等于常量的字符串长度

typedef struct {
    ngx_http_script_code_pt     code;
    uintptr_t                   index;        //变量索引值
} ngx_http_script_var_code_t;

ngx_http_script_engine_t会依次执行以code_t结尾的单元中的函数 code,nginx会每个执行翻译(编译)成一个对于的code_t结尾的结构体,最终使用ngx_http_script_engine_t来执行其中的每个结构体的code函数

此结构体计算的是变量,index是该变量的在全局数组的下标,code函数会去取变量的值(如果变量是不可缓存的,直接调用变量的get函数获取到变量的值和长度)

在函数 ngx_http_script_run中可以看到下面的代码

    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_script_code_pt *) e.ip;
        code((ngx_http_script_engine_t *) &e);//依次执行每个单元,每个code函数中,会做e.ip的偏移操作,使其偏移到下个code的地址,即e.ip会保存下个code的起始地址
    }

观察所有的code_t结尾的结构体的第一个成员都是ngx_http_script_code_pt     code;因此上面的代码才可以这样实现,不管code_t是怎样的,但是首地址都是执行函数地址。

下面具体参考http_proxy_module模块来说,ng是怎么翻译(编译)复杂变量的,以及是怎么执行这些单元的

一、翻译(编译)基本流程(在ngx_http_proxy_pass中实现):

1.获取配置中的变量个数ngx_http_script_variables_count(个数>0走下面的步骤),即$的个数

2.执行ngx_http_script_compile对脚本进行编译

        2.1 调用ngx_http_script_init_arrays主要是初始化两个数组(ngx_array_t),lengths和values,

                lengths中依次存的是计算变量长度的执行单元(code)

                values中依次存在的是计算变量的执行单元(code)

        因为nginx需要先计算整个复杂变量的总长度,然后才能分配足够的内存空间来依次存放每个变量值,得到最终的值。(在计算长度的时候,就可能调用变量的get函数,该函数会计算出值,如果值是可缓存的,后续计算值的时候,就可以不再调用get方法了,以此可以提高效率)

        2.2 遍历出所有的变量,为每个变量构造一个code_t的结构体存储,其中常量也是一个变量

                对于'you local host is $host and port is $port',这样的,分割出来就是4个变量,

                ‘’you local host is ‘是第一个变量,此变量为常量(含is后面的空格)

                $host为第二个,

                '  and port is '是第三个变量,此变量也为常量

                $port为第四个变量

                  依次为此4个变量生产执行单元,以code_t结尾的结构体.

        其中常量使用的是ngx_http_script_copy_code_t,其中的len就是常量字符串的长度,调用ngx_http_script_add_copy_code进行添加到lengths和values中去

        变量使用的是ngx_http_script_var_code_t,其中的index就是该变量在全局数组中的下标,调用ngx_http_script_add_var_code进行添加到lengths和values中去

        2.3 最后调用 ngx_http_script_done 来结束脚本编译。其目的就是在lengths和values的尾部添加2个空的code,用以标记脚本的结束。

二、脚本的执行

调用 ngx_http_script_run来执行之前生产好的脚本数组,其实现比较简单

u_char * ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
    void *code_lengths, size_t len, void *code_values)
{
    ngx_uint_t                    i;
    ngx_http_script_code_pt       code;
    ngx_http_script_len_code_pt   lcode;
    ngx_http_script_engine_t      e;
    ngx_http_core_main_conf_t    *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    //变量重置,如果变量不可缓存,则清除其valid和not_found标志位,使其每次获取都需要重新计算,即重新调用其get_handler方法

    for (i = 0; i < cmcf->variables.nelts; i++) {
        if (r->variables[i].no_cacheable) {
            r->variables[i].valid = 0;
            r->variables[i].not_found = 0;
        }
    }

    ngx_memzero(&e, sizeof(ngx_http_script_engine_t));

    e.ip = code_lengths;//code_lengths为上面所说的存放计算变量长度code的数组 lengths
    e.request = r;
    e.flushed = 1;

    while (*(uintptr_t *) e.ip) {
        lcode = *(ngx_http_script_len_code_pt *) e.ip;
        len += lcode(&e);//执行每个计算长度的code,返回的长度累加,得到整个复杂变量的总长
    }


    value->len = len;
    value->data = ngx_pnalloc(r->pool, len);//分配可以存储整个复杂变量的内存空间
    if (value->data == NULL) {
        return NULL;
    }

    e.ip = code_values;//code_values为上面所说的存放计算变量值的code数组values
    e.pos = value->data;//指向变量值的内存空间,在执行单元函数内,会对其进行偏移

    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_script_code_pt *) e.ip;
        code((ngx_http_script_engine_t *) &e);//依次执行每个code,得到的值填充到e.pos中

                                                                        //然后对e.pos进行偏移操作
    }

    return e.pos;
}
 我们来具体看一个变量长度的计算函数和变量值计算函数

size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e)
{
    ngx_http_variable_value_t   *value;
    ngx_http_script_var_code_t  *code;

    code = (ngx_http_script_var_code_t *) e->ip;//获取到当前的code_t

    e->ip += sizeof(ngx_http_script_var_code_t);//e->ip指向下个code_t

    if (e->flushed) {
        value = ngx_http_get_indexed_variable(e->request, code->index);//计算变量的具体值,根据

                                                                        //index找到变量,然后调用 变量的get_handler方法

    } else {
        value = ngx_http_get_flushed_variable(e->request, code->index);
    }

    if (value && !value->not_found) {
        return value->len;        //变量计算ok,返回变量长度
    }

    return 0;
}

变量值计算函数

void ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
{
    u_char                      *p;
    ngx_http_variable_value_t   *value;
    ngx_http_script_var_code_t  *code;

    code = (ngx_http_script_var_code_t *) e->ip;//获取当前code_t

    e->ip += sizeof(ngx_http_script_var_code_t);//指向下个code_t

    if (!e->skip) {

        if (e->flushed) {
            value = ngx_http_get_indexed_variable(e->request, code->index);//计算变量值

        } else {
            value = ngx_http_get_flushed_variable(e->request, code->index);
        }

        if (value && !value->not_found) {
            p = e->pos;
            e->pos = ngx_copy(p, value->data, value->len);//变量值填充到e->pos,并偏移e->pos

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP,
                           e->request->connection->log, 0,
                           "http script var: \"%*s\"", e->pos - p, p);
        }
    }
}

总结:

编译流程:

1.获取变量数量

2.根据变量数量和原始配置初始化lengths和values数组,数组中存储的都是code_t的结构体,这两个数组存储在http_proxy_module模块对应的location位置,其中也是根据lengths是否为空来做判断是否需要调用ngx_http_script_run来执行

3.为每个变量(常量和变量)构造code_t,前者是copy_code_t 后者是var_code_t,分别依次存入lengths和values数组中,最终调用ngx_http_script_done结束脚本的构造

执行流程:

1.依次执行lengths中的code_t,累加得到整个长度

2.分配足够的内存空间

3.依次执行values中的code_t,计算的值依次填入到分配好的空间内

标签:code,http,变量,script,value,nginx,详解,ngx
From: https://blog.csdn.net/wb1986218/article/details/139589546

相关文章

  • CDH详解(史上最全)
    工作记录知识研究CDH概览CDH(ClouderaDistributionIncludingApacheHadoop)是由Cloudera公司提供的一个集成了ApacheHadoop以及相关生态系统的发行版本。CDH是一个大数据平台,简化和加速了大数据处理分析的部署和管理。CDH提供Hadoop的核心元素-可伸缩存储和分布式计算-以......
  • 大模型「训练」与「微调」概念详解【6000字长文】
    本文你将学到什么1、大模型预训练与微调的基本流程2、预训练、训练、后期预训练、微调的区别3、大模型训练与微调的一些概念,如:Post-pretrain、SFT、RLHF、模型对齐、Lora、Q-Lora、大模型量化、微调指标、微调参数、大模型评测指标预训练与微调概览在大模型的预训练与微......
  • ubuntu在apt安装时出现的弹窗详解
    在一个全新安装的Ubuntu22.04LTS上,每次使用apt安装或更新软件包时,会出现一个Daemonsusingoutdatedlibraries弹窗,Whichserviceshouldberestarted?大概是下面这个样子: 这让我想起了windows上每次打开一个应用都会弹窗提示安全提醒,异曲同工啊。一脸懵的情况下,只能一......
  • 算法01 递推算法及相关问题详解
    目录递推的概念训练:斐波那契数列解析参考代码训练:上台阶参考代码训练:信封解析参考代码递推的概念递推是一种处理问题的重要方法。递推通过对问题的分析,找到问题相邻项之间的关系(递推式),从起点出发(首项或者末项)然后使用循环不断地迭代,得到最后需要的结果。训练:斐波......
  • 针对ubuntu系统,如何更改环境变量中的网络代理
    要更改Ubuntu系统中的网络代理环境变量,可以按照以下步骤操作:打开终端。可以使用快捷键Ctrl+Alt+T来打开终端。使用nano或vim等编辑器打开~/.bashrc文件。运行以下命令:nano~/.bashrc在文件末尾添加以下几行代码,以设置你的网络代理:#设置HTTP代理exporthttp_p......
  • C语言详解(编译和链接)
    Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎~~......
  • Interlocked 为多个线程共享的变量提供原子操作 多线程重入
    Interlocked可以为多个线程共享的变量提供原子操作主要使用的读写方法varrunningState=Interlocked.Read(refisRunning);Interlocked.Exchange(refisRunning,0);可以配合lock实现业务常用方法Add(Int32,Int32) 对两个32位整数进行求和并用和替换第一个整数,上述操......
  • 【代码+详解】算法题 : 金银岛
    ❗❗❗必看:下列题我全部都使用Java语言写的,并且均可以提交成功,获得Accepted结果的.如果代码和详解看了之后,对答案有任何疑问,都可以在评论区提出来,我都会一个一个回答.❗❗❗感谢大家的支持,如果喜欢我的博客,关注点赞收藏评论一波,非常感谢!!!文章目录......
  • 网络安全学习路线图(2024版详解)
     近期,大家在网上对于网络安全讨论比较多,想要学习的人也不少,但是需要学习哪些内容,按照什么顺序去学习呢?其实我们已经出国多版本的网络安全学习路线图,一直以来效果也比较不错,本次我们针对市场需求,整理了一套系统的网络安全学习路线图,供大家学习参考。希望大家按照路线图进行系统......
  • 14款测试用例管理工具详解
    14款不错的测试用例管理工具对比:PingCode、TestRAIl、Xray、PractiTest、TricentisqTest、禅道(ZenTao)、Zephyr、Tapd、TestLink、TestCollab、Testin云测、云效(AlibabaCloudEffect)、TeavCloud、FitNesse。在软件开发过程中,测试用例管理工具的使用变得越来越重要。这些工具......