nginx 变量 获取 ngx_http_script_run 详解

ngx_http_script_run 的作用是从配置文件中获取配置项的值。
这样说可能太抽象,我们来举个例子。

比如:
现在需要从 nginx.conf 中取这样一个配置项:

tt_signpass “mypass$remote_addr”;

注意,这个配置项的值包含 2 个部分 mypass(静态字符 / 常量) + $remote_addr(动态参数)。

那 ngx_http_script_run 是怎么完成这 2 个部分的解析的呢?
先来跟踪下这个函数:

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;

    // 获取 core 模块的配置项
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    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;
    e.request = r;
    e.flushed = 1;

    // 找到已经设置好的函数,执行并获得字符长度结果
    while (*(uintptr_t *) e.ip) {
        lcode = *(ngx_http_script_len_code_pt *) e.ip;
        len += lcode(&e);
    }

    value->len = len;
    value->data = ngx_pnalloc(r->pool, len);
    if (value->data == NULL) {
        return NULL;
    }

    e.ip = 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);
    }

    return e.pos;
}

看到这里,有个疑问:回调函数哪里来的? 来,直接看
-> ngx_http_script_compile, 它的作用是根据配置项的字符,分离常量和变量,同时准备获取实际内容的环境和条件。
可能有人会有疑问:
1,为什么不马上获取?
2,获取出来的内容不是直接就是字符了么,还分什么实际内容。

这都是针对配置项的值可能包含了 $remote_addr 这种动态参数,并且这种动态参数对于每次请求来说,都可能是不一样的值。所以有这个需求,在需要的地方及时获取。ngx_http_script_compilengx_http_script_run 一起, 这 2 个函数的实现我们可以看成是 LazyLoad, 或者是延迟调用这样的概念。

我们继续看 ngx_http_script_compile

ngx_int_t
ngx_http_script_compile(ngx_http_script_compile_t *sc)
{
    u_char       ch;
    ngx_str_t    name;
    ngx_uint_t   i, bracket;

    // 初始化存放变量的数组
    if (ngx_http_script_init_arrays(sc) != NGX_OK) {
        return NGX_ERROR;
    }

    for (i = 0; i < sc->source->len; /* void */ ) {

        name.len = 0;

        // 变量以 $ 为起始标记
        if (sc->source->data[i] == '$') {

            // $ 结尾的不算变量
            if (++i == sc->source->len) {
                goto invalid_variable;
            }

#if (NGX_PCRE) // Perl的正则库支持,处理类似 $1 的变量模型
            ...
#endif
            // 处理 ${var}www 变量和字符相连的模型,以 { 开始
            if (sc->source->data[i] == '{') {
                bracket = 1;

                if (++i == sc->source->len) {
                    goto invalid_variable;
                }

                name.data = &sc->source->data[i];

            } else {
                bracket = 0;
                name.data = &sc->source->data[i];
            }

            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
                ch = sc->source->data[i];

                // 处理 ${var}www 变量和字符相连的模型,以 } 结束
                if (ch == '}' && bracket) {
                    i++;
                    bracket = 0;
                    break;
                }

                if ((ch >= 'A' && ch <= 'Z')
                    || (ch >= 'a' && ch <= 'z')
                    || (ch >= '0' && ch <= '9')
                    || ch == '_')
                {
                    continue;
                }

                break;
            }

            if (bracket) {
                ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
                                   "the closing bracket in \"%V\" "
                                   "variable is missing", &name);
                return NGX_ERROR;
            }

            if (name.len == 0) {
                goto invalid_variable;
            }

            sc->variables++;

            // **关键**-这里来准备要解析变量长度和内容的回调,后面细说
            if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
                return NGX_ERROR;
            }

            continue;
        }

        // 根据 sc->compile_args 确定 '?' 是带参数的形式解析,
        // 还是认定它本身是常量字符,这里是针对 ${var}?*** 这种模型来解析
        if (sc->source->data[i] == '?' && sc->compile_args) {
            sc->args = 1;
            sc->compile_args = 0;

            if (ngx_http_script_add_args_code(sc) != NGX_OK) {
                return NGX_ERROR;
            }

            i++;

            continue;
        }

        name.data = &sc->source->data[i];

        // 在解析常量的时候遇到了 '$''?'
        while (i < sc->source->len) {

            if (sc->source->data[i] == '$') {
                break;
            }

            // 此处处理类似 ABC?abc 的模型
            if (sc->source->data[i] == '?') {

                sc->args = 1;

                if (sc->compile_args) {
                    break;
                }
            }

            i++;
            name.len++;
        }

        sc->size += name.len;

        // 对常量的长度和值的获取,准备回调等环境
        if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }

    return ngx_http_script_done(sc);

    ...
}

