一、location配置解析
nginx中location的配置示例:
server {
listen 9999;
location /a1 {
return 200 $uri;
}
location /aa {
return 200 $uri;
}
location /aac {
return 200 $uri;
}
location /aad {
return 200 $uri;
}
location /ab {
return 200 $uri;
}
location =/abc {
return 200 $uri;
}
}
关键函数:
static char *ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
代码解析:
static char *
ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
/*每次调用location时,都需要重新将NGX_HTTP_MODULE类型的模块重新创建一遍内存*/
ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}
for (i = 0; cf->cycle->modules[i]; i++) {
if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) {
continue;
}
module = cf->cycle->modules[i]->ctx;
if (module->create_loc_conf) {
ctx->loc_conf[cf->cycle->modules[i]->ctx_index] =
module->create_loc_conf(cf);
if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
//配置解析
//3个参数 例如: location ~* /abc(.*) {}
if (cf->args->nelts == 3) {
} else {
//2个参数 例如: location ^/abc {}
}
//表示location嵌套
if (cf->cmd_type == NGX_HTTP_LOC_CONF) {
}
//将本次的配置clcf放入到父结构体下的locations队列中
if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
return rv;
}
上述代码解析后,关注点如下:
3个参数: location ~* /abc/a(.*)
- = :
- clcf->name = *name
- clcf->exact_match = 1
- ^~ :
- clcf->name = *name
- clcf->noregex = 1
- ~ :
- ngx_http_core_regex_location(cf, clcf, name, 0) 大小写敏感 regex=ngx_compile
- clcf->regex = ngx_http_regex_compile(cf, &rc)
- clcf->name = *name
- ~* :
- ngx_http_core_regex_location(cf, clcf, name, 1) 大小写不敏感 regex=ngx_compile
- clcf->regex = ngx_http_regex_compile(cf, &rc)
- clcf->name = *name
location ~*/test
2个参数:
- =开头
- clcf->name = *name 已经将前面的符号过滤掉
- clcf->exact_match = 1
- ^~开头
- clcf->noregex = 1
- ~开头
- *紧随其后
- clcf->regex = ngx_http_regex_compile(cf, &rc);
- clcf->name = *name
- 其他
- clcf->regex = ngx_http_regex_compile(cf, &rc);
- clcf->name = *name
- 其他
- clcf->name = *name;
- @开头:
- clcf->named = 1
以下情形不可以location嵌套:
1.精确匹配的前提下
2.@开始的location
3.非正则的情况下, 嵌套的location开始字符需一致
最终将所有的location都放入到了队列中
二、location内部布局
内部布局的触发点为: 在http配置块解析完之后即在ngx_http_block函数中,代码如下:
for (s = 0; s < cmcf->servers.nelts; s++) {
clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
//将locations队列进行排序,并剔除named,regex,if/limit_except类型的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;
}
}
看一下locations排序过程:
ngx_queue_sort(locations, 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;
first = lq1->exact ? lq1->exact : lq1->inclusive;
second = lq2->exact ? lq2->exact : lq2->inclusive;
//noname标识, 在if limit_except中会置1
//如果发现noname的,将其放到队列的最后面
if (first->noname && !second->noname) {
/* shift no named locations to the end */
return 1;
}
if (!first->noname && second->noname) {
/* shift no named locations to the end */
return -1;
}
if (first->noname || second->noname) {
/* do not sort no named locations */
return 0;
}
//named在location @xxx{} 使用内部跳转的时候
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;
}
if (first->named && second->named) {
return ngx_strcmp(first->name.data, second->name.data);
}
//正则处理
#if (NGX_PCRE)
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一致,需要将精确匹配的放到前面
if (rc == 0 && !first->exact_match && second->exact_match) {
/* an exact match must be before the same inclusive one */
return 1;
}
return rc;
}
排序完之后的队列结果为:前缀匹配(如果有精确匹配的,放到前面),正则,named(内部跳转),noname(if/limit_except)
只保留前缀匹配的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)
{
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;
}
}
//将后面noname的剔除掉
if (q != ngx_queue_sentinel(locations)) {
ngx_queue_split(locations, q, &tail);
}
//上述循环会计算named的个数,将配置放入到cscf->named_locations中
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)
//上述循环会计算regex的个数,将配置放入到cscf->regex_locations中
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
}
- 马上进入最关键的tree的初始化,将这个阶段分开处理会更好理解
- 精确与前缀合并
- 创建list队列
- 初始化静态树
下面对上述几个步骤详细分析
1.精确与前缀合并
//这个函数比较容易理解,具体含义是 如果发现精确匹配和前缀匹配的url一致,需要将前缀匹配的配置放到精确匹配的配置中
if (ngx_http_join_exact_locations(cf, locations) != NGX_OK) {
return NGX_ERROR;
}
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;
//发现两个location的name一样 将后面的inclusive前缀配置,
//放到前一个节点中的inclusive配置中
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队列
主要功能:将具有相同前缀的url放到list队列中
ngx_http_create_locations_list(locations, ngx_queue_head(locations));
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) {
//注意:代表如果这个lq节点为精确匹配的节点,需要跳过。
//上来先不要理解这个地方,等这个函数全部看完在回头看具体意思:
//如果本次的开始节点为精确节点,会跳到下一个节点处理,意味着 精确节点的list一定是空
//如果是空的话,在创建树的时候就不会有tree节点(哈哈,有点难理解!)
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;
//寻找和lq节点前缀一致的节点
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;
}
//在q节点截断并将截断后的队列放入到lq的list队列中
ngx_queue_split(locations, q, &tail);
ngx_queue_add(&lq->list, &tail);
if (x == ngx_queue_sentinel(locations)) {
//如果上述处理的x节点已经为最后一个节点了,就将lq->list作为一个全新的队列进行处理
ngx_http_create_locations_list(&lq->list, ngx_queue_head(&lq->list));
return;
}
//此时表示x后续还有节点,需要将后续节点放回到 原 队列中
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);
}
文字确实不好理解,看如下图片,会共容易理解:
上述图片描述了list创建的过程,这个一定要好好理解!
3.初始化静态树
树的初始化,完全取决与上述的list列表
pclcf->static_locations = ngx_http_create_locations_tree(cf, locations, 0);
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);
//如果前面的队列为空(上述获取中间节点,如果偶数个,会获取中间偏后的那个节点,所有前面队列为空那么后面队列也一定为空)
//此次去创建tree节点
if (ngx_queue_empty(locations)) {
/*
* ngx_queue_split() insures that if left part is empty,
* then right one is empty too
*/
goto inclusive;
}
//如果前面队列不为空,创建左子树
node->left = ngx_http_create_locations_tree(cf, locations, prefix);
if (node->left == NULL) {
return NULL;
}
//将中间节点去掉,注意 在代码执行的后续过程还会走到inclusive中,此时使用的是lq节点
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;
}
文字枯燥,上图:
1已中间节点为分隔,前半部分作为左子树,后半部分作为右子树
2如果前半部分为空,创建tree(前缀相同)树
3踢掉中间节点,后半部分创建右子树
4为中间节点的list创建tree树
三、location如何使用
在大阶段FIND LOCTION中使用
static ngx_int_t ngx_http_core_find_location(ngx_http_request_t *r) {}
static ngx_int_t
ngx_http_core_find_static_location(ngx_http_request_t *r,
ngx_http_location_tree_node_t *node)
{
u_char *uri;
size_t len, n;
ngx_int_t rc, rv;
len = r->uri.len;
uri = r->uri.data;
rv = NGX_DECLINED;
for ( ;; ) {
if (node == NULL) {
return rv;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"test location: \"%*s\"",
(size_t) node->len, node->name);
n = (len <= (size_t) node->len) ? len : node->len;
rc = ngx_filename_cmp(uri, node->name, n);
//如果发现小于0,需要遍历左子树,大于0,需要遍历右子树
if (rc != 0) {
node = (rc < 0) ? node->left : node->right;
continue;
}
//当前url的长度大于节点的长度
if (len > (size_t) node->len) {
//如果该节点为前缀类型,需要继续使用该节点的tree节点
if (node->inclusive) {
r->loc_conf = node->inclusive->loc_conf;
rv = NGX_AGAIN;
node = node->tree;
uri += n;
len -= n;
continue;
}
/* exact only */
//当时这个分支很难理解,其实这样理解就可以了
//在创建list的时候,最开始的时候跳过的精确匹配节点,由于精确匹配的节点下不会有tree节点,所以该精确节点下只会有 左子树 或 右子树。 此时url的长度还大于node的长度,所以只有执向右子树
node = node->right;
continue;
}
if (len == (size_t) node->len) {
//精确匹配,结束
if (node->exact) {
r->loc_conf = node->exact->loc_conf;
return NGX_OK;
} else {
//前缀匹配, 看后续是否有正则匹配(非^~开头的前缀)
r->loc_conf = node->inclusive->loc_conf;
return NGX_AGAIN;
}
}
/* len < node->len */
if (len + 1 == (size_t) node->len && node->auto_redirect) {
r->loc_conf = (node->exact) ? node->exact->loc_conf:
node->inclusive->loc_conf;
rv = NGX_DONE;
}
//长度小于, 就按照大于的方式理解即可
node = node->left;
}
}
匹配的最后结果:
location查找的优先级关系:
1,优先精确匹配
2,^~的前缀匹配
3,正则匹配,若不成功,使用2中的结果