nginx location 代码详解

一、location配置解析

nginx中location的配置示例:

server {
    listen 9999;

    location /a1 {
        return 200 $uri;
    }   
    location /aa {
        return 200 $uri;
    }   
    location /aac {
        return 200 $uri;
    }   
    location /aad {
        return 200 $uri;
    }   
    location /ab {
        return 200 $uri;
    }   
    location =/abc {
        return 200 $uri;
    }
}

关键函数:

static char *ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);

代码解析:

static char *
ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
    /*每次调用location时,都需要重新将NGX_HTTP_MODULE类型的模块重新创建一遍内存*/
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->loc_conf == NULL) {
        return NGX_CONF_ERROR;
    }    

    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[i]->ctx;

        if (module->create_loc_conf) {
            ctx->loc_conf[cf->cycle->modules[i]->ctx_index] =
                                                   module->create_loc_conf(cf);
            if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }



    //配置解析
    //3个参数 例如: location  ~*   /abc(.*) {}
    if (cf->args->nelts == 3) {
    } else {
    //2个参数 例如: location ^/abc {}
    }

    //表示location嵌套
    if (cf->cmd_type == NGX_HTTP_LOC_CONF) {
    }


    //将本次的配置clcf放入到父结构体下的locations队列中
    if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return rv;
}

上述代码解析后,关注点如下:

3个参数: location  ~*  /abc/a(.*)

  • = :
    • clcf->name = *name
    • clcf->exact_match = 1
  • ^~ :
    • clcf->name = *name
    • clcf->noregex = 1
  • ~ :
    • ngx_http_core_regex_location(cf, clcf, name, 0) 大小写敏感 regex=ngx_compile
    • clcf->regex = ngx_http_regex_compile(cf, &rc)
    • clcf->name = *name
  • ~* :
    • ngx_http_core_regex_location(cf, clcf, name, 1) 大小写不敏感 regex=ngx_compile
    • clcf->regex = ngx_http_regex_compile(cf, &rc)
    • clcf->name = *name

 

location ~*/test

2个参数:

  • =开头
    • clcf->name = *name 已经将前面的符号过滤掉
    • clcf->exact_match = 1
  • ^~开头
    • clcf->noregex = 1
  • ~开头
    • *紧随其后
      • clcf->regex = ngx_http_regex_compile(cf, &rc);
      • clcf->name = *name
    • 其他
      • clcf->regex = ngx_http_regex_compile(cf, &rc);
      • clcf->name = *name
  • 其他
    • clcf->name = *name;
      • @开头:
        • clcf->named = 1

 

以下情形不可以location嵌套:

1.精确匹配的前提下

2.@开始的location

3.非正则的情况下, 嵌套的location开始字符需一致

 

最终将所有的location都放入到了队列中

 

二、location内部布局

内部布局的触发点为: 在http配置块解析完之后即在ngx_http_block函数中,代码如下:

    for (s = 0; s < cmcf->servers.nelts; s++) {

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

        //将locations队列进行排序,并剔除named,regex,if/limit_except类型的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;
        }    
    }

 

看一下locations排序过程:

ngx_queue_sort(locations, 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;

    first = lq1->exact ? lq1->exact : lq1->inclusive;
    second = lq2->exact ? lq2->exact : lq2->inclusive;


    //noname标识, 在if limit_except中会置1
    //如果发现noname的,将其放到队列的最后面
    if (first->noname && !second->noname) {
        /* shift no named locations to the end */
        return 1;
    }

    if (!first->noname && second->noname) {
        /* shift no named locations to the end */
        return -1;
    }

    if (first->noname || second->noname) {
        /* do not sort no named locations */
        return 0;
    }



    //named在location @xxx{} 使用内部跳转的时候
    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;
    }

    if (first->named && second->named) {
        return ngx_strcmp(first->name.data, second->name.data);
    }


    //正则处理
#if (NGX_PCRE)

    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一致,需要将精确匹配的放到前面
    if (rc == 0 && !first->exact_match && second->exact_match) {
        /* an exact match must be before the same inclusive one */
        return 1;
    }

    return rc;
}

排序完之后的队列结果为:前缀匹配(如果有精确匹配的,放到前面),正则,named(内部跳转),noname(if/limit_except)

 

