注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

FY

Johnson 's Blog

 
 
 

日志

 
 

转 Nginx 配置文件解析 locations 设计和实现  

2011-12-23 10:31:15|  分类: Web 技术 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

链接出处


内部数据结构
-------------------
先看下下面这个数据结构:
typedef struct {
    void        **main_conf;
    void        **srv_conf;
    void        **loc_conf;
} ngx_http_conf_ctx_t;

这里面有三个指针数组;数组的长度相同,都是 Nginx 加载的模块的总数;三个数组相同位置上的元素的类型相同,如果 main_conf[3] 是 ngx_http_gzip_conf_t 类型的数据的话,srv_conf 和 loc_conf 也是。

以 Gzip Filter Module 为例:
static ngx_http_module_t  ngx_http_gzip_filter_module_ctx = {
    ngx_http_gzip_add_variables,           /* preconfiguration */
    ngx_http_gzip_filter_init,             /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_gzip_create_conf,             /* create location configuration */
    ngx_http_gzip_merge_conf               /* merge location configuration */
};
loc_conf[n] 里面的内容,就是 ngx_http_gzip_create_conf 的返回值,也就是创建并经过初始化的ngx_http_gzip_conf_t 类型的结构体数据。

没有定义创建 main_conf 和 srv_conf 的函数,所以 main_conf[n] 和 srv_conf[n] 为 NULL。


配置分三个级别:http,server,location,每一级别的每一个对象都会有一个 ngx_http_conf_ctx_t 结构来存储各个模块在各个级别的配置信息。 

