功能简介
nginx中有很多配置项支持以变量的形式存在,在运行时根据实时值进行处理。例如如下配置:
location / {
sub_filter '<a href="http://127.0.0.1:8080/' '<a href="https://$host/';
}
使用到了host变量,每个请求的host变量内容不同,运行的结果也不同。
解析带有变量的配置项,并在运行时求值,就是用通过ngx_http_script.c文件实现的。
使用方法
使用方式相对简单,需要了解2个结构体和2个函数。
typedef struct {
ngx_conf_t *cf;
ngx_str_t *value;
ngx_http_complex_value_t *complex_value;
//...
} ngx_http_compile_complex_value_t; //代码中常用ccv作为变量名
typedef struct {
//...
} ngx_http_complex_value_t;
//编译解析带变量的配置,常在配置阶段调用
//ccv->cf和cc->value(带变量配置的字符串)为入参
//ccv->complex_value 为出参,常保存在xxx_conf_t中。
ngx_int_t ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv);
//将带变量的配置项求值,常在运行阶段调用
//r,val为入参,val通过ngx_http_compile_complex_value()获得
//value为出参,即求值后的具体值。
ngx_int_t ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val, ngx_str_t *value);
使用示例
static char *
ngx_http_sub_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
//...
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &pair->match;//编译解析后的变量记录在match中
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
//...
}
static ngx_int_t
ngx_http_sub_header_filter(ngx_http_request_t *r)
{
//...
m = &matches[j].match; //m为具体的取值
if (ngx_http_complex_value(r, &pairs[i].match, m) != NGX_OK) {
return NGX_ERROR;
}
//...
}
数据结构
整体上功能比较简单,但在ngx_http_script中设计得比较复杂,个人觉得存在过设计和部分设计冗余的问题。我主要记录核心的东西。
typedef struct {
ngx_conf_t *cf;
ngx_str_t *value;
ngx_http_complex_value_t *complex_value;
} ngx_http_compile_complex_value_t;
typedef struct {
ngx_str_t value; //编译前的值,与ccv->value一致
ngx_uint_t *flushes;
void *lengths;
void *values;
//这3个指针是核心,分别指向了计算变量和长度的函数指针。flushes记录了变量的索引。
} ngx_http_complex_value_t;
typedef struct {
u_char *ip;
u_char *pos;
//....
} ngx_http_script_engine_t;
//用于求值执行的结构体
//ip是一些列求值函数,常来自cv->values
//pos是存放求值结果的指针
typedef struct {
ngx_conf_t *cf;
ngx_str_t *source; ////编译前的值,与ccv->value一致
ngx_array_t **flushes;
ngx_array_t **lengths;
ngx_array_t **values;
//...
} ngx_http_script_compile_t;
//用于编译解释时的结构体,几个核心指针与cv中的一致。
这个文件中涉及的数据结构还有很多其他的字段,都只在比较少的场景用使用到。抓住以上几个核心字段,代码理解起来就不困难了。
内部算法
算法没有高深的东西,就是把字符串变成一堆函数指针,再求值时依次执行函数。
解析过程中可以关注对变量的解析方式,核心代码如下:
主要处理了$var
和${var}
的场景。关于?做参数的使用方式,实际运用中比较少。为了看方便,我注释掉部分分支的处理
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;
//...
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 (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
//...
}
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];
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) {
//...
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;
}
//...
}
return ngx_http_script_done(sc);
invalid_variable:
ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name");
return NGX_ERROR;
}
求值部分带代码就更简单了,合适就是执行一圈cv->lengts和cv->values
ngx_int_t
ngx_http_complex_value(ngx_http_request_t *r, ngx_http_complex_value_t *val,
ngx_str_t *value)
{
size_t len;
ngx_http_script_code_pt code;
ngx_http_script_len_code_pt lcode;
ngx_http_script_engine_t e;
//...
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = val->lengths;
e.request = r;
e.flushed = 1;
len = 0;
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 NGX_ERROR;
}
e.ip = val->values;
e.pos = value->data;
e.buf = *value;
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}
*value = e.buf;
return NGX_OK;
}
具体执行的函数一般以xxx_var_code和xxx_var_len_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) {
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);
//...
}
}
}
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;
e->ip += sizeof(ngx_http_script_var_code_t);
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) {
return value->len;
}
return 0;
}