只保留前缀匹配的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)
{

     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;

        if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {
            return NGX_ERROR;
        }

#if (NGX_PCRE)

        if (clcf->regex) {
            r++;

            if (regex == NULL) {
                regex = q;
            }

            continue;
        }

#endif   
    if (clcf->named) {
            n++;

            if (named == NULL) {
                named = q;
            }

            continue;
        }

        if (clcf->noname) {
            break;
        }
    }

    //将后面noname的剔除掉

    if (q != ngx_queue_sentinel(locations)) {
        ngx_queue_split(locations, q, &tail);
    }


    //上述循环会计算named的个数,将配置放入到cscf->named_locations中
    if (named) {
        clcfp = ngx_palloc(cf->pool,
                           (n + 1) * sizeof(ngx_http_core_loc_conf_t *));
        if (clcfp == NULL) {
            return NGX_ERROR;
        }

        cscf->named_locations = clcfp;

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

            *(clcfp++) = lq->exact;
        }

        *clcfp = NULL;

        ngx_queue_split(locations, named, &tail);
    }
    
    #if (NGX_PCRE)

    //上述循环会计算regex的个数,将配置放入到cscf->regex_locations中
    if (regex) {

        clcfp = ngx_palloc(cf->pool,
                           (r + 1) * sizeof(ngx_http_core_loc_conf_t *));
        if (clcfp == NULL) {
            return NGX_ERROR;
        }

        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;

        ngx_queue_split(locations, regex, &tail);
    }

#endif


}
  1. 马上进入最关键的tree的初始化,将这个阶段分开处理会更好理解
    1. 精确与前缀合并
    2. 创建list队列
    3. 初始化静态树

下面对上述几个步骤详细分析

1.精确与前缀合并

//这个函数比较容易理解,具体含义是   如果发现精确匹配和前缀匹配的url一致,需要将前缀匹配的配置放到精确匹配的配置中

if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
    return NGX_ERROR;
}
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;


        //发现两个location的name一样 将后面的inclusive前缀配置,
        //放到前一个节点中的inclusive配置中
        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;
            }

            lq->inclusive = lx->inclusive;
            //在队列中删掉后面的这个节点
            ngx_queue_remove(x);

            continue;
        }

        q = ngx_queue_next(q);
    }

    return NGX_OK;
}

2.创建list队列

  主要功能:将具有相同前缀的url放到list队列中

 

ngx_http_create_locations_list(locations, ngx_queue_head(locations));


static void
ngx_http_create_locations_list(ngx_queue_t *locations, ngx_queue_t *q)
{
    u_char                     *name;
    size_t                      len;
    ngx_queue_t                *x, tail;
    ngx_http_location_queue_t  *lq, *lx;

    if (q == ngx_queue_last(locations)) {
        return;
    }

    lq = (ngx_http_location_queue_t *) q;

    if (lq->inclusive == NULL) {
        //注意:代表如果这个lq节点为精确匹配的节点,需要跳过。
        //上来先不要理解这个地方,等这个函数全部看完在回头看具体意思:
        //如果本次的开始节点为精确节点,会跳到下一个节点处理,意味着 精确节点的list一定是空
        //如果是空的话,在创建树的时候就不会有tree节点(哈哈,有点难理解!)
        ngx_http_create_locations_list(locations, ngx_queue_next(q));
        return;
    }

    len = lq->name->len;
    name = lq->name->data;

    for (x = ngx_queue_next(q);
         x != ngx_queue_sentinel(locations);
         x = ngx_queue_next(x))
    {
        lx = (ngx_http_location_queue_t *) x;


        //寻找和lq节点前缀一致的节点
        if (len > lx->name->len
            || ngx_filename_cmp(name, lx->name->data, len) != 0)
        {
            break;
        }
    }

    q = ngx_queue_next(q);

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

    //在q节点截断并将截断后的队列放入到lq的list队列中
    ngx_queue_split(locations, q, &tail);
    ngx_queue_add(&lq->list, &tail);

    if (x == ngx_queue_sentinel(locations)) {
        //如果上述处理的x节点已经为最后一个节点了,就将lq->list作为一个全新的队列进行处理
        ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
        return;
    }

    //此时表示x后续还有节点,需要将后续节点放回到  原  队列中
    ngx_queue_split(&lq->list, x, &tail);
    ngx_queue_add(locations, &tail);

    //分开处理,新形成的
    ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
    //后续分开的
    ngx_http_create_locations_list(locations, x);
}

