nginx location的管理以及查找

【原文链接】 http://blog.csdn.net/fengmo_q/article/details/6683377

关于nginx代码解析,我师兄雕梁的博客(http://simohayha.javaeye.com)有一系列的文章可以阅读。我这里将只介绍他博客里没有关注到的或者讲述不详细的,但是我个人又认为是nginx里面比较重要的东西。在这一篇文章里,我将介绍nginx关于location的处理,大家都知道Nginx配置文件里面会有很多的location,nginx的配置指令的作用域可以分为 main,server,location这3个种,实际上这3者不是依次包含的关系,而是相互独立的关系,比如一个只具有main级别作用域的指令,是不能写在某个server或者location内的,模块的某个指令可以同时具有main,server,location这3种作用域,另外每个模块有 main,srv,loc这3个级别的配置,一个模块的main级别的配置对所有的server和location都是共享的,srv级别的配置对所有 location都是共享的,location只有自己独立的loc级别的配置,这就是为什么一个模块的srv和loc级别的配置需要merge,而 main级别的配置不需要merge的原因。这里看起来有点绕,区分一下main,server,location分别作为一种作用域级别和一个主体,类似于形容词和名字的区别,nginx的配置关系还是不难理解的。

        一般来说一个请求url过来,nginx会将它解析到某一个location来处理。这个解析的过程实际上根据location的配置基本可以分为字符串匹配和正则表达式匹配这2种。对于location的组织方式,最简单的就是直接将它们保存为一个链表,解析url的时候一个一个遍历即可找到相应location,但是这样效率太低,对像nginx这种高性能的服务器来说是完全不可取的,nginx将字符串匹配的location组织成了一个三叉的字符串排序树,而且建立的时候也考虑了树的平衡性。文章后面我讲详细介绍源码的实现。

        首先我来大概的介绍一下location的种类和匹配规则,以nginx wiki(http://wiki.nginx.org/HttpCoreModule#location)的例子做说明:

  1. location  = / {  
  2.   # matches the query / only.  
  3.   [ configuration A ]   
  4. }  
  5. location  / {  
  6.   # matches any query, since all queries begin with /, but regular  
  7.   # expressions and any longer conventional blocks will be  
  8.   # matched first.  
  9.   [ configuration B ]   
  10. }  
  11. location ^~ /images/ {  
  12.   # matches any query beginning with /images/ and halts searching,  
  13.   # so regular expressions will not be checked.  
  14.   [ configuration C ]   
  15. }  
  16. location ~* \.(gif|jpg|jpeg)$ {  
  17.   # matches any request ending in gif, jpg, or jpeg. However, all  
  18.   # requests to the /images/ directory will be handled by  
  19.   # Configuration C.     
  20.   [ configuration D ]   
  21. }  
  22.   
  23. location  @named {  
  24.   # Such locations are not used during normal processing of requests,   
  25.   # they are intended only to process internally redirected requests (for example error_page, try_files).  
  26.   [ configuration E ]   
  27. }  

        可以看到上面的例子中有5种不同类型的location,其中第4个带 “~” 号前缀的为需要正则匹配的location,nginx在进行url解析时对这5种不同类型的location具有不同的优先级规则,大致的规则如下:

1,字符串精确匹配到一个带 “=” 号前缀的location,则停止,且使用这个location的配置;

2,字符串匹配剩下的非正则和非特殊location,如果匹配到某个带 "^~" 前缀的location,则停止;

3,正则匹配,匹配顺序为location在配置文件中出现的顺序。如果匹配到某个正则location,则停止,并使用这个location的配置;否则,使用步骤2中得到的具有最大字符串匹配的location配置。

       例如,对下面的请求有:

1, /   ->   精确匹配到第1个location,匹配停止,使用configuration A
2,/some/other/url    ->  首先前缀部分字符串匹配到了第2个location,然后进行正则匹配,显然没有匹配上,则使用第2个location的配置configurationB
3,/images /1.jpg  ->  首先前缀部分字符串匹配到了第2个location,但是接着对第3个location也前缀匹配上了,而且这时已经是配置文件里面对这个url的最大字符串匹配了,并且location带有 "^~" 前缀,则不再进行正则匹配,最终使用configuration C
4,/some/other/path/to/1.jpg  -> 首先前缀部分同样字符串匹配到了第2个location,然后进行正则匹配,这时正则匹配成功,则使用congifuration D

      nginx的url匹配规则实际上有点不妥,大部分情况下一个url必须先进行字符串匹配,然后再做正则匹配,但是实际上如果先做正则匹配,没有匹配上再 做字符串匹配,在很多情况下可以节省掉做字符串匹配的时间。不管怎样,先来看一下nginx源码里面的实现,在介绍匹配location过程之前,先来介 绍一下nginx里面对location的组织方式,实际上在配置解析阶段,nginx将字符串匹配的location和正则匹配的location分别 存储在http core模块的loc配置ngx_http_core_loc_conf_t结构的下面2个字段:

  1. ngx_http_location_tree_node_t   *static_locations;  
  2. (NGX_PCRE)  
  3. ngx_http_core_loc_conf_t       **regex_locations;  
  4. if  
从这2个字段的类型可以看出,字符串匹配的location被组织成了一个location tree,而正则匹配的location只是一个数组,location tree和regex_locations数组建立过程在ngx_http_block中:
  1. /* create location trees */  
  2.   
  3.     for (s = 0; s < cmcf->servers.nelts; s++) {  
  4.   
  5.         clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];  
  6.   
  7.         if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {  
  8.             return NGX_CONF_ERROR;  
  9.         }  
  10.   
  11.         if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {  
  12.             return NGX_CONF_ERROR;  
  13.         }  
  14.     }  
        经过配置的读取之后,所有server都被保存在http core模块的main配置中的servers数组中,而每个server里面的location都被按配置中出现的顺序保存在http core模块的loc配置的locations队列中,上面的代码中先对每个server的location进行排序和分类处理,这一步发生在 ngx_http_init_location()函数中:
  1. static ngx_int_t  
  2. ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,  
  3.     ngx_http_core_loc_conf_t *pclcf)  
  4. {  
  5.   ...  
  6.     locations = pclcf->locations;  
  7.   
  8.   ...  
  9.     /* 按照类型排序location,排序完后的队列:  (exact_match 或 inclusive) (排序好的,如果某个exact_match名字和inclusive location相同,exact_match排在前面) 
  10.        |  regex(未排序)| named(排序好的)  |  noname(未排序)*/  
  11.     ngx_queue_sort(locations, ngx_http_cmp_locations);  
  12.   
  13.     named = NULL;  
  14.     n = 0;  
  15. #if (NGX_PCRE)  
  16.     regex = NULL;  
  17.     r = 0;  
  18. #endif  
  19.   
  20.     for (q = ngx_queue_head(locations);  
  21.          q != ngx_queue_sentinel(locations);  
  22.          q = ngx_queue_next(q))  
  23.     {  
  24.         lq = (ngx_http_location_queue_t *) q;  
  25.   
  26.         clcf = lq->exact ? lq->exact : lq->inclusive;  
  27.         /* 由于可能存在nested location,也就是location里面嵌套的location,这里需要递归的处理一下当前location下面的nested location */  
  28.         if (ngx_http_init_locations(cf, NULL, clcf) != NGX_OK) {  
  29.             return NGX_ERROR;  
  30.         }  
  31.   
  32. #if (NGX_PCRE)  
  33.   
  34.         if (clcf->regex) {  
  35.             r++;  
  36.   
  37.             if (regex == NULL) {  
  38.                 regex = q;  
  39.             }  
  40.   
  41.             continue;  
  42.         }  
  43.   
  44. #endif  
  45.   
  46.         if (clcf->named) {  
  47.             n++;  
  48.   
  49.             if (named == NULL) {  
  50.                 named = q;  
  51.             }  
  52.   
  53.             continue;  
  54.         }  
  55.   
  56.         if (clcf->noname) {  
  57.             break;  
  58.         }  
  59.     }  
  60.   
  61.     if (q != ngx_queue_sentinel(locations)) {  
  62.         ngx_queue_split(locations, q, &tail);  
  63.     }  
  64.     /* 如果有named location,将它们保存在所属server的named_locations数组中 */  
  65.     if (named) {  
  66.         clcfp = ngx_palloc(cf->pool,  
  67.                            (n + 1) * sizeof(ngx_http_core_loc_conf_t **));  
  68.         if (clcfp == NULL) {  
  69.             return NGX_ERROR;  
  70.         }  
  71.   
  72.         cscf->named_locations = clcfp;  
  73.   
  74.         for (q = named;  
  75.              q != ngx_queue_sentinel(locations);  
  76.              q = ngx_queue_next(q))  
  77.         {  
  78.             lq = (ngx_http_location_queue_t *) q;  
  79.   
  80.             *(clcfp++) = lq->exact;  
  81.         }  
  82.   
  83.         *clcfp = NULL;  
  84.   
  85.         ngx_queue_split(locations, named, &tail);  
  86.     }  
  87.   
  88. #if (NGX_PCRE)  
  89.     /* 如果有正则匹配location,将它们保存在所属server的http core模块的loc配置的regex_locations 数组中, 
  90.        这里和named location保存位置不同的原因是由于named location只能存在server里面,而regex location可以作为nested location */  
  91.     if (regex) {  
  92.   
  93.         clcfp = ngx_palloc(cf->pool,  
  94.                            (r + 1) * sizeof(ngx_http_core_loc_conf_t **));  
  95.         if (clcfp == NULL) {  
  96.             return NGX_ERROR;  
  97.         }  
  98.   
  99.         pclcf->regex_locations = clcfp;  
  100.   
  101.         for (q = regex;  
  102.              q != ngx_queue_sentinel(locations);  
  103.              q = ngx_queue_next(q))  
  104.         {  
  105.             lq = (ngx_http_location_queue_t *) q;  
  106.   
  107.             *(clcfp++) = lq->exact;  
  108.         }  
  109.   
  110.         *clcfp = NULL;  
  111.   
  112.         ngx_queue_split(locations, regex, &tail);  
  113.     }  
  114.   
  115. #endif  
  116.   
  117.     return NGX_OK;  
  118. }  
  119.           
       上面的步骤将正则匹配的location保存好了,location tree的建立在ngx_http_init_static_location_trees中进行:
  1. static ngx_int_t  
  2. ngx_http_init_static_location_trees(ngx_conf_t *cf,  
  3.     ngx_http_core_loc_conf_t *pclcf)  
  4. {  
  5.     ngx_queue_t                *q, *locations;  
  6.     ngx_http_core_loc_conf_t   *clcf;  
  7.     ngx_http_location_queue_t  *lq;  
  8.   
  9.     locations = pclcf->locations;  
  10.   
  11.     if (locations == NULL) {  
  12.         return NGX_OK;  
  13.     }  
  14.   
  15.     if (ngx_queue_empty(locations)) {  
  16.         return NGX_OK;  
  17.     }  
  18.     /* 这里也是由于nested location,需要递归一下 */  
  19.     for (q = ngx_queue_head(locations);  
  20.          q != ngx_queue_sentinel(locations);  
  21.          q = ngx_queue_next(q))  
  22.     {  
  23.         lq = (ngx_http_location_queue_t *) q;  
  24.   
  25.         clcf = lq->exact ? lq->exact : lq->inclusive;  
  26.   
  27.         if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {  
  28.             return NGX_ERROR;  
  29.         }  
  30.     }  
  31.     /* join队列中名字相同的inclusive和exact类型location,也就是如果某个exact_match的location名字和普通字符串匹配的location名字相同的话, 
  32.        就将它们合到一个节点中,分别保存在节点的exact和inclusive下,这一步的目的实际是去重,为后面的建立排序树做准备 */  
  33.     if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {  
  34.         return NGX_ERROR;  
  35.     }  
  36.     /* 递归每个location节点,得到当前节点的名字为其前缀的location的列表,保存在当前节点的list字段下 */  
  37.     ngx_http_create_locations_list(locations, ngx_queue_head(locations));  
  38.   
  39.     /* 递归建立location三叉排序树 */  
  40.     pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);  
  41.     if (pclcf->static_locations == NULL) {  
  42.         return NGX_ERROR;  
  43.     }  
  44.   
  45.     return NGX_OK;  
  46. }  
        经过ngx_http_init_location()函数处理之后,locations队列已经是排好序的了,建立三叉树的过程的主要工作都在ngx_http_create_locations_list()和ngx_http_create_locations_tree()中完成,这2个 函数都是递归函数,第1个函数递归locations队列中的每个节点,得到以当前节点的名字为前缀的location,并保存在当前节点的list字段 下,例如,对下列location:
  1. location  /xyz {  
  2.   
  3. }  
  4.   
  5. location  = /xyz {  
  6.   
  7. }  
  8. location  /xyza {  
  9.   
  10. }  
  11.   
  12. location  /xyzab {  
  13.   
  14. }  
  15. location  /xyzb {  
  16.   
  17. }  
  18. location  /abc {  
  19.   
  20. }  
  21. location  /efg {  
  22.   
  23. }  
  24. location  /efgaa {  
  25.   
  26. }  
        排序的结果为/abc  /efg   /efgaa  =/xyz  /xyz  /xyza /xyzab /xyzb,去重后结果为 /abc  /efg   /efgaa   /xyz  /xyza /xyzab/xyzb,ngx_http_create_locations_list()执行后的结果为:


          最后,来看下ngx_http_create_locations_tree函数:

  1. static ngx_http_location_tree_node_t *  
  2. ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations,  
  3.     size_t prefix)  
  4. {  
  5.    ...  
  6.     /* 根节点为locations队列的中间节点 */  
  7.     q = ngx_queue_middle(locations);  
  8.   
  9.     lq = (ngx_http_location_queue_t *) q;  
  10.     len = lq->name->len - prefix;  
  11.       
  12.     node = ngx_palloc(cf->pool,  
  13.                       offsetof(ngx_http_location_tree_node_t, name) + len);  
  14.     if (node == NULL) {  
  15.         return NULL;  
  16.     }  
  17.   
  18.     node->left = NULL;  
  19.     node->right = NULL;  
  20.     node->tree = NULL;  
  21.     node->exact = lq->exact;  
  22.     node->inclusive = lq->inclusive;  
  23.   
  24.     node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect)  
  25.                            || (lq->inclusive && lq->inclusive->auto_redirect));  
  26.   
  27.     node->len = (u_char) len;  
  28.     ngx_memcpy(node->name, &lq->name->data[prefix], len);  
  29.   
  30.     /* 从中间节点开始断开 */  
  31.     ngx_queue_split(locations, q, &tail);  
  32.   
  33.     if (ngx_queue_empty(locations)) {  
  34.         /* 
  35.          * ngx_queue_split() insures that if left part is empty, 
  36.          * then right one is empty too 
  37.          */  
  38.         goto inclusive;  
  39.     }  
  40.   
  41.     /* 从locations左半部分得到左子树 */  
  42.     node->left = ngx_http_create_locations_tree(cf, locations, prefix);  
  43.     if (node->left == NULL) {  
  44.         return NULL;  
  45.     }  
  46.   
  47.     ngx_queue_remove(q);  
  48.   
  49.     if (ngx_queue_empty(&tail)) {  
  50.         goto inclusive;  
  51.     }  
  52.    
  53.   
  54.     /* 从locations右半部分得到右子树 */  
  55.     node->right = ngx_http_create_locations_tree(cf, &tail, prefix);  
  56.     if (node->right == NULL) {  
  57.         return NULL;  
  58.     }  
  59.   
  60. inclusive:  
  61.   
  62.     if (ngx_queue_empty(&lq->list)) {  
  63.         return node;  
  64.     }  
  65.   
  66.     /* 从list队列得到tree子树 */  
  67.     node->tree = ngx_http_create_locations_tree(cf, &lq->list, prefix + len);  
  68.     if (node->tree == NULL) {  
  69.         return NULL;  
  70.     }  
  71.   
  72.     return node;  
  73. }  
          location tree节点的ngx_http_location_tree_node_s结构:
  1. struct ngx_http_location_tree_node_s {  
  2.     ngx_http_location_tree_node_t   *left;  
  3.     ngx_http_location_tree_node_t   *right;  
  4.     ngx_http_location_tree_node_t   *tree;  
  5.   
  6.     ngx_http_core_loc_conf_t        *exact;  
  7.     ngx_http_core_loc_conf_t        *inclusive;  
  8.   
  9.     u_char                           auto_redirect;  
  10.     u_char                           len;  
  11.     u_char                           name[1];  
  12. };  

         location tree结构用到的是left,right,tree 这3个字段, location tree实际上是一个三叉的字符串排序树,而且这里如果某个节点只考虑左,右子树,它是一颗平衡树,它的建立过程有点类似于一颗平衡排序二叉树的建立过程,先排序再用二分查找找到的节点顺序插入,ngx_http_location_tree_node_s的tree节点也是一颗平衡排序树,它是用该节点由ngx_http_create_locations_list()得到的list建立的,也就是该节点的名字是它的tree子树里面的所有节点名字的前缀,所以tree子树里面的所有节点的名字不用保存公共前缀,而且查找的时候,如果是转向tree节点的话,也是不需要再比较父节点的那段字符串了。
         ngx_http_create_locations_tree()函数写的很清晰,它有一个参数是队列locations,它返回一颗三叉树,根节点为locations的中间节点,其左子树为locations队列的左半部分建立的location tree,右子树为location队列的右半部分建立的tree,tree节点为该根节点的list队列建立的tree。

       最终建立的location tree如下(为了方便阅读,图中列出了tree节点的完整名字):


       location的匹配发生在nginx请求处理的NGX_HTTP_FIND_CONFIG_PHASE阶段(请求的处理所有PHASE可以看这篇博客 http://simohayha.iteye.com/blog/670326),匹配的规则文章前面已经做过介绍,源码的实现也很简单,这里不再做介绍了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值