前面讲了nginx的变量机制,今天来讲讲nginx的脚本引擎。我们以一个比较简单的例子来讲述nginx的脚本引擎。例如,我们自己定以一个变量:
set $file t_a;
这个set指令就会调用ngx_http_rewrite_set函数,下面来看下ngx_http_rewrite_set:
static char * ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_rewrite_loc_conf_t *lcf = conf; ..... if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) { return NGX_CONF_ERROR; } ..... vcode = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_var_code_t)); if (vcode == NULL) { return NGX_CONF_ERROR; } vcode->code = ngx_http_script_set_var_code; vcode->index = (uintptr_t) index; return NGX_CONF_OK; }
这里仅仅是列出了与脚本引擎相关的代码,并且是与我们例子相关的。首先会调用ngx_http_rewrite_value这个函数,这个函数是做什么的,等下再看。接着会调用ngx_http_script_start_code从lcf->codes这个数组中获取sizeof(ngx_http_script_var_code_t)个元素,在下面为这个元素赋值。其实ngx_http_script_start_code获取的元素的大小都是一个字节,也就是说ngx_http_script_start_code其实就是给vcode分配空间。下面来看下ngx_http_rewrite_value这个函数的作用:
static char * ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value) { ..... n = ngx_http_script_variables_count(value); if (n == 0) { val = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(ngx_http_script_value_code_t)); if (val == NULL) { return NGX_CONF_ERROR; } n = ngx_atoi(value->data, value->len); if (n == NGX_ERROR) { n = 0; } val->code = ngx_http_script_value_code; val->value = (uintptr_t) n; val->text_len = (uintptr_t) value->len; val->text_data = (uintptr_t) value->data; return NGX_CONF_OK; } ..... }
这个函数我们也之列出了与我们例子相关的代码。首先会调用ngx_http_script_start_code这个函数,这个函数的作用就是统计value中的变量数目(以$开头的字符串),我们这个例子中value为t_a,所以没有变量,也就是n==0.而if判断条件的逻辑与我们上讲述vcode是一样的。这里也就不啰嗦了。
脚本引擎构建成功后内存图如下:此图来自深入剖析nginx(高群凯编写)书中的。
自此我们的脚本引擎构建成功了。那我们怎么让脚本引擎跑起来呢?我们可以利用rewrite指令来让我们的脚本引擎跑起来,在配置文件中配置如下:
location /t{ set $file t_a; rewrite ^(.*)$ /index.html?$file redirect; root html }
这样只要访问t目录都会被重定向到根目录,并把$file作为参数带过去。重定向是在nginx的NGX_HTTP_REWRITE_PHASE阶段调用ngx_http_rewrite_handler函数,下面来看下这个函数:
static ngx_int_t ngx_http_rewrite_handler(ngx_http_request_t *r) { ..... rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module); if (rlcf->codes == NULL) { return NGX_DECLINED; } e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t)); if (e == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } e->sp = ngx_pcalloc(r->pool, rlcf->stack_size * sizeof(ngx_http_variable_value_t)); if (e->sp == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } e->ip = rlcf->codes->elts; e->request = r; e->quote = 1; e->log = rlcf->log; e->status = NGX_DECLINED; while (*(uintptr_t *) e->ip) { code = *(ngx_http_script_code_pt *) e->ip; code(e); } ..... }
代码中rlcf->codes就是我们在构建脚本引擎中为vcode分配空间的lcf->codes,这个理解应该很简单。代码出现了一个新的类型(ngx_http_variable_value_t)。先不管这个新的类型。来看e->ip是获取了codes的元素的地址,而在while循环中把e->ip转换成ngx_http_script_code_pt*的类型,然后直接调用这个函数指针指向的函数。那这个函数是什么呢?我回到脚本引擎构建的时候,在ngx_http_rewrite_value有这么一句话:
val->code = ngx_http_script_value_code;
在ngx_http_rewrite_set也有这么一句话:
vcode->code = ngx_http_script_set_var_code;
我们来看下val和vcode的类型:
typedef struct { ngx_http_script_code_pt code; uintptr_t value; uintptr_t text_len; uintptr_t text_data; } ngx_http_script_value_code_t; typedef struct { ngx_http_script_code_pt code; uintptr_t index; } ngx_http_script_var_code_t;
从这两个结构可以看出,第一个成员都是ngx_http_script_code_pt类型的code。刚好对应上面ngx_http_rewrite_handler中的:
while (*(uintptr_t *) e->ip) { code = *(ngx_http_script_code_pt *) e->ip; code(e); }
这样刚好就可以调用val->code以及vcode->code了。而e->ip的移动都是在code中进行的。因为val,vcode的类型不一样,需要移动的偏移也是不一样的。所以e->ip移动的偏移由各自的类型决定。
从上面脚本引擎的内存图来看,是先调用val->code,来看下val->code的逻辑:
void ngx_http_script_value_code(ngx_http_script_engine_t *e) { ngx_http_script_value_code_t *code; code = (ngx_http_script_value_code_t *) e->ip; e->ip += sizeof(ngx_http_script_value_code_t); e->sp->len = code->text_len; e->sp->data = (u_char *) code->text_data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script value: \"%v\"", e->sp); e->sp++; }
其实就是把code的值付给e->sp。那code->text_len和code->text_data的值可以看ngx_http_rewrite_value。再来看vcode->code的逻辑:
void ngx_http_script_set_var_code(ngx_http_script_engine_t *e) { ngx_http_request_t *r; 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); r = e->request; e->sp--; r->variables[code->index].len = e->sp->len; r->variables[code->index].valid = 1; r->variables[code->index].no_cacheable = 0; r->variables[code->index].not_found = 0; r->variables[code->index].data = e->sp->data; }
整个逻辑就是把e->sp的值付给r->variables[code->index]。其实从代码中可以看出来sp的操作有点类似与数据结构中的栈。ngx_http_script_value_code通过e->sp++压栈,ngx_http_script_set_var_code通过e->sp--出栈。有人会在想r->variables又是从哪里冒出来的。ngx_http_init_request有这么几句话:
r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts * sizeof(ngx_http_variable_value_t)); if (r->variables == NULL) { ngx_destroy_pool(r->pool); ngx_http_close_connection(c); return; }
r->varibales刚好就是ngx_http_variables_valut_t的类型。这样与cmcf->variables刚好形成key-value的结构,而且key和value在各自索引是对应起来的。