nginx中if命令的设计和实现
先看这篇文章:http://wiki.nginx.org/IfIsEvil,这篇文章只是简单的介绍了if使用中一些很恶心的地方,接下来我会通过代码来看if为什么是 evil的。
if是rewrite模块里面的一个命令,因此if部分的执行也是在rewrite的phase执行的,下面就来简要的描述下rewrite模块是如何运行的。
这里有一个很关键的函数就是ngx_http_script_code_p,它的原型如下:
1 |
|
typedef struct {
//这个指针指向了所有的需要执行的函数(ngx_http_script_code_pt)数组的首地址.
u_char *ip;
u_char *pos;
ngx_http_variable_value_t *sp;
………………………………………………………
//表示执行完对应的函数之后的返回值.
ngx_int_t status;
ngx_http_request_t *request;
} ngx_http_script_engine_t;
1 |
|
static ngx_int_t
ngx_http_rewrite_handler(ngx_http_request_t *r)
{
ngx_http_script_code_pt code;
ngx_http_script_engine_t *e;
ngx_http_rewrite_loc_conf_t *rlcf;
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));
……………………………………………….
//取得回调函数的地址
e->ip = rlcf->codes->elts;
e->request = r;
e->quote = 1;
e->log = rlcf->log;
//默认返回值是declined
e->status = NGX_DECLINED;
//开始遍历回调函数.
while (*(uintptr_t *) e->ip) {
code = *(ngx_http_script_code_pt *) e->ip;
//执行回调,在回调函数中会更新ip指针,以便与下次调用.
code(e);
}
if (e->status == NGX_DECLINED) {
return NGX_DECLINED;
}
if (r->err_status == 0) {
return e->status;
}
return r->err_status;
}
1 |
|
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
pctx = cf->ctx;
//main conf和serv conf不变
ctx->main_conf = pctx->main_conf;
ctx->srv_conf = pctx->srv_conf;
//新建loc conf
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
//开始新建location conf
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
if (module->create_loc_conf) {
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}
ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
}
}
1 |
|
clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
clcf->loc_conf = ctx->loc_conf;
clcf->name = pclcf->name;
clcf->noname = 1;
//加location
if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
//设置if的条件对应的回调.
if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}
//从数组中取得元素(codes默认是一个每个元素为1个字节的数组).
if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
if (if_code == NULL) {
return NGX_CONF_ERROR;
}
//给code赋值,后面会详细分析这个回调函数.
if_code->code = ngx_http_script_if_code;
…………………………………………………………..
//如果name长度为0,则说明这是一个server if。
if (pclcf->name.len == 0) {
//此时loc就为null
if_code->loc_conf = NULL;
cf->cmd_type = NGX_HTTP_SIF_CONF;
} else {
//否则保存对应loc_conf,这里loc_conf里面保存了我们需要的信息.
if_code->loc_conf = ctx->loc_conf;
cf->cmd_type = NGX_HTTP_LIF_CONF;
}
//解析,这时if 作用域里面的命令都会保存在if_code->loc_conf中.因为上面我们改变了cf本身的loc conf
rv = ngx_conf_parse(cf, NULL);
1 |
|
static char *
ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf)
{
……………………………………………………….
if (len == 1 && p[0] == ‘=’) {
if (ngx_http_rewrite_value(cf, lcf, &value[last]) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}
//从codes数组中得到对应的值。
code = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(uintptr_t));
if (code == NULL) {
return NGX_CONF_ERROR;
}
//然后赋值。
*code = ngx_http_script_equal_code;
return NGX_CONF_OK;
}
……………………….
}
1 |
|
void
ngx_http_script_equal_code(ngx_http_script_engine_t *e)
{
ngx_http_variable_value_t *val, *res;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
“http script equal”);
e->sp–;
val = e->sp;
res = e->sp – 1;
e->ip += sizeof(uintptr_t);
//比较是否相等
if (val->len == res->len
&& ngx_strncmp(val->data, res->data, res->len) == 0)
{
//相等赋值为ngx_http_variable_true_value
*res = ngx_http_variable_true_value;
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
“http script equal: no”);
*res = ngx_http_variable_null_value;
}
1 |
|
void
ngx_http_script_if_code(ngx_http_script_engine_t *e)
{
ngx_http_script_if_code_t *code;
code = (ngx_http_script_if_code_t *) e->ip;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
“http script if”);
e->sp–;
//判断if的条件是否成立
if (e->sp->len && e->sp->data[0] != ‘0’) {
if (code->loc_conf) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
“http script if: update”);
//修改loc conf,然后update。
e->request->loc_conf = code->loc_conf;
ngx_http_update_location_config(e->request);
}
e->ip += sizeof(ngx_http_script_if_code_t);
return;
}
//否则修改ip,然后进入下面的处理
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
“http script if: false”);
e->ip += code->next;
}
1 |
|
location /proxy-pass-uri {
proxy_pass http://127.0.0.1:8080/;
set $true 1;
if ($true) {
# nothing
}
}
# try_files wont work due to if
location /if-try-files {
try_files /file @fallback;
set $true 1;
if ($true) {
# nothing
}
}
1 |
|
location /only-one-if {
set $true 1;
if ($true) {
add_header X-First 1;
}
if ($true) {
add_header X-Second 2;
}
return 204;
}
```
不知道igor以后会不会改写if,我的想法是,把if放到core http module,然后单独做一个if作用域,它要么属于server要么属于loc,然后每次解析对应的server或者loc的时候,merge存在的if作用域就可以了。