内容简介:前面已经分析过HTTP属于nginx的core顶层模块,下面又包括了三部分:
概述
前面已经分析过 nginx解析配置文件的整体流程 ,接下来看查询HTTP配置的流程。
HTTP属于nginx的core顶层模块,下面又包括了三部分:
- main部分配置:即在HTTP块但是又不在任何server、location块中的配置,如下图中的sendfile配置指令。
- server块:在server块内部的配置。
- location块:在location块内部分配置。
解析HTTP模块的入口函数是ngx_http_block,这一点可以从http指令相关的配置看出:
{ ngx_string("http), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_block, 0, 0, NULL }
在这个解析函数的开始,就创建了ngx_http_conf_ctx_t结构体,所以看的出来这个结构体是HTTP模块的第一级配置,它的定义如下:
typedef struct { void **main_conf; void **srv_conf; void **loc_conf; } ngx_http_conf_ctx_t;
下面列举出来这几部分相关的函数以及数据结构:
块 | 入口函数 | 数据结构 |
---|---|---|
http | ngx_http_block | ngx_http_conf_ctx_t |
main | ngx_http_core_main_conf_t | |
server | ngx_http_core_server | ngx_http_core_srv_conf_t |
location | ngx_http_core_location | ngx_http_core_loc_conf_t |
另外,由于HTTP块内的一些配置,作用域可以在多种块中,因此需要涉及到合并配置的流程,即:
- 如果子作用域某配置项在解析过程中未被赋值,则将父作用域的 相同的配置项值拷贝至此配置项里;
- 如果子作用域配置项在解析过程中被赋值了,则保留原 样;如果子作用域配置项和父作用域配置项都没有被初始化,则填入代码中预设的默认值。
相关的合并配置函数列举如下:
块 | 合并函数 |
---|---|
server | ngx_http_merge_servers |
location | ngx_http_merge_locations |
以下具体看看一次HTTP请求如何查找到相关HTTP配置的流程,分为两步:
- 根据Host查找server块
- 根据URI查找location块
根据Host查找server块流程
前面分析 nginx接收HTTP请求流程 中分析到,nginx在接收HTTP请求流程中,将调用ngx_http_process_request_headers函数来处理请求头。
nginx使用一个ngx_http_header_t结构体,定义了哪些请求头需要进行特定的函数回调处理,函数ngx_http_process_request_headers会根据这个表来查询接收到的请求头都需要哪些回调函数来处理:
ngx_http_header_t ngx_http_headers_in[] = { { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host), ngx_http_process_host }, { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection), ngx_http_process_connection }, .... }
可以看到,针对Host这个header,会调用ngx_http_process_host函数,这个函数最终会调用ngx_http_set_virtual_server函数来根据Host头确定对应的server块。
nginx中,不同的server块可以监听同一个地址端口,只要对应的server_name不一样就可以了。
而相同的地址端口,在nginx中对应的是ngx_http_addr_conf_t,内部将同样地址端口的多个不同server_name再组织到一起来:
typedef struct { ngx_hash_combined_t names; ngx_uint_t nregex; ngx_http_server_name_t *regex; } ngx_http_virtual_names_t; struct ngx_http_addr_conf_s { /* the default server configuration for this address:port */ ngx_http_core_srv_conf_t *default_server; ngx_http_virtual_names_t *virtual_names; unsigned ssl:1; unsigned http2:1; unsigned proxy_protocol:1; };
显然,如果相同地址端口的server如果使用链表组织在一起,每一次都是线性时间的查找复杂度,这就太慢了。因此nginx定义了ngx_hash_combined_t这个数据结构,将相同地址端口的server_name组织到一起来:
typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; } ngx_hash_combined_t;
该结构体中有三个成员,区分不同的server_name格式:
- ngx_hash_t hash:精确匹配的哈希表,用于存储没有使用通配符的虚拟主机名,如”www.example.com“。
- ngx_hash_wildcard_t wc_head:前置通配符哈希表,用于存储如” .example.org“和”.example.org“这样的前置通配符虚拟主机名。
- ngx_hash_wildcard_t *wc_tail:后置通配符哈希表,用于存储如”example.*“这样的后置通配符虚拟主机名。
具体这个支持通配符的hash表,不在这里讲解,只谈host的查找顺序:
- 首先查找精确匹配hash表,查找到则返回;
- 接着查找前置通配符hash表,查找到则返回;
- 最后查找后置通配符hash表,查找到则返回;
- 如果以上都没有查找到,落到default_server的server块进行处理。
根据URI查找location块流程
根据Host查找到了server块,紧跟着就是根据URI来查找location块了。
location区分几种格式:
location = / { [ configuration A ] } location / { [ configuration B ] } location /documents/ { [ configuration C ] } location ^~ /images/ { [ configuration D ] } location ~* \.(gif|jpg|jpeg)$ { [ configuration E ] }
在上面的配置例子中:
- 配置A:精确匹配”/” URI,主机名后面不能带任何字符串。
- 配置B:因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求,但是正则和最长字符串会优先匹配。
- 配置C:匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索,只有后面的正则表达式没有匹配到时,这一条才会采用这一条。
- 配置D:匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。
- 配置E:匹配所有以 gif,jpg或jpeg 结尾的请求,然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^~ 到达不了这一条正则。
具体根据URI匹配location的流程如下:
- 首先先检查使用前缀字符定义的location,选择最长匹配的项并记录下来;
- 如果找到了精确匹配的location,也就是使用了=修饰符的location,结束查找,使用它的配置。
- 然后按顺序查找使用正则定义的location,如果匹配则停止查找,使用它定义的配置。
- 如果没有匹配的正则location,则使用前面记录的最长匹配前缀字符location。
可以看到:
- 不包含正则的 location 在配置文件中的顺序不会影响匹配顺序。而包含正则表达式的 location 会按照配置文件中定义的顺序进行匹配。
- 设置为精确匹配 (with = prefix) 的 location 如果匹配请求 URI 的话,此 location 被马上使用,匹配过程结束。
- 在其它只包含普通字符的 location 中,找到和请求 URI 最长的匹配。如果此 server {} 没有包含正则的 location 或者该 location 启用了 ^~ 的话,这个最 长匹配的 location 会被使用。如果此 server {} 中包含正则的 location,则先在 这些正则 location 中进行匹配,如果找到匹配,则使用匹配的正则 location,如果 没找到匹配,依然使用最大匹配的 location。
有了以上的准备,开始看具体的代码实现。
ngx_http_core_loc_conf_s结构体
ngx_http_core_loc_conf_s结构体对应一个location块的配置,相关的成员如下:
struct ngx_http_core_loc_conf_s { ngx_str_t name; /* URI 部分字符串 */ ngx_http_regex_t *regex; /* 正则引擎编译过的 正则表达式对象 */ ... unsigned named:1; /* @ 修饰符 */ unsigned noname:1; /* if () {} */ unsigned exact_match:1; /* = 修饰符 */ unsigned noregex:1; /* ^= 修饰符 */ ... ngx_http_location_tree_node_t *static_location; ngx_http_core_loc_conf_t **regex_location; void **loc_conf; ... ngx_queue_t *locations; /* 连接 `location` 作用域,由 ngx_http_location_queue_t 强制转 换而来 */ };
可以看到,在ngx_http_core_loc_conf_s中使用了几个成员named、noname、exact_match、noregex区分了以上的情况。
ngx_http_location_queue_t
结构体ngx_http_location_queue_t用于临时保存location的队列:
typedef struct { ngx_queue_t queue; ngx_http_core_loc_conf_t *exact; /* exact_match, regex, named, noname */ ngx_http_core_loc_conf_t *inclusive; /* 非 exact 的 location */ ngx_str_t *name; } ngx_http_location_queue_t;
ngx_http_location_tree_node_t
ngx_http_location_tree_node_t结构体是最终存储location的结构体,将location以树状组织在一起,实现location的快速查找:
struct ngx_http_location_tree_node_s { ngx_http_location_tree_node_t *left; ngx_http_location_tree_node_t *right; ngx_http_location_tree_node_t *tree; ngx_http_core_loc_conf_t *exact; // 精确匹配的location配置 ngx_http_core_loc_conf_t *inclusive; // inclusive匹配的location配置 u_char auto_redirect; u_char len; u_char name[1]; };
构建location查找树的流程
在函数ngx_http_block中(该函数即HTTP块的入口函数),将调用两个函数进行location的初始化:
- ngx_http_init_locations:用于完成location的 排序 以及分类存放。
- ngx_http_init_static_location_trees:用于将exact以及inclusive类型的location进一步处理,构造出可以快速访问的树状结构。
ngx_http_init_locations
- 首先调用ngx_queue_sort(locations, ngx_http_cmp_locations)函数对location队列进行排序,排序的结果为:exact(sorted) -> inclusive(sorted) -> regex -> named -> noname,这里说明一下inclusive,它表示URI之间的包含关系,即”/abc/a“这个URI是包含”/abc“的。
- 遍历排序过后的location队列,将其中的noname类型的location分离出队列。
- 将named类型的location分离出来,放到配置的named_locations中。
- 将含有正则的location分离出来,放到配置的regex_locations中。
可以看到,以上流程完成之后,原先的location队列就只剩下exact以及inclusive类型的location了。接着调用ngx_http_init_static_location_trees函数做进一步的处理。
ngx_http_init_static_location_trees
有以下几个流程:
- ngx_http_join_exact_locations:将当前虚拟主机中 uri 字符串完全一致的 exact 和 inclusive 类型的 location 进行合并。
- ngx_http_create_locations_list:将前缀一致的location放到list链表中。
- ngx_http_create_locations_tree:构造location的树结构。
ngx_http_create_locations_list
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) { // 如果不是inclusive类型的location,直接跳过,继续队列中下一个location的处理 ngx_http_create_locations_list(locations, ngx_queue_next(q)); return; } len = lq->name->len; name = lq->name->data; // 从该location的下一个元素开始遍历队列 for (x = ngx_queue_next(q); x != ngx_queue_sentinel(locations); x = ngx_queue_next(x)) { lx = (ngx_http_location_queue_t *) x; // 找到第一个不以q的location做为前缀的location就退出循环 // 比如当前队列location为:/a /ab /abc /b // 这里的q就是/a,x就是/b,中间的/ab和/abc都是以/a为前缀的,不会终止循环 if (len > lx->name->len || ngx_filename_cmp(name, lx->name->data, len) != 0) { break; } } q = ngx_queue_next(q); if (q == x) { // 如果x就是q的下一个元素,说明没有找到前缀匹配的,那么直接进入x进行下次递归调用 ngx_http_create_locations_list(locations, x); return; } // 到了这里说明前面找到有前缀匹配的location了 // 这里将与q相同前缀的节点,分离出队列 ngx_queue_split(locations, q, &tail); // 然后加入到q的list链表中 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; } // 将x从队列中分离出来 ngx_queue_split(&lq->list, x, &tail); // 放回到location队列中 ngx_queue_add(locations, &tail); // 对lq->list做相同的操作 ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list)); // 对从x开始的剩余节点做相同的操作 ngx_http_create_locations_list(locations, x); }
对该函数的几个说明:
- 由于存在递归调用,所以函数开始要做q == ngx_queue_last(locations)的判断,做为递归的终止条件。
- 对于非 inclusive 类型 (此时 locations 队列中也只包含 exact 和 inclusive 类型的 location 节点) 的 location 节点,直接跳过,不做任何整理。
- 从lq开始遍历队列,直到查找到第一个不以q做为前缀的location才退出循环,退出循环时保存当前位置为x。比如当前队列location为:/a /ab /abc /b,这里的q就是/a,x就是/b,中间的/ab和/abc都是以/a为前缀的,不会终止循环。
- 将与lq前缀匹配的队列元素,放到lq的list中,同时针对这个list递归调用ngx_http_create_locations_list函数。
- 继续针对x开始的剩余队列节点递归调用ngx_http_create_locations_list函数。
如下图所示就是ngx_http_create_locations_list调用前后的效果:
ngx_http_create_location_trees
ngx_http_create_location_trees在上面的基础上构造location查找树
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中 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); // 从中间节点将location分为两部分 ngx_queue_split(locations, q, &tail); // 如果分离完毕location队列为空 if (ngx_queue_empty(locations)) { /* * ngx_queue_split() insures that if left part is empty, * then right one is empty too */ // 直接跳到构造inclusive类型的子树 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: // 到这里构造inclusive类型的树保存到tree成员中 // list为空说明没有inclusive类型的location了 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; }
说明:
- 调用ngx_queue_middle快速确定locaiton队列的中间节点。
- 从中间节点将location分为两部分。
- 分别构造左右子树放到成员left和right中。
- 将inclusive类型的location放入到成员tree中。
如下图所示就是ngx-location-create-locations-tree调用前后的效果:
查找location流程
请求的 location 匹配,在请求处理的 FIND_CONFIG 阶段相对应的 checker ngx_http_core_find_config_phase 函数中完成。ngx_http_core_find_config_phase 函数调用 ngx_http_core_find_location 函数完成实际的匹配工作。
本质上就是根据前面构建好的树结构,进行二分查找,不再阐述。
参考资料
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- kubelet 分析源码:启动流程
- 【zookeeper源码】启动流程详解
- View绘制流程源码分析
- gorm查询流程源码分析
- ReactNative源码解析-启动流程
- Android 系统源码-1:Android 系统启动流程源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网站开发案例课堂:HTML5+CSS3+JavaScript网页设计案例课堂
刘玉红 / 2015-1-1 / 68
《网站开发案例课堂:HTML5+CSS3+JavaScript网页设计案例课堂》作者根据在长期教学中积累的网页设计教学经验,完整、详尽地介绍HTML 5 + CSS 3 + JavaScript网页设计技术。 《网站开发案例课堂:HTML5+CSS3+JavaScript网页设计案例课堂》共分24章,分别介绍HTML 5概述、HTML 5网页文档结构、HTML 5网页中的文本和图像、HTML......一起来看看 《网站开发案例课堂:HTML5+CSS3+JavaScript网页设计案例课堂》 这本书的介绍吧!