Nginx 之 Location 的整理
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 进行排序的规则如下:
- 两个比较 location 中的未命名 location(即 noname 为 1)排到后面。
- 如果比较的两个 location 都为未命名 location,那么保持原定次序,即保持用户在配置文件里书写的先后顺序(按文件从头到尾)。
- 两个比较 location 中的命名 location(即 named 为 1)排到后面。
- 如果比较的两个 location 都为命名 location,那么按它们名称的字符序进行排序,即通过函数 strcmp() 比较它们的名称,名称字符序大的排到后面。
- 两个比较 location 中的正则匹配 location(即 regex 字段不为空)排到后面。
- 如果比较的两个 location 都为正则匹配 location,那么保持原定次序,即保持用户在配置文件里书写的先后顺序(按文件从头到尾)。
- 其他情况,按名称的字符序进行排序。但是有一个特别处理,即对于出现比较的两个 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 的整理的更多相关文章
- rewrite规则写法及nginx配置location总结
rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用. 例如http://seanlook.com/a/we/index.php ...
- Nginx之location 匹配规则详解
有些童鞋的误区 1. location 的匹配顺序是“先匹配正则,再匹配普通”. 矫正: location 的匹配顺序其实是“先匹配普通,再匹配正则”.我这么说,大家一定会反驳我,因为按“先匹配普通, ...
- nginx 中location和root
nginx 中location和root,你确定真的明白他们关系? 2016-01-17 14:48 3774人阅读 评论(1) 收藏 举报 分类: linux(17) 版权声明:本文为博主原创文 ...
- Nginx 的 Location 配置指令块
最近一段时间在学习 Nginx ,以前一直对 Nginx 的 Location 配置很头大,最近终于弄出点眉目.总结如下:nginx 配置文件,自下到上分为三种层次分明的结构: | http b ...
- 快速掌握Nginx(二) —— Nginx的Location和Rewrite
1 location详解 1.location匹配规则 Nginx中location的作用是根据Url来决定怎么处理用户请求(转发请求给其他服务器处理或者查找本地文件进行处理).location支持正 ...
- nginx之location的匹配规则
nginx之location的匹配规则 一.语法规则 location [=|~|~*|^~] /uri/ { - } 符号 含义 = 开头表示精确匹配 ^~ 开头表示 uri 以某个常规字符串开头 ...
- Nginx的location匹配规则
一 Nginx的location语法 location [=|~|~*|^~] /uri/ { … } = 严格匹配.如果请求匹配这个location,那么将停止搜索并立即处理此请求 ...
- nginx:location指令中的正则表达式
nginx:location指令中的正则表达式 uri匹配步骤 官网说明https://docs.nginx.com/nginx/admin-guide/web-server/web-server/ ...
- Nginx的location配置规则梳理
Nginx几乎是当下绝大多数公司在用的web应用服务,熟悉Nginx的配置,对于我们日常的运维工作是至关重要的,下面就Nginx的location配置进行梳理: 1)location匹配的是nginx ...
随机推荐
- How to find Oracle EBS Weblogic Server Admin Port and URL
How to find Oracle EBS Weblogic Server Admin Port and URL Weblogic admin portMethod 1 Open the App ...
- [LeetCode] 206. Reverse Linked List ☆(反转链表)
Reverse Linked List 描述 反转一个单链表. 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3-> ...
- Ubuntu 文件和目录常用命令
目标 查看目录内容 ls 切换目录 cd 创建和删除操作 touch rm mkdir 拷贝和移动文件 cp mv 查看文件内容 cat more grep 其他 echo 重定向 > 和 &g ...
- idou老师教你学Istio12 : Istio 实现流量镜像
微服务为我们带来了快速开发部署的优秀特性,而如何降低开发和变更的风险成为了一个问题.Istio的流量镜像,也称为影子流量,是将生产流量镜像拷贝到测试集群或者新的版本中,在引导实时流量之前进行测试,可以 ...
- python学习之基础入门,安装,字符串,数据转换,三元运算符
python基础 我们要开始学习新的编程语言了,加油~~ python是“世界上最好的语言”,学习它当然是认为它是最好的所以我们才学(人生苦短我学python),python运用于不同的领域,采集分析 ...
- Spring-整合MyBatis-声明式事务
12.整合Mybatis 步骤: 导入相关jar包 junit mybatis mysql数据库 spring相关 aop织入 mybatis-spring[new] 编写配置文件 测试 12.1.会 ...
- nginx 普通用户使用80端口启动nginx
方法一: 依次执行如下命令 cd /usr/local/nginx/sbin/ chown root nginx chmod u+s nginx 优点是,方便简单,缺点是,既然sudo权限都不给了.这 ...
- 在 Linux 下使用 scp 命令
将文件或文件夹从网络上的一个主机拷贝到另一个主机当中去. here:在 Linux 下使用 scp 命令 摘要: scp 是安全拷贝协议(Secure Copy Protocol)的缩写, scp 是 ...
- jQuery和原生JS的对比
原生JS的缺点: 不能添加多个入口函数(window.onload),如果添加了多个,后面的会把前面的覆盖掉 原生js的api名字太长,难以书写,不易记住 原生js有的代码冗余 原生js中的属性或者方 ...
- 【题解】士兵训练-C++
题目DescriptionN个士兵排成一队进行军事训练,每个士兵的等级用1…K范围内的数来表示,长官每隔1小时就随便说出M个等级a1,a2…am(1≤ai≤K,M个等级中允许有重复),如果这M个等级组 ...