zoukankan      html  css  js  c++  java
  • Nginx 之 Location 的整理

    1. Location 的整理

    在将配置解析完后,所有的 location 此时都以 tree 的形式组织起来,具体可参考 Nginx之 Location 的生成

    此时需要对所有 server 下的 location 进行整理(如把同类型的 location 放置在一起以便于查找等)。

    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++) {
    
            /* cscfp 指向当前 http{} 下所属于的 ngx_http_conf_ctx_t 结构体的 main_conf 
             * 数组中 ngx_http_core_module.ctx_index 索引处的上下文结构体 
             * ngx_http_core_main_conf_t 结构体中的 servers 成员数组,该数组保存着当前 http{}
             * 下所有的 server{} 的配置上下文结构体 ngx_http_core_srv_conf_t,每一个该结构体
             * 代表一个 server{} */
            clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
    
            /* 这里会对所有的 location 进行排序,然后:
             * 1. 先将未命名 location 从 locations 队列中拆分出来;
             * 2. 接着将命名 location 从 locations 队列中拆分出来(拆分之前会将所有的
             *    命名 location 逐一保存到 cscf->named_location 数组中)
             * 3. 最后将 regex location 拆分出来(拆分之前会将所有的 regex location 的配置数据
             *    以数组的形式逐一保存到 clcf->regex_locations 数组中)
             * 经过拆分之后,此时 locations 队列中只剩下被称之为 static 的 location (包括绝对匹配 
             * location 和普通的 location)*/
            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;
            }
        }
        
        ...
    }
    

    1.1 ngx_http_init_locations

    /*
     * @cscf: 代表一个 server{}
     * @pclcf: 当前 server{}(即 cscf)下的一个 location
     */
    static ngx_int_t
    ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
        ngx_http_core_loc_conf_t *pclcf)
    {
        ngx_uint_t                   n;
        ngx_queue_t                 *q, *locations, *named, tail;
        ngx_http_core_loc_conf_t    *clcf;
        ngx_http_location_queue_t   *lq;
        ngx_http_core_loc_conf_t   **clcfp;
    #if (NGX_PCRE)
        ngx_uint_t                   r;
        ngx_queue_t                 *regex;
    #endif
    
        /* 获取当前 server{} 下管理的所有 location 的 locations 队列 */
        locations = pclcf->locations;
    
        /* 若当前 server{} 没有配置 location,则返回 NGX_OK */
        if (locations == NULL) {
            return NGX_OK;
        }
    
        /* 对该 server 下的 location 进行排序,大致排序如下:
         * 绝对匹配 location,正则 location,命名location,未命名location */
        ngx_queue_sort(locations, ngx_http_cmp_locations);
    
        named = NULL;
        n = 0;
    #if (NGX_PCRE)
        regex = NULL;
        r = 0;
    #endif
    
        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;
    
            /* 若该 location 下有嵌套的 location,则同样对其进行排序,否则直接返回 */
            if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {
                return NGX_ERROR;
            }
    
    #if (NGX_PCRE)
    
            /* 若为正则location,则 r 加 1,同时记录第一个正则 location */
            if (clcf->regex) {
                r++;
    
                if (regex == NULL) {
                    regex = q;
                }
    
                continue;
            }
    
    #endif
    
            /* 若为命名 location,则 n 加 1,同时记录第一个命名 location */
            if (clcf->named) {
                n++;
    
                if (named == NULL) {
                    named = q;
                }
    
                continue;
            }
    
            /* 若当前 location 为未命名 location,则退出循环 */
            if (clcf->noname) {
                break;
            }
        }
    
        /* 上面遍历完排序后的 locations 时,假设此时 q 指向一个未命名 location,则
         * 这里会将 locatons 队列中的所有未命名 location 从 locations 队列中拆分
         * 出来,将其保存到 q 和 tail 构成的双向循环链表中;
         * 而此时 locations 剩下的是除未命名 locations 以外的所有 location */
        if (q != ngx_queue_sentinel(locations)) {
            ngx_queue_split(locations, q, &tail);
        }
        
        /* 从这里之后 locations 队列就不存在有未命名 location 了,配置解析时之所以将
         * 未命名 location 也加入到 locations 队列中,是因为在进行配置合并的时候,可以
         * 让它们也可以继承接受来自上层 location 的相关设置值,因此在配置合并完之后,
         * 这里也就把未命名 location 中 locations 队列中移除了
         * 注意,这里虽然没有对拆分后的未命名 location 进行保存,看似这些数据丢失了,
         * 但是,在其他地方的其他字段,如 limit_except_loc_conf 或脚本引擎里已经保存
         * 了对这些 location 数据的引用 */
    
        if (named) {
            /* 为命名 location 分配内存 */
            clcfp = ngx_palloc(cf->pool,
                               (n + 1) * sizeof(ngx_http_core_loc_conf_t *));
            if (clcfp == NULL) {
                return NGX_ERROR;
            }
    
            cscf->named_locations = clcfp;
            
            /* named location 有两个限定:
             * 1. named location 只会出现在 server 上下文里
             * 2. named location 配置数据(即 clcf)固定保存在 exact 字段下 */
    
            /* 将 locations 队列中的所有命名 location 都存放到上面
             * named_locations 数组指针指向的新分配的内存中 */
            for (q = named;
                 q != ngx_queue_sentinel(locations);
                 q = ngx_queue_next(q))
            {
                lq = (ngx_http_location_queue_t *) q;
    
                /* 将命名 location 的配置项结构体保存到 named_locations 数组中 */
                *(clcfp++) = lq->exact;
            }
    
            *clcfp = NULL;
    
            /* 将命名 location 从 locations 队列中拆分出来 */
            ngx_queue_split(locations, named, &tail);
        }
    
    #if (NGX_PCRE)
    
        if (regex) {
    
            /* 这里为所有的正则匹配 location 分配存储空间 */
            clcfp = ngx_palloc(cf->pool,
                               (r + 1) * sizeof(ngx_http_core_loc_conf_t *));
            if (clcfp == NULL) {
                return NGX_ERROR;
            }
    
            /* 下面的循环中将 locations 队列中所有的正则匹配 location 
             * 存放到 regex_locations 指针数组中 */
            pclcf->regex_locations = clcfp;
    
            for (q = regex;
                 q != ngx_queue_sentinel(locations);
                 q = ngx_queue_next(q))
            {
                lq = (ngx_http_location_queue_t *) q;
    
                *(clcfp++) = lq->exact;
            }
    
            *clcfp = NULL;
    
            /* 然后将正则匹配 location 中 locations 队列中移除,
             * 此时 locations 队列只剩下被称为静态(static)的 location
             * 配置(包括绝对匹配 location 和不属于正则、命名、未命名外的
             * location 配置)*/
            ngx_queue_split(locations, regex, &tail);
        }
    
    #endif
    
        return NGX_OK;
    }
    

    1.1.1 ngx_queue_sort

    /* the stable insertion sort */
    
    void
    ngx_queue_sort(ngx_queue_t *queue,
        ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *))
    {
        ngx_queue_t  *q, *prev, *next;
    
        /* 获取该队列中的第一个节点 */
        q = ngx_queue_head(queue);
    
        /* 若该队列为空,则直接返回 */
        if (q == ngx_queue_last(queue)) {
            return;
        }
    
        /* 遍历该队列中的所有节点 */
        for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) {
    
            prev = ngx_queue_prev(q);
            next = ngx_queue_next(q);
    
            /* 将当前节点从队列中移除 */
            ngx_queue_remove(q);
    
            do {
                /* 将当前节点与它的上一个节点进行比较
                 * 返回 0,则表示插入到 prev 之后 */
                if (cmp(prev, q) <= 0) {
                    break;
                }
                
                /* 返回 1,则继续取 prev 的上一个节点,再次进行比较 */
                prev = ngx_queue_prev(prev);
    
            } while (prev != ngx_queue_sentinel(queue));
    
            /* 将该 q 插入到 prev 的后面 */
            ngx_queue_insert_after(prev, q);
        }
    }
    

    对于 locations 队列中的所有节点(即location),进行比较的函数为 ngx_http_cmp_locations:

    static ngx_int_t
    ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two)
    {
        ngx_int_t                   rc;
        ngx_http_core_loc_conf_t   *first, *second;
        ngx_http_location_queue_t  *lq1, *lq2;
    
        lq1 = (ngx_http_location_queue_t *) one;
        lq2 = (ngx_http_location_queue_t *) two;
    
        /* 获取该location挂载在 exact 或 inclusive 字段下的  
         * ngx_http_core_loc_conf_t 配置结构体 */
        first = lq1->exact ? lq1->exact : lq1->inclusive;
        second = lq2->exact ? lq2->exact : lq2->inclusive;
    
        /* 若前一个 location 为未命名 location,且当前 location 不为
         * 未命名 location 时,则将一个 location(即 first)后移 */
        if (first->noname && !second->noname) {
            /* shift no named locations to the end */
            return 1;
        }
    
        /* 未命名 location 排到后面 */
        if (!first->noname && second->noname) {
            /* shift no named locations to the end */
            return -1;
        }
    
        /* 都为未命名 location 则,排序不变 */
        if (first->noname || second->noname) {
            /* do not sort no named locations */
            return 0;
        }
    
        /* 命名 location 后移 */
        if (first->named && !second->named) {
            /* shift named locations to the end */
            return 1;
        }
    
        if (!first->named && second->named) {
            /* shift named locations to the end */
            return -1;
        }
    
        /* 若两个 location 都为命名 location,则按它们名称的字符序进行排序,
         * 名称字符序大的排到后面 */
        if (first->named && second->named) {
            return ngx_strcmp(first->name.data, second->name.data);
        }
    
    #if (NGX_PCRE)
    
        /* 正则 location 排到后面 */
        if (first->regex && !second->regex) {
            /* shift the regex matches to the end */
            return 1;
        }
    
        if (!first->regex && second->regex) {
            /* shift the regex matches to the end */
            return -1;
        }
    
        if (first->regex || second->regex) {
            /* do not sort the regex matches */
            return 0;
        }
    
    #endif
    
        /* 其他情况,按名称的字符序进行排序 */
        rc = ngx_filename_cmp(first->name.data, second->name.data,
                              ngx_min(first->name.len, second->name.len) + 1);
    
        /* 但有一个特殊处理,即对于出现比较的两个 location 名称相同的情况,
         * 如果存在有绝对匹配 location,则将其放到前面 */
        if (rc == 0 && !first->exact_match && second->exact_match) {
            /* an exact match must be before the same inclusive one */
            return 1;
        }
    
        return rc;
    }
    

    对 locations 队列中任意两个 location 进行排序的规则如下:

    1. 两个比较 location 中的未命名 location(即 noname 为 1)排到后面。
    2. 如果比较的两个 location 都为未命名 location,那么保持原定次序,即保持用户在配置文件里书写的先后顺序(按文件从头到尾)。
    3. 两个比较 location 中的命名 location(即 named 为 1)排到后面。
    4. 如果比较的两个 location 都为命名 location,那么按它们名称的字符序进行排序,即通过函数 strcmp() 比较它们的名称,名称字符序大的排到后面。
    5. 两个比较 location 中的正则匹配 location(即 regex 字段不为空)排到后面。
    6. 如果比较的两个 location 都为正则匹配 location,那么保持原定次序,即保持用户在配置文件里书写的先后顺序(按文件从头到尾)。
    7. 其他情况,按名称的字符序进行排序。但是有一个特别处理,即对于出现比较的两个 loaction 名称相同的情况,如果存在有绝对匹配 location,那么要把它放在前面。

    1.1.2 ngx_http_init_static_location_trees

    static ngx_int_t
    ngx_http_init_static_location_trees(ngx_conf_t *cf,
        ngx_http_core_loc_conf_t *pclcf)
    {
        ngx_queue_t                *q, *locations;
        ngx_http_core_loc_conf_t   *clcf;
        ngx_http_location_queue_t  *lq;
    
        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;
    
            /* 这里是处理 location 嵌套的情况 */
            if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
                return NGX_ERROR;
            }
        }
    
        /* 该函数把同一层次里的名称相同的不同 location 合并在一起。
         * "名称相同,但又是不同的 location"?
         * 这主要是因为 location 有绝对匹配(对应 exact 字段)和包含匹配(也就是前缀
         * 匹配,以指定前缀开头的都匹配上,所以是包含匹配,对应 inclusive 字段)。若
         * 这两个 location 的名称相同(如都为 "/"),则可以把它们共用在一个队列节点里 */
        if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
            return NGX_ERROR;
        }
    
        /* Nginx 对包含匹配的查找采用的是最佳匹配原则,也就是说,如果有两个 location 
         * A 和 B 的包含匹配串分别为 "/" 和 "/images/",那么对于 uri 地址 "/images/top.jpg"
         * 在这两个 location 里的查找将匹配到 location B。为了提高这种查找速度,所以
         * 下面两个函数是把队列转换成 tree */
        /*  */
        ngx_http_create_locations_list(locations, ngx_queue_head(locations));
    
        pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);
        if (pclcf->static_locations == NULL) {
            return NGX_ERROR;
        }
    
        return NGX_OK;
    }
    

    1.1.3 ngx_http_join_exact_locations

    static ngx_int_t
    ngx_http_join_exact_locations(ngx_conf_t *cf, ngx_queue_t *locations)
    {
        ngx_queue_t                *q, *x;
        ngx_http_location_queue_t  *lq, *lx;
    
        q = ngx_queue_head(locations);
    
        while (q != ngx_queue_last(locations)) {
    
            x = ngx_queue_next(q);
    
            lq = (ngx_http_location_queue_t *) q;
            lx = (ngx_http_location_queue_t *) x;
    
            /* 比较名称是否相同 */
            if (lq->name->len == lx->name->len
                && ngx_filename_cmp(lq->name->data, lx->name->data, lx->name->len)
                   == 0)
            {
                /* 若都为绝对匹配或都为包含匹配,则表示出错 */
                if ((lq->exact && lx->exact) || (lq->inclusive && lx->inclusive)) {
                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                                  "duplicate location "%V" in %s:%ui",
                                  lx->name, lx->file_name, lx->line);
    
                    return NGX_ERROR;
                }
    
                /* 将名称相同的两个 location 公用同一个节点 */
                lq->inclusive = lx->inclusive;
    
                /* 移除后一个名称相同的节点*/
                ngx_queue_remove(x);
    
                continue;
            }
    
            /* 若名称不同,则继续取下一个进行比较 */
            q = ngx_queue_next(q);
        }
    
        return NGX_OK;
    }
    

    经过上面的一系列整理后,所有的 location 都被恰当地整理后放置在对应的字段下:

    • 未命名 location 被 limit_except_loc_conf 或脚本引擎引用
    • 命名 location 被放置在 server 配置的 cscf->named_locations 字段下
    • 正则匹配 location 被放置在 server 或 location 配置的 pclcf->regex_locations 字段下
    • 静态匹配(包括绝对匹配和前缀匹配)location 被放置在 server 或 location 配置的 pclcf->static_locations 字段下
  • 相关阅读:
    学习asp.net比较完整的流程
    时间的获取和转换,C#和Sql
    学习一种新编程语言要做的10几个练习
    分布式网络管理优点总结
    点对点网络大解析
    如何阅读他人的程序代码
    WebConfig
    正则表达式30分钟入门教程
    程序员遇到BUG的解释
    在T-SQL语句中访问远程数据库(openrowset/opendatasource/openquery)
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/9247916.html
Copyright © 2011-2022 走看看