http block 中 ctx 的初始化
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    // ...
    /* 初始化 http block 的 ctx*/
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL{
        return NGX_CONF_ERROR;
    }

    *(ngx_http_conf_ctx_t **conf = ctx;


    /* count the number of the http modules and set up their indices */
    // 原英文注释够清楚了
    ngx_http_max_module = 0;
    for (m = 0ngx_modules[m]; m++{
        if (ngx_modules[m]->type != NGX_HTTP_MODULE{
            continue;
        }

        ngx_modules[m]->ctx_index = ngx_http_max_module++;
    }
    /*
     * the http null srv_conf context, it is used to merge
     * the server{}s' srv_conf's
     */
    ctx->main_conf = ngx_pcalloc(cf->pool,
                                 sizeof(void ** ngx_http_max_module);
    if (ctx->main_conf == NULL{
        return NGX_CONF_ERROR;
    }
    /*
     * the http null srv_conf context, it is used to merge
     * the server{}s' srv_conf's
     */
    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void **ngx_http_max_module);
    if (ctx->srv_conf == NULL{
        return NGX_CONF_ERROR;
    }
    /*
     * the http null loc_conf context, it is used to merge
     * the server{}s' loc_conf's
     */

    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void **ngx_http_max_module);
    if (ctx->loc_conf == NULL{
        return NGX_CONF_ERROR;
    }
    
    /*
     * create the main_conf's, the null srv_conf's, and the null loc_conf's
     * of the all http modules
     */

    for (m = 0ngx_modules[m]; m++{
        if (ngx_modules[m]->type != NGX_HTTP_MODULE{
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        if (module->create_main_conf{
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL{
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf{
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL{
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_loc_conf{
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL{
                return NGX_CONF_ERROR;
            }
        }
    }
    // ...
}
代码很简单,就不解释了

server block 配置的初始化是在 ngx_http_core_server 里面,和 http block 类似;
server block 只初始化 srv_conf 和 loc_conf,无 main_conf,它的 main_conf 指向 http 级别的main_conf。

location block 配置的初始化是在 ngx_http_core_location 里面,main_conf 和 srv_conf 都指向 server 级别的配置,只负责初始化 loc_conf。

配置文件解析过程
---------------------------
ngx_init_cycle  函数里面,先调用 ngx_conf_param 处理命令行的配置选项(惯例,命令行和配置文件都支持),然后执行 ngx_conf_parse(&conf, &cycle->conf_file) 进行配置文件的解析。

基本处理流程:
1)  先设置要解析的配置文件的类型 module_type 和 cmd_type,ngx_init_cycle 里面的初始化是这样的:
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

那问题来了,那些是 NGX_CORE_MODULE 呢?http、events、mail、error_log、ngx_core_module 等都是 NGX_CORE_MODULE。

而 http、mail、event 等 module 还包含了多个子 module。

http 的子 module 的类型是 NGX_HTTP_MODULE;event 的子 module 是 NGX_EVENT_MODULE。

解析是先从 NGX_CORE_MODULE 类型的 module 开始的。

2) 解析过程,ngx_init_cycle 里面从解析 NGX_CORE_MODULE 类型的配置开始,一行一行的解析文件。
对于每一行,执行以空格(或者 \t 等) 为分隔符字符串 split,split 的结果存储在一个数组里面;
数组的第一个元素就是配置指令;遍历所有这种类型的 module,查找该指令出现在那个模块,然后执行该模块为该指令定义的回调函数来设置相关参数。

对于含有子 module 的模块,通常会在指令的回调函数里面,重新设置 module_type 和 cmd_type ,再次执行 ngx_conf_parse 来解析所有的子模块的配置,止到无子模块为止。

相关源码解析
------------
ngx_conf_parse 函数
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{

   //...
   if (filename{
      // 第一次调用时,会传入配置文件路径,这里打开配置文件,设置相应 buffer,和 文件的 offset 等
      // 以后调用,该参数保持为空即可
      //...
    } else if (cf->conf_file->file.fd != NGX_INVALID_FILE{

        // 基本上都是 parse block        
        type = parse_block;

    } else {
        // 这个用来解析命令行参数
        type = parse_param;
    }
    for ( ;; ) {
        // 读取一行,然后以空格或者 tab 进行 split,各个元素以数组形式存储在 cf->args 成员里面 
        rc = ngx_conf_read_token(cf);

        /*
         * ngx_conf_read_token() may return
         *
         *    NGX_ERROR             there is error
         *    NGX_OK                the token terminated by ";" was found
         *    NGX_CONF_BLOCK_START  the token terminated by "{" was found
         *    NGX_CONF_BLOCK_DONE   the "}" was found
         *    NGX_CONF_FILE_DONE    the configuration file is done
         */
         //...
         
         // 寻找该行配置指令的回调函数,然后执行设置相关参数,下面详细分析
         rc = ngx_conf_handler(cf, rc);
         /..
    }
    //...
}

ngx_conf_handler 函数
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    //...
    name = cf->args->elts;
    
    // 开始遍历所有 module
    for (i = 0ngx_modules[i]; i++{

        /* look up the directive in the appropriate modules */ 
        if (ngx_modules[i]->type != NGX_CONF_MODULE
            && ngx_modules[i]->type != cf->module_type)
        {
            continue;
        }

        // 获取该 module 定义的所有 commands
        cmd = ngx_modules[i]->commands;
        if (cmd == NULL{
            continue;
        }

        // 搜索所有 commands
        for ( /* void */ ; cmd->name.lencmd++{

            if (name->len != cmd->name.len{
                continue;
            }

            if (ngx_strcmp(name->data, cmd->name.data!= 0{
                continue;
            }


            // 查找成功,下面对该指令的类型、参数做检查
            
            /* is the directive's location right ? */

            if (!(cmd->type & cf->cmd_type)) {
                if (cmd->type & NGX_CONF_MULTI{
                    multi = 1;
                    continue;
                }

                goto not_allowed;
            }

            if (!(cmd->type & NGX_CONF_BLOCK&& last != NGX_OK{
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                  "directive \"%s\" is not terminated by\";\"",
                                  name->data);
                return NGX_ERROR;
            }
            //...

            /* set up the directive's configuration context */

            conf = NULL;

            // 获取相关的 ctx,这是一个配置的指针数组,前面有讲
            if (cmd->type & NGX_DIRECT_CONF{
                conf = ((void **cf->ctx)[ngx_modules[i]->index];

            } else if (cmd->type & NGX_MAIN_CONF{
                conf = &(((void **cf->ctx)[ngx_modules[i]->index]);

            } else if (cf->ctx{
                confp = *(void **) ((char *cf->ctx + cmd->conf);

                if (confp{
                    conf = confp[ngx_modules[i]->ctx_index];
                }
            }

            // 执行回调函数
            rv = cmd->set(cf, cmd, conf);
            // ...
        }
    }

对于 http block,它执行的回调函数是 ngx_http_block,里面会继续对所有子 http module 进行解析,相关代码如下:
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //...
    /* parse inside the http{} block */

    // 设置要解析的 module 类型为 NGX_HTTP_MODULE
    cf->module_type = NGX_HTTP_MODULE;
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
    
    // 开始解析子 module
    rv = ngx_conf_parse(cf, NULL);
    /...
}


Locations 数据结构及初始化

1) 先看看 ngx_http_core_loc_conf_t 这个结构体
struct ngx_http_core_loc_conf_s {
    // location 名称
    ngx_str_t     name;

    // 如果是 re location,这里存储 re 信息    
#if (NGX_PCRE)
    ngx_http_regex_t  *regex;
#endif

    //...
    
    // 是否是精确匹配 location =xxx
    unsigned      exact_match:1;
    unsigned      noregex:1;
    
    //...
    // locations 子树
    ngx_http_location_tree_node_t   *static_locations;
#if (NGX_PCRE)
    ngx_http_core_loc_conf_t       **regex_locations;
#endif

    /* pointer to the modules' loc_conf */
    void        **loc_conf;
    //...
    // ngx_queue_t 包含一个 *prev 和一个 *next 指针,用于构造链表。
    ngx_queue_t  *locations;
};
Nginx 的 locations 数据结构信息,都存储在这个结构体里面。


2) 配置文件中,location 指令对应的处理函数是 ngx_http_core_location,先分析下这个函数
static char *
ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
    //...
    // 创建 location 的 ctx
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL{
        return NGX_CONF_ERROR;
    }

    // 保存下父 ctx,可能是 server 的 ctx,也可能是父 location 的 ctx
    pctx = cf->ctx;
    
    // location 级别,无特别的 main_conf 和 srv_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;
    }
    
    // 初始化 loc_conf
    for (i = 0ngx_modules[i]; i++{
        if (ngx_modules[i]->type != NGX_HTTP_MODULE{
            continue;
        }

        module = ngx_modules[i]->ctx;

        if (module->create_loc_conf{
            ctx->loc_conf[ngx_modules[i]->ctx_index] =
                                                   module->create_loc_conf(cf);
            if (ctx->loc_conf[ngx_modules[i]->ctx_index] == NULL{
                 return NGX_CONF_ERROR;
            }
        }
    }
    
    // 获取 ngx_http_core_loc_conf_t 的配置
    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
    
    // 保存下当前的 loc_conf 到 clcf 中,用途后面分析
    clcf->loc_conf = ctx->loc_conf;

    // 获取 location 行解析结果,数组类型,如:["location", "^~", "/images/"]
    value = cf->args->elts;
    
    // 根据参数个数不同,来判断 location 类型,对对相应字段赋值
    // 如果是正则表达式,则会调用 ngx_http_core_regex_location 对 re 进行编译
    if (cf->args->nelts == 3{

        len = value[1].len;
        mod = value[1].data;
        name = &value[2];

        if (len == 1 && mod[0] == '='{
           //..
        }
        //..
     }else {
        //...
     }
     
    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
    // ...
    // 将当前的 location 配置加入的父 locations 的队列里面
    if (ngx_http_add_location(cf, &pclcf->locations, clcf!= NGX_OK{
        return NGX_CONF_ERROR;
    }

    // 保存当前配置,然后继续向下解析
    save = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_HTTP_LOC_CONF;

    // 解析 location 内的配置
    rv = ngx_conf_parse(cf, NULL);

    *cf = save;
    
    // 返回
    return rv;
}

2) 下面分析下 ngx_http_add_location 函数
ngx_int_t
ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
    ngx_http_core_loc_conf_t *clcf)
{
    ngx_http_location_queue_t  *lq;

    // 创建 locations 队列
    if (*locations == NULL{
        *locations = ngx_palloc(cf->temp_pool,
                                sizeof(ngx_http_location_queue_t));
        if (*locations == NULL{
            return NGX_ERROR;
        }

        ngx_queue_init(*locations);
    }

    // 创建队列 item
    lq = ngx_palloc(cf->temp_pool, sizeof(ngx_http_location_queue_t));
    if (lq == NULL{
        return NGX_ERROR;
    }

    // 设置队列 item 的 exact 和 inclusive 成员
    // 也就是之前创建的 ngx_http_core_loc_conf_t 变量 clcf
    if (clcf->exact_match
#if (NGX_PCRE)
        || clcf->regex
#endif
        || clcf->named || clcf->noname)
    {
        lq->exact = clcf;
        lq->inclusive = NULL;

    } else {
        lq->exact = NULL;
        lq->inclusive = clcf;
    }

    lq->name = &clcf->name;
    lq->file_name = cf->conf_file->file.name.data;
    lq->line = cf->conf_file->line;

    ngx_queue_init(&lq->list);

    // 插入到队列尾部
    ngx_queue_insert_tail(*locations, &lq->queue);

    return NGX_OK;
}

创建 Locations Tree
1) http block 的配置解析完成后,locations 的结构初始化完成(见上面分析)。
仍然在 ngx_http_block 函数中,随后开始了 locations tree 的构建。
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //...
    /* create location trees */
    for (s = 0; s < cmcf->servers.nelts; s++{

        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];

        if (ngx_http_init_locations(cf, cscfp[s], clcf!= NGX_OK{
            return NGX_CONF_ERROR;
        }

        if (ngx_http_init_static_location_trees(cf, clcf!= NGX_OK{
            return NGX_CONF_ERROR;
        }
    }
    //...
}
遍历每个 server,先执行 ngx_http_init_locations ,然后执行 ngx_http_init_static_location_trees 完成 locations tree 的构建。

2) ngx_http_init_locations 完成的任务其实比较简单。
首先执行 ngx_queue_sort(locations, ngx_http_cmp_locations) 对 locations 进行排序,排序规则比较复杂,请参见 ngx_http_cmp_locations 函数。

然 后将 regex location 和 named location 这两种 location 分离出来,分别存储在 cscf->named_locations 和 pclcf->regex_locations 队列里面,这两种 locations 不参与 location tree 的构建。location tree 只是 static location。

3) ngx_http_init_static_location_trees 开始 static location 的构建。
static ngx_int_t
ngx_http_init_static_location_trees(ngx_conf_t *cf,
    ngx_http_core_loc_conf_t *pclcf)
{
    //...
    locations = pclcf->locations;

    if (locations == NULL{
        return NGX_OK;
    }

    if (ngx_queue_empty(locations)) {
        return NGX_OK;
    }

    for (q = ngx_queue_head(locations);
         q != ngx_queue_sentinel(locations);
         q = ngx_queue_next(q))
    {
        lq = (ngx_http_location_queue_t *q;

        clcf = lq->exact ? lq->exact : lq->inclusive;

        // 递归创建子 locations tree
        if (ngx_http_init_static_location_trees(cf, clcf!= NGX_OK{
            return NGX_ERROR;
        }
    }
    // 合并相同的 Static location
    if (ngx_http_join_exact_locations(cf, locations!= NGX_OK{
        return NGX_ERROR;
    }

    // 将具有相同前缀的 locations 转移到 q->list 成员里面
    // 待下面详细分析
    ngx_http_create_locations_list(locations, ngx_queue_head(locations));

    // 创建 location tree,下面详细分析
    pclcf->static_locations = ngx_http_create_locations_tree(cf, locations,0);
    if (pclcf->static_locations == NULL{
        return NGX_ERROR;
    }

    return NGX_OK;
}

4) ngx_http_create_locations_list 函数
ngx_http_create_locations_list 处理比较复杂。假设有这么一些排好序的 locations 队列。
a ab abc abd ac acd ae bc bcd be,那么经过 ngx_http_create_locations_list  处理后,构造出来的结构大家如下图:
[Nginx 源码分析]locations 设计和实现 - ChinarenWei - 偶有所得,记录在此
下面简要分析下这个函数
static void
ngx_http_create_locations_list(ngx_queue_t *locations, ngx_queue_t *q)
{
    //...
    // 队列最后一个元素,递归结束 
    if (q == ngx_queue_last(locations)) {
        return;
    }

    lq = (ngx_http_location_queue_t *q;

    // 非 inclusive 类型,无需处理,处理下一个
    if (lq->inclusive == NULL{
        ngx_http_create_locations_list(locations, ngx_queue_next(q));
        return;
    }

    // 获取当前 location 的 name 和 name 的长度
    len = lq->name->len;
    name = lq->name->data;

    // 以 lq 为准,把所有以 lq->name 为前缀的 location 全部取出来
    // 由于所有的 locations 都是经过排序,相同前缀按长度排列在一起
    // 这里是寻找第一个不是以 lq->name 为前缀的 location 元素 x
    // x 之前的元素,都具有相同的前缀 lq->name 
    for (x = ngx_queue_next(q);
         x != ngx_queue_sentinel(locations);
         x = ngx_queue_next(x))
    {
        lx = (ngx_http_location_queue_t *x;

        if (len > lx->name->len
            || (ngx_strncmp(name, lx->name->data, len!= 0))
        {
            break;
        }
    }

    // 下面几行的的含义是
    // 除 lq 元素外,将其它具有相同前缀的 location 队列从 locations 里面分出来
    // 然后放入 lq->list 队列里面,剩余的仍然保留在 locations 队列里面等待处理
    q = ngx_queue_next(q);

    if (q == x{
        ngx_http_create_locations_list(locations, x);
        return;
    }

    ngx_queue_split(locations, q, &tail);
    ngx_queue_add(&lq->list, &tail);

    // 如果 locations 队列处理完毕,则开始递归处理 lq->list
    if (x == ngx_queue_sentinel(locations)) {
        ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
        return;
    }

    ngx_queue_split(&lq->list, x, &tail);
    ngx_queue_add(locations, &tail);

    // 递归处理 lq->list 里面的 location
    ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));

    // 继续处理 locations 队列里面的 location
    ngx_http_create_locations_list(locations, x);
}

5) 构建 location tree 的过程
ngx_http_create_locations_tree 函数会利用上面构建的 locations list,来构建一个真正的前缀树,它是一个三叉树。
下面看代码:
/*
* to keep cache locality for left leaf nodes, allocate nodes in following
* order: node, left subtree, right subtree, inclusive subtree
*/

static ngx_http_location_tree_node_t *
ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,
    size_t prefix)
{
    size_t                          len;
    ngx_queue_t                    *q, tail;
    ngx_http_location_queue_t      *lq;
    ngx_http_location_tree_node_t  *node;

    // 取得居中的 location,准备将 locations 分为两半
    q = ngx_queue_middle(locations);

    lq = (ngx_http_location_queue_t *q;
    // 取得当前除去 prefix 后 name 的长度
    len = lq->name->len - prefix;

    node = ngx_palloc(cf->pool,
                      offsetof(ngx_http_location_tree_node_t, name+ len);
    if (node == NULL{
        return NULL;
    }

    node->left = NULL;
    node->right = NULL;
    node->tree = NULL;
    node->exact = lq->exact;
    node->inclusive = lq->inclusive;

    node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect)
                           || (lq->inclusive && lq->inclusive->auto_redirect));

    node->len = (u_charlen;
    // 给 lq->name 赋值,其值为 lq->name 去掉 prefix 后的部分
    ngx_memcpy(node->name, &lq->name->data[prefix], len);

    // 将 locations 分为两半
    ngx_queue_split(locations, q, &tail);

    if (ngx_queue_empty(locations)) {
        /*
         * ngx_queue_split() insures that if left part is empty,
         * then right one is empty too
         */
        goto inclusive;
    }


    // locations 这一半用来构建左字树
    node->left = ngx_http_create_locations_tree(cf, locations, prefix);
    if (node->left == NULL{
        return NULL;
    }

    // 将当前 location 移除队列
    ngx_queue_remove(q);

    if (ngx_queue_empty(&tail)) {
        goto inclusive;
    }

    // 用另外一半构建右子树
    node->right = ngx_http_create_locations_tree(cf, &tail, prefix);
    if (node->right == NULL{
        return NULL;
    }

inclusive:

    if (ngx_queue_empty(&lq->list)) {
        return node;
    }

    // 递归构建 lq->list tree,prefix = prefix + len
    node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len);
    if (node->tree == NULL{
        return NULL;
    }

    return node;
}

Location 的查找
下面分析下,玩家请求过来后,根据玩家访问的 url 查找到对应的 location 的过程。
查找逻辑在 ngx_http_core_find_location 里面,比较简单。
1) 首先查找上面创建的 location tree,典型的树的查找方法,没啥新鲜的,如果查找成功,设置 r->loc_conf。
2) 如果 location tree 没有,则逐个遍历 regex_locations,利用正则表达式进行匹配比较。
简要代码如下:
static ngx_int_t
ngx_http_core_find_location(ngx_http_request_t *r)
{
    //...
    pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    // 查找 location tree
    rc = ngx_http_core_find_static_location(r, pclcf->static_locations);
    //...
    if (noregex == 0 && pclcf->regex_locations{

        for (clcfp = pclcf->regex_locations*clcfpclcfp++{

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "test location: ~ \"%V\"", &(*clcfp)->name);

            n = ngx_http_regex_exec(r, (*clcfp)->regex, &r->uri);

            if (n == NGX_OK{
                r->loc_conf = (*clcfp)->loc_conf;

                /* look up nested locations */

                rc = ngx_http_core_find_location(r);

                return (rc == NGX_ERROR? rc : NGX_OK;
            }

            if (n == NGX_DECLINED{
                continue;
            }

            return NGX_ERROR;
        }
    }
    //..
}
  评论这张
 
阅读(385)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018