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 字段下

Nginx 之 Location 的整理的更多相关文章

  1. rewrite规则写法及nginx配置location总结

    rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用. 例如http://seanlook.com/a/we/index.php ...

  2. Nginx之location 匹配规则详解

    有些童鞋的误区 1. location 的匹配顺序是“先匹配正则,再匹配普通”. 矫正: location 的匹配顺序其实是“先匹配普通,再匹配正则”.我这么说,大家一定会反驳我,因为按“先匹配普通, ...

  3. nginx 中location和root

    nginx 中location和root,你确定真的明白他们关系? 2016-01-17 14:48 3774人阅读 评论(1) 收藏 举报  分类: linux(17)  版权声明:本文为博主原创文 ...

  4. Nginx 的 Location 配置指令块

    最近一段时间在学习 Nginx ,以前一直对 Nginx 的 Location 配置很头大,最近终于弄出点眉目.总结如下:nginx 配置文件,自下到上分为三种层次分明的结构: |    http b ...

  5. 快速掌握Nginx(二) —— Nginx的Location和Rewrite

    1 location详解 1.location匹配规则 Nginx中location的作用是根据Url来决定怎么处理用户请求(转发请求给其他服务器处理或者查找本地文件进行处理).location支持正 ...

  6. nginx之location的匹配规则

    nginx之location的匹配规则 一.语法规则 location [=|~|~*|^~] /uri/ { - } 符号 含义 = 开头表示精确匹配 ^~ 开头表示 uri 以某个常规字符串开头 ...

  7. Nginx的location匹配规则

    一 Nginx的location语法 location [=|~|~*|^~] /uri/ { … } =         严格匹配.如果请求匹配这个location,那么将停止搜索并立即处理此请求 ...

  8. nginx:location指令中的正则表达式

    nginx:location指令中的正则表达式 uri匹配步骤 官网说明https://docs.nginx.com/nginx/admin-guide/web-server/web-server/ ...

  9. Nginx的location配置规则梳理

    Nginx几乎是当下绝大多数公司在用的web应用服务,熟悉Nginx的配置,对于我们日常的运维工作是至关重要的,下面就Nginx的location配置进行梳理: 1)location匹配的是nginx ...

随机推荐

  1. SVN 问题解决之 Working copy path does not exist in repository

    同事的SVN更新时提示某个特定文件提示 Working copy path does not exist in repository svn更新会被这个错误打断,导致无法完全更新 删掉文件再更新仍然有 ...

  2. React/react相关小结

    React React组件由React元素组成,React组件使用React.Component或React.PureComponent来生成:React元素使用JSX的语法来编写或使用React.c ...

  3. TDDL生成全局ID原理

    TDDL 在分布式下的SEQUENCE原理 TDDL大家应该很熟悉了,淘宝分布式数据层.很好的为我们实现了分库分表.Master/Salve.动态数据源配置等功能. 那么分布式之后,数据库自增序列肯定 ...

  4. CentOS7安装CDH 第一章:CentOS7系统安装

    相关文章链接 CentOS7安装CDH 第一章:CentOS7系统安装 CentOS7安装CDH 第二章:CentOS7各个软件安装和启动 CentOS7安装CDH 第三章:CDH中的问题和解决方法 ...

  5. Image Processing and Analysis_21_Scale Space:Scale-space theory A basic tool for analysing structures at different scales——1994

    此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...

  6. Scyther 形式化分析工具资料整理(三)

    1.作者Cas Cremers在做TLS1.3的时候我么发现并没有使用Scyther 形式化丰分析工具对其进行分析,而是使用了 The Tamarin .作者建立了TLS.13的模型. 那么我的目标是 ...

  7. 网络编程基础之TCP编程学习(一)

    网络编程基础了解 socket套接字 socket是一种通讯机制,它包含一整套的调用接口和数据结构的定义,他给应用程序提供了使用如TCP/UDP等网络通讯的手段. linux中的网络编程通过socke ...

  8. Linux教程 网络管理命令Netstat的使用

    Netstat(network statistics) 网络统计命令是一个命令行工具,用于监视网络出入的连接,路由表以接口统计等.Netstat 在所有的Unix或者Linux系统,Windows系统 ...

  9. JDK源码那些事儿之红黑树基础下篇

    说到HashMap,就一定要说到红黑树,红黑树作为一种自平衡二叉查找树,是一种用途较广的数据结构,在jdk1.8中使用红黑树提升HashMap的性能,今天就来说一说红黑树,上一讲已经给出插入平衡的调整 ...

  10. JAVA遇见HTML——JSP篇(JSP内置对象上)

    action:表单交给哪个动作去处理 MIME类型: 浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档:因此服务器设置正确以将正确的MIME类型附加到响应对象的头部是非常重要的. 语法 ...