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