ngx_http_script_compile 函数是对配置项值解析的流程和框架的预加载,在实际环境中只需要调用 ngx_http_script_run 就可以获取到配置项实际代表的内容了。
将复杂的逻辑提前,简化实际运行的处理逻辑,这符合一个高性能服务器的要求。

看到这里,可能有了一个粗略的了解,但是在 ngx_http_script_run 调用的回调到底是哪个呢?

再来看 ngx_http_script_compile 中调用的 2 个函数:

ngx_http_script_add_var_code
ngx_http_script_add_copy_code

来分别跟踪下:

static ngx_int_t
ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
{
    ngx_int_t                    index, *p;
    ngx_http_script_var_code_t  *code;

    index = ngx_http_get_variable_index(sc->cf, name);

    if (index == NGX_ERROR) {
        return NGX_ERROR;
    }

    if (sc->flushes) {
        p = ngx_array_push(*sc->flushes);
        if (p == NULL) {
            return NGX_ERROR;
        }

        *p = index;
    }

    //为将要解析的变量,申请空间,并添加到链尾
    code = ngx_http_script_add_code(*sc->lengths,
                                    sizeof(ngx_http_script_var_code_t), NULL);
    if (code == NULL) {
        return NGX_ERROR;
    }

    //添加获取变量长度的回调函数
    code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code;
    code->index = (uintptr_t) index;

    code = ngx_http_script_add_code(*sc->values,
                                    sizeof(ngx_http_script_var_code_t),
                                    &sc->main);
    if (code == NULL) {
        return NGX_ERROR;
    }

    // 添加获取参数的内容的回调
    code->code = ngx_http_script_copy_var_code;
    code->index = (uintptr_t) index;

    return NGX_OK;
}

这个函数先为要解析的变量申请了空间,并附加在全局链表尾部,然后为这个变量长度的计算定义了回调函数 ngx_http_script_copy_var_len_code, 以及为这个变量内容的获取定义了回调函数 ngx_http_script_copy_var_code, 那么其实在ngx_http_script_run 函数中就是通过调用这 2 个回调来获取变量的值的。

对于常量的获取其实也是相同的道理。来看下常量的处理函数:

static ngx_int_t
ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc, ngx_str_t *value,
    ngx_uint_t last)
{
    u_char                       *p;
    size_t                        size, len, zero;
    ngx_http_script_copy_code_t  *code;

    zero = (sc->zero && last);
    len = value->len + zero;

    //申请内存,并添加至链尾
    code = ngx_http_script_add_code(*sc->lengths,
                                    sizeof(ngx_http_script_copy_code_t), NULL);
    if (code == NULL) {
        return NGX_ERROR;
    }

    //为常量长度的获取,添加回调函数
    code->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
    code->len = len;

    size = (sizeof(ngx_http_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
            & ~(sizeof(uintptr_t) - 1);

    code = ngx_http_script_add_code(*sc->values, size, &sc->main);
    if (code == NULL) {
        return NGX_ERROR;
    }

    //为常量内容的获取,添加回调函数
    code->code = ngx_http_script_copy_code;
    code->len = len;

    p = ngx_cpymem((u_char *) code + sizeof(ngx_http_script_copy_code_t),
                   value->data, value->len);

    if (zero) {
        *p = '\0';
        sc->zero = 0;
    }

    return NGX_OK;
}

OK,相同的结构,偶们不在重复了。

接下来我们来看看获取变量内容的回调函数的实现 ngx_http_script_copy_var_code 中都干了些什么。

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;

    e->ip += sizeof(ngx_http_script_var_code_t);

    if (!e->skip) {

        // e->flushed 标示是否去缓存还是重头获取值
        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);

        }
    }
}