文字确实不好理解,看如下图片,会共容易理解:

 

 

 

 

上述图片描述了list创建的过程,这个一定要好好理解!

3.初始化静态树

  树的初始化,完全取决与上述的list列表

  

pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);


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;

    //取队列的中间节点
    q = ngx_queue_middle(locations);

    lq = (ngx_http_location_queue_t *) q;
    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;
    //前缀节点一样的字符,在子节点中就没有再次存储
    ngx_memcpy(node->name, &lq->name->data[prefix], len);


    //按照中间节点进行分割, 前面的作为左子树, 后面的作为右子树
    ngx_queue_split(locations, q, &tail);

    //如果前面的队列为空(上述获取中间节点,如果偶数个,会获取中间偏后的那个节点,所有前面队列为空那么后面队列也一定为空)
    //此次去创建tree节点
    if (ngx_queue_empty(locations)) {
        /*
         * ngx_queue_split() insures that if left part is empty,
         * then right one is empty too
         */
        goto inclusive;
    }

    //如果前面队列不为空,创建左子树
    node->left = ngx_http_create_locations_tree(cf, locations, prefix);
    if (node->left == NULL) {
        return NULL;
    }

    //将中间节点去掉,注意 在代码执行的后续过程还会走到inclusive中,此时使用的是lq节点
    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;
    }
    //创建前缀节点
    node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len);
    if (node->tree == NULL) {
        return NULL;
    }

    return node;
}


文字枯燥,上图:

1已中间节点为分隔,前半部分作为左子树,后半部分作为右子树

2如果前半部分为空,创建tree(前缀相同)树

3踢掉中间节点,后半部分创建右子树

4为中间节点的list创建tree树

 

 

三、location如何使用

在大阶段FIND LOCTION中使用

static ngx_int_t ngx_http_core_find_location(ngx_http_request_t *r) {}

static ngx_int_t
ngx_http_core_find_static_location(ngx_http_request_t *r,
    ngx_http_location_tree_node_t *node)
{
    u_char     *uri;
    size_t      len, n;
    ngx_int_t   rc, rv;

    len = r->uri.len;
    uri = r->uri.data;

    rv = NGX_DECLINED;

    for ( ;; ) {

        if (node == NULL) {
            return rv;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "test location: \"%*s\"",
                       (size_t) node->len, node->name);

        n = (len <= (size_t) node->len) ? len : node->len;

        rc = ngx_filename_cmp(uri, node->name, n);
        //如果发现小于0,需要遍历左子树,大于0,需要遍历右子树
        if (rc != 0) {
            node = (rc < 0) ? node->left : node->right;

            continue;
        }


        //当前url的长度大于节点的长度
        if (len > (size_t) node->len) {

            //如果该节点为前缀类型,需要继续使用该节点的tree节点
            if (node->inclusive) {

                r->loc_conf = node->inclusive->loc_conf;
                rv = NGX_AGAIN;

                node = node->tree;
                uri += n;
                len -= n;

                continue;
            }

            /* exact only */
            //当时这个分支很难理解,其实这样理解就可以了
            //在创建list的时候,最开始的时候跳过的精确匹配节点,由于精确匹配的节点下不会有tree节点,所以该精确节点下只会有 左子树 或 右子树。 此时url的长度还大于node的长度,所以只有执向右子树
            node = node->right;

            continue;
        }

        if (len == (size_t) node->len) {

            //精确匹配,结束
            if (node->exact) {
                r->loc_conf = node->exact->loc_conf;
                return NGX_OK;

            } else {
            //前缀匹配, 看后续是否有正则匹配(非^~开头的前缀)
                r->loc_conf = node->inclusive->loc_conf;
                return NGX_AGAIN;
            }
        }

        /* len < node->len */

        if (len + 1 == (size_t) node->len && node->auto_redirect) {

            r->loc_conf = (node->exact) ? node->exact->loc_conf:
                                          node->inclusive->loc_conf;
            rv = NGX_DONE;
        }
        //长度小于,  就按照大于的方式理解即可
        node = node->left;
    }
}

匹配的最后结果:

location查找的优先级关系:

1,优先精确匹配

2,^~的前缀匹配

3,正则匹配,若不成功,使用2中的结果

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值