nginx location的管理以及查找

来源:互联网 发布:433m模块stc单片机解码 编辑:程序博客网 时间:2024/05/16 16:57

【原文链接】 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)的例子做说明:

view plain
  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个字段:

view plain
  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中:
view plain
  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()函数中:
view plain
  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中进行:
view plain
  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:
view plain
  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函数:

view plain
  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结构:
view plain
  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),匹配的规则文章前面已经做过介绍,源码的实现也很简单,这里不再做介绍了。
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 红米4x喇叭坏了怎么办 红米4x电池坏了怎么办 小米电视4a开不了机怎么办 小米x4手机下面三个键失灵怎么办 红米4a一直重启怎么办 红米4a手机一直关机重启怎么办 红米5A手机4G信号差怎么办 红米4x手机黑屏打不开怎么办 红米手机前置摄像头用不了怎么办 红米4a手机电池不耐用怎么办 红米6全网通联通网络不好怎么办 红米手机死机了怎么办不可拆卸电池 厦华电视指示灯亮但打不开机怎么办 oppo一体机的开机键坏了怎么办 小米手机长时间没用开不了机怎么办 红米2a充电坏了怎么办? 红米手机恢复出厂设置失败怎么办 红米关机强行恢复出厂失败怎么办 红米2a太卡了怎么办 红米2a上网好卡怎么办 红米1内部存储空间坏了怎么办 红米3s开关机键失灵怎么办 红米3s下面三个键失灵怎么办 红米3s手机掉水怎么办 红米手机用久了卡怎么办 红米4x手机不支持计步怎么办 红米4x手机耗电快怎么办 红米4a一体机手机死机怎么办 红米4x打王者卡怎么办 红米5 4g信号不稳定怎么办 红米3x玩游戏卡顿怎么办 红米3开不了机了怎么办 苹果手机装了sim卡没反应怎么办 小米手机打电话的图标没了怎么办 租房时和房东没签协议装修怎么办 三星安卓手机忘记锁屏密码怎么办 刷机了支付宝的余额宝钱没了怎么办 手机刷机支付宝里面的钱怎么办 支付宝宽带缴费交错账号了怎么办 电信宽带反回到翼支付里的钱怎么办 天猫盒子连接电视没反应怎么办