对于以上代码,有兴趣的可以关注 ngx_http_get_indexed_variable 和 ngx_http_get_flushed_variable 这 2 个函数,不同点在于是否需要获取事先设置好的 handler 去立即更新数据。 另外 nginx 提供了一些了功能性函数。比如 array, string 的操作,用 C 写出了面向对象的概念,值得思考。 其中的一些功能函数,比如 ngx_array_push_n , 在数组扩充上是新结构大小的 2 倍(和 Vector 一致,这一点 C/C++ 和 Java都是一样的, Java 中 ArrayList 是 (×3/2 + 1)),由此可见,编程的思想基本是一致的。

扯远了,回来总结下,当我们要取一个配置项的值:

tt_signpass “mypass$remote_addr”;

对于常量和变量部分,其实是分开去获取,然后拼接在一起的。在 ngx_http_script_compile 的实现中,先将常量和变量拆分,然后分别为他们设置好回调处理函数(长度 + 内容)。比如变量内容的获取就为它设置好处理函数 ngx_http_script_copy_var_code。实际的运行环境中,在 ngx_http_script_run 中调用事先在 ngx_http_script_compile 中设置好的回调函数,来获取该变量具体的值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Nginx 中安装 ngx_http_mp4_module 模块,需要按照以下步骤进行操作: 1. 确认 Nginx 是否支持 ngx_http_mp4_module 模块 首先需要确认 Nginx 是否支持 ngx_http_mp4_module 模块,可以使用以下命令查看 Nginx 是否已经编译了该模块: ``` nginx -V 2>&1 | grep -o with-http_mp4_module ``` 如果输出结果为 with-http_mp4_module,则说明 Nginx 已经编译了 ngx_http_mp4_module 模块;如果输出结果为空,则说明 Nginx 没有编译该模块。 2. 下载 ngx_http_mp4_module 模块 如果 Nginx 没有编译 ngx_http_mp4_module 模块,需要下载该模块并添加到 Nginx 中。可以从 Github 上下载该模块,链接为:https://github.com/kaltura/nginx-vod-module。 可以使用以下命令将 ngx_http_mp4_module 模块下载到 /opt 目录下: ``` cd /opt git clone https://github.com/kaltura/nginx-vod-module.git ``` 3. 编译 Nginx 并添加 ngx_http_mp4_module 模块 在编译 Nginx 时需要添加 --add-module=/opt/nginx-vod-module 参数来指定 ngx_http_mp4_module 模块所在的目录,具体命令如下: ``` ./configure --prefix=/usr/local/nginx --add-module=/opt/nginx-vod-module make make install ``` 4. 配置 NginxNginx 的配置文件中添加以下内容,即可使用 ngx_http_mp4_module 模块: ``` location /video/ { mp4; mp4_buffer_size 1m; mp4_max_buffer_size 5m; } ``` 其中,/video/ 是视频文件所在的目录。mp4 是 ngx_http_mp4_module 模块提供的指令,表示该目录下的文件都是 MP4 格式的视频文件。 mp4_buffer_size 和 mp4_max_buffer_size 是 ngx_http_mp4_module 模块提供的两个参数,用于控制视频文件的缓存大小。 5. 重启 Nginx 完成以上步骤后,需要重启 Nginx 使配置生效: ``` nginx -s reload ``` 至此,ngx_http_mp4_module 模块安装完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值