Nginx 匹配树构建
3月 23
NGINX
C , NGINX
字数统计: 1.9k(字)
阅读时长: 9(分)
一 概述 location
是 nginx 的核心指令, 在 ngx_http_block
处理 http
指令过程中, 当所有 http 模块解析完配置后会遍历所有 server
(保存在 cmcf->servers
中)建立匹配匹配树. 配置匹配树构建需要两个步骤: 初始化(ngx_http_init_locations
)和构建匹配树(ngx_http_init_static_location_trees
).
二 匹配树预处理 匹配树预处理是将 location 队列中的 location 进行分类,排序 . 处理结束后会在移除队列中的无名 location, 命名 location 存储在 cscf->named_locations
中,正则 location 存储在 clcf->regex_locations
中. 队列中仅保存绝对匹配和前缀匹配 location(并且是排序好的) .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 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 locations = pclcf->locations; if (locations == NULL ) { return NGX_OK; } 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; 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 ; } } if (q != ngx_queue_sentinel(locations)) { ngx_queue_split(locations, q, &tail); } 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) 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 return NGX_OK; }
三 匹配树构建 1. 匹配树预处理 - 合并前缀匹配与精确匹配 前缀匹配和精确匹配 location 是否应该共存? 在 nginx 中支持前缀匹配与精确匹配相同的情况, 即可以同时存在 location = /a
与 location /a
. 但是如果同时存在两个相同的精确匹配或两个相同的前缀匹配是错误的. 函数 ngx_http_join_exact_locations
有两个功能: 用于判断是否有两个相同的前缀或精确匹配 location; 将相同的 location 合并到精确匹配 location 中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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; } lq->inclusive = lx->inclusive; ngx_queue_remove(x); continue ; } q = ngx_queue_next(q); } return NGX_OK; }
2. 匹配树预处理 - 创建构建匹配树用的 list 函数 ngx_http_create_locations_list
用来将 locations
队列中有相同前缀的前缀匹配 location
放在最短前缀的 list
成员中, 用于后续构建二叉树使用. 此过程是递归过程.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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 ) { 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; 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 ; } ngx_queue_split(locations, q, &tail); ngx_queue_add(&lq->list , &tail); 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); ngx_http_create_locations_list(&lq->list , ngx_queue_head(&lq->list )); ngx_http_create_locations_list(locations, x); }
3. 二叉匹配树构建 nginx 中的匹配树应该是三叉树, 有左右子树与当前节点的子树.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 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); if (ngx_queue_empty(locations)) { goto inclusive; } node->left = ngx_http_create_locations_tree(cf, locations, prefix); if (node->left == NULL ) { return NULL ; } 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; }
4. 总结 在匹配树构建过程中, nginx 会遍历所有的 server
进行前三个步骤构建匹配树, 每个步骤又是递归调用.
使用 ngx_http_init_static_location_trees
函数处理 server 内的 locations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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; if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) { return NGX_ERROR; } } if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) { return NGX_ERROR; } 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; }
四 参考链接