nginx中if命令的设计和实现

标签: nginx server web server 服务器设计 | 发表时间:2011-03-13 17:13 | 作者:Simon Liu GFans
出处:http://www.pagefault.info

原创文章,转载请注明: 转载自pagefault

本文链接地址: nginx中if命令的设计和实现

先看这篇文章:http://wiki.nginx.org/IfIsEvil,这篇文章只是简单的介绍了if使用中一些很恶心的地方,接下来我会通过代码来看if为什么是 evil的。

if是rewrite模块里面的一个命令,因此if部分的执行也是在rewrite的phase执行的,下面就来简要的描述下rewrite模块是如何运行的。

这里有一个很关键的函数就是ngx_http_script_code_p,它的原型如下:

typedef void (*ngx_http_script_code_pt) (ngx_http_script_engine_t *e);

在rewrite模块中,所有将要在rewrite phase执行的代码的函数都会是一个ngx_http_script_code_pt类型的函数(比如rewrtie的正则匹配,比如if指令等等,而当进入rewrite handler的时候,将会依次执行这些函数,这些函数都是保存在ngx_http_script_engine_t中,下面我们来看这个结构。

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;


接下来就是ngx_http_rewrite_handler函数,这个函数是rewrite phase的handler,可以看到它的实现比较简单,先取得将要执行的回调函数的地址,然后依次执行他们,最终通过返回值(e->status)来决定需要如何返回.

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;
}

了解了大体流程之后,我们来看if指令的实现。首先来看ngx_http_rewrite_if的实现,函数比较长,我们分段来看,首先是新建一个ctx,然后新建location(调用create_loc_conf),然后将新建的location挂载到新的ctx中,这里要注意server conf和main conf是不变的。

    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;
        }
    }

接下来就是加新的location(ngx_http_add_location),紧接着就会解析if后面的指令(比如等号,括号等),通过不同的符号设置不同的回调函数,我们后面会分析这个函数,然后就是从codes属猪中取得对应的if_code,然后设置code值,也就是回调函数。

    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);

接下来来看ngx_http_rewrite_if_condition,这个函数比较长,我们就关注当if的条件是等于时的情况,其它的情况都类似。它也是会设置一个回调函数(code).

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;
        }
............................
}

然后来看ngx_http_script_equal_code,它主要是会判断if中声明的两个值是否相等,如果相等则设置对应的值为ngx_http_variable_true_value,否则设置为ngx_http_variable_null_value,以供后面调用ngx_http_script_if_code时判断。

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;
}

最后来看ngx_http_script_if_cod,它主要是就是根据前面的函数设置的变量来判断是否if条件成立,如果成立,则将在ngx_http_rewrite_if保存的loc conf赋值为当前的request的loc conf.这样,接下来的都会使用新的loc conf.

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;
}

最后来看一开始的nginx wiki中的几个if的例子。

从上面可以看到最关键的一个就是update loc conf的那段,而loc是每次在解析if指令的时候创建的,因此如果我们的指令在if之前就被解析的话,此时if中这个指令的设置就是无效的,我们来看一开始nginx wiki中的2个例子:

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
             }
        }

可以看到如果进入if的话,location里面的指令将不会被继承。所以对应的proxy_pass 和try_files都不会在if里面起作用.

而如果有两个if的话,第二个将会覆盖第一个,所以在下面的这个里面只有第二个会起作用.

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作用域就可以了。

Related posts:

  1. nginx的启动流程分析(一)
  2. nginx中处理http header详解(1)
  3. nginx对TCP_CORK/TCP_NOPUSH的使用

相关 [nginx if 命令] 推荐:

nginx中if命令的设计和实现

- GFans - pagefault
原创文章,转载请注明: 转载自pagefault. 本文链接地址: nginx中if命令的设计和实现. 先看这篇文章:http://wiki.nginx.org/IfIsEvil,这篇文章只是简单的介绍了if使用中一些很恶心的地方,接下来我会通过代码来看if为什么是 evil的. if是rewrite模块里面的一个命令,因此if部分的执行也是在rewrite的phase执行的,下面就来简要的描述下rewrite模块是如何运行的.

nginx配置ssl

- - 邢红瑞的blog
先生成网关证书 ,仿照CA模式.

Nginx安装

- - 企业架构 - ITeye博客
nginx可以使用各平台的默认包来安装,本文是介绍使用源码编译安装,包括具体的编译参数信息. 正式开始前,编译环境gcc g++ 开发库之类的需要提前装好,这里默认你已经装好. ububtu平台编译环境可以使用以下指令. centos平台编译环境使用如下指令. 一般我们都需要先装pcre, zlib,前者为了重写rewrite,后者为了gzip压缩.

Nginx GZip 压缩

- - 开心平淡对待每一天。热爱生活
  Nginx GZip 模块文档详见: http://wiki.nginx.org/HttpGzipModule 常用配置片段如下:. # 压缩比例,比例越大,压缩时间越长. 默认是1 gzip_types. text/css text/javascript; # 哪些文件可以被压缩 gzip_disable.

Nginx 限流

- - 鸟窝
电商平台营销时候,经常会碰到的大流量问题,除了做流量分流处理,可能还要做用户黑白名单、信誉分析,进而根据用户ip信誉权重做相应的流量拦截、限制流量. Nginx自身有的请求限制模块 ngx_http_limit_req_module、流量限制模块 ngx_stream_limit_conn_module基于令牌桶算法,可以方便的控制令牌速率,自定义调节限流,实现基本的限流控制.

NGINX的流媒体插件 nginx-rtmp-module

- - 开源软件 - ITeye博客
战斗民族俄罗斯人民开发的一款NGINX的流媒体插件,除了直播发布音视频流之外具备流媒体服务器的常见功能. 基于HTTP的FLV/MP4 VOD点播. HLS (HTTP Live Streaming) M3U8的支持. 基于http的操作(发布、播放、录制). 可以很好的协同现有的流媒体服务器以及播放器一起工作.

Nginx content cache Nginx内容缓存

- - CSDN博客推荐文章
原文地址: http://nginx.com/resources/admin-guide/caching/. When caching is enabled NGINX saves responses in the cache on the disk and uses them to respond to clients without proxying the requests..

nginx + memcached session 同步

- - 企业架构 - ITeye博客
squid 缓存疑问 问题归纳:. 1 squid 缓存的数据,何时过期,如何判断缓存的数据已经过期,如何把最新的数据缓入squid 并且替换掉旧的内容. 2 如何判断数据是否应该被缓存. 3 校验失败时,是否给出缓存中旧的内容. 上面的几个问题  都可以通过 squid中的 refresh_pattern 配置项 找到答案.

nginx日志切割

- - haohtml's blog
nginx的日志文件没有rotate功能. 如果你不处理,日志文件将变得越来越大,还好我们可以写一个nginx日志切割脚本来自动切割日志文件. 第一步就是重命名日志文件,不用担心重命名后nginx找不到日志文件而丢失日志. 在你未重新打开原名字的日志文件前,nginx还是会向你重命名的文件写日志,linux是靠文件描述符而不是文件名定位文件.

nginx框架总结

- - CSDN博客互联网推荐文章
a、内存池的设计,为一个多级链表结构,本身不负责内存的回收,减少内存碎片,提高内存的利用率,将多次向操作系统申请内存压缩为一次,减少向操作系统申请内存的次数,提高cpu资源的利用;. b、基于事件的master-worker异步处理. 处理事件的机制是由事件分发进程加载事件处理模块来完成与传统将事件提交给handle-thread来处理的方式相比,减少了进程(线程)间的切换,从而降低了请求响应延迟的时间;.