locations 设计和实现

来源:互联网 发布:android web服务器软件 编辑:程序博客网 时间:2024/05/16 09:06
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 = 0; ngx_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_char) len;
    // 给 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; *clcfp; clcfp++){

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

----
0 0