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_compile 和 ngx_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 中设置好的回调函数,来获取该变量具体的值。