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

本文将结合实际的源码来探讨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,计算的值依次填入到分配好的空间内

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值