nginx服务器的配置文件支持前置通配符或者后置通配符(例如: *.baidu.com, www.sina.*), 不支持通配符在中间位置。在解析nginx.conf时,如果server_name配置项存在通配符时,会把通配符存放到通配符哈希表中。
一、初始化哈希表
对于后置通配符www.baidu.com.*, 则nginx以.为界限分割每一个单词,得到"www", "baidu", "com";然后将这三个单词存放到后置通配符哈希表中。
对于前置通配符*.baidu.com.cn, 则nginx先翻转通配符,得到cn.com.baidu, 然后以.为界限分割每一个单词,得到 "baidu", "com","cn";然后将这三个单词存放到前置通配符哈希表中。
函数ngx_hash_wildcard_init用于递归的初始化通配符哈希表。即每一个哈希元素指向的内容又是一个哈希表。
//功能: 递归初始化前置通配符或者后置通配符哈希表。
// 在普通哈希表中,哈希元素指向的是用户数据。而在通配符哈希表中,哈希元素指向是子哈希表。
// 例如:www.baidu.com.*, 则www哈希元素指向的是baidu所在的子哈希表;而baidu哈希元素指向的是com所在
// 的子哈希表;com哈希元素指向的则为真正的用户数据
//参数: names 通配符构成的数组,例如:www.baidu.*; www.sina.*; *.domain.com; *.example.com
// 这4个通配符构成的数组
// nelts 数组元素个数
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
{
size_t len, dot_len;
ngx_uint_t i, n, dot;
ngx_array_t curr_names, next_names;
ngx_hash_key_t *name, *next_name;
ngx_hash_init_t h;
ngx_hash_wildcard_t *wdc;
//例如:www.baidu.com, www.sina.com, org.china.com
//则curr_names存放的是www; org两个元素。
//后面会用这个数组填充当前哈希表
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
//例如:www.baidu.com, www.sina.com, org.china.com
//则next_names存放剩余的是baidu.com; sina.com;hina.com
//后面会使用这个数组填充子哈希表
if (ngx_array_init(&next_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
//遍历所有数组元素
for (n = 0; n < nelts; n = i)
{
dot = 0;
//查找到以.分割的单词
for (len = 0; len < names[n].key.len; len++)
{
if (names[n].key.data[len] == '.')
{
dot = 1;
break;
}
}
//从数组中获取一个元素,构造用于构造当前哈希表的元素
name = ngx_array_push(&curr_names);
if (name == NULL)
{
return NGX_ERROR;
}
name->key.len = len;
name->key.data = names[n].key.data;
name->key_hash = hinit->key(name->key.data, name->key.len);
name->value = names[n].value;
dot_len = len + 1;
if (dot)
{
len++;
}
next_names.nelts = 0;
//例如:www.baidu.com, 则names[n].key.len为www.baidu.com长度,
//而len为www的长度。因此把剩余的baidu.com放入到next_names数组。目的是用于填充子哈希表
if (names[n].key.len != len)
{
next_name = ngx_array_push(&next_names);
if (next_name == NULL)
{
return NGX_ERROR;
}
next_name->key.len = names[n].key.len - len;
next_name->key.data = names[n].key.data + len;
next_name->key_hash = 0;
next_name->value = names[n].value;
}
//假设有www.baidu.com, www.sina.com两个元素
//则由于第一个关键字www相同,因此把baidu.com, sina.com放入到同一个子哈希表中
for (i = n + 1; i < nelts; i++)
{
if (ngx_strncmp(names[n].key.data, names[i].key.data, len) != 0)
{
break;
}
if (!dot
&& names[i].key.len > len
&& names[i].key.data[len] != '.')
{
break;
}
next_name = ngx_array_push(&next_names);
if (next_name == NULL)
{
return NGX_ERROR;
}
next_name->key.len = names[i].key.len - dot_len;
next_name->key.data = names[i].key.data + dot_len;
next_name->key_hash = 0;
next_name->value = names[i].value;
}
//子数组中有数据,则说明需要构造子哈希表
if (next_names.nelts)
{
h = *hinit;
h.hash = NULL;
//递归构造子哈希表
if (ngx_hash_wildcard_init(&h, (ngx_hash_key_t *) next_names.elts,
next_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
wdc = (ngx_hash_wildcard_t *) h.hash;
//如果当前元素是最后一个关键字,则该哈希元素的value指向的用户数据
if (names[n].key.len == len)
{
wdc->value = names[n].value;
}
//如果当前元素不是最后一个关键字,则当前哈希元素的value指向的是子哈希表
name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
}
else if (dot)
{
name->value = (void *) ((uintptr_t) name->value | 1);
}
}
//初始化当前哈希表
if (ngx_hash_init(hinit, (ngx_hash_key_t *) curr_names.elts,
curr_names.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
例如存在由
图: 通配符哈希表初始化
从图中可以看出,每一个哈希元素指向的内容又是一个哈希表。直到叶子节点哈希元素,叶子节点哈希元素指向的空间才是用户真实数据
二、前置通配符查询操作
创建通配符哈希表时,采用递归的方式创建哈希表。而查询过程也是一样,递归查询。
函数ngx_hash_find_wc_head用来在前置通配符哈希表中查找到key对应的value值
//功能: 在前置通配符哈希表中,查找key对应的value值。
//参数: name 关键字key
// len 关键字key对应的长度
//例如: 例如查找www.baidu.com.cn是否匹配*.baidu.com.cn
//返回值: key对应的value值
void * ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void *value;
ngx_uint_t i, n, key;
n = len;
//从后往前查找以.分割的每一个单词。
//例如: 查找www.baidu.com.cn是否匹配*.baidu.com.cn
while (n)
{
if (name[n - 1] == '.')
{
break;
}
n--;
}
key = 0;
//计算单词的哈希值
for (i = n; i < len; i++)
{
key = ngx_hash(key, name[i]);
}
//查找这个单词是否在哈希表中存在
//如果在哈希表中查找到key,则返回value值。
//这里返回的value值有可能是最终关键字*.baidu.com.cn对应的value
//也可能是指向下一个子哈希表的指针
value = ngx_hash_find(&hwc->hash, key, &name[n], len - n);
if (value)
{
if ((uintptr_t) value & 2)
{
if (n == 0)
{
/* "example.com" */
if ((uintptr_t) value & 1)
{
return NULL;
}
hwc = (ngx_hash_wildcard_t *)
((uintptr_t) value & (uintptr_t) ~3);
return hwc->value;
}
hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
//返回的是一个指向下一个关键字的哈希表指针,则递归的查找。
value = ngx_hash_find_wc_head(hwc, name, n - 1);
if (value)
{
return value;
}
return hwc->value;
}
if ((uintptr_t) value & 1)
{
if (n == 0)
{
/* "example.com" */
return NULL;
}
return (void *) ((uintptr_t) value & (uintptr_t) ~3);
}
return value;
}
return hwc->value;
}
例如要在通配符哈希表中查找www.domain.com是否匹配*.domain.com,则从后往前查找每一个关键词。则查找过程如图所示:
图:前置通配符哈希表查找过程
对于www.domain.com,则先在根哈希表中查找com,而com指向一级哈希表。然后在一级哈希表中查找domain,因为domain是叶子节点了,domain指向的空间就是用户数据,查找过程结束。
三、后置通配符查询操作
后置通配符哈希表查询操作,跟前置通配符查找操作基本类似。差别是后置通配符是从第一个关键字往后查询。例如: 查找www.baidu.com.cn是否匹配"www.baidu.com.*", 则先查询www, 接着查询baidu,最后查询com. 函数ngx_hash_find_wc_tail用来在后置通配符哈希表中查找到key对应的value值。
//功能: 在后置通配符哈希表中,查找key对应的value值。
//参数: name 关键字key
// len 关键字key对应的长度
//返回值: key对应的value值
void * ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len)
{
void *value;
ngx_uint_t i, key;
key = 0;
//查找以.分割的每个单词,并计算这个单词的key值
//例如: 查找www.baidu.com.cn是否匹配www.baidu.com.*, 则这里为计算www的key值
for (i = 0; i < len; i++)
{
if (name[i] == '.')
{
break;
}
key = ngx_hash(key, name[i]);
}
if (i == len)
{
return NULL;
}
//如果在哈希表中查找到key,则返回value值。
//这里返回的value值有可能是最终关键字www.baidu.com.*对应的value
//也可能是指向下一个哈希表的指针
value = ngx_hash_find(&hwc->hash, key, name, i);
if (value)
{
if ((uintptr_t) value & 2)
{
i++;
hwc = (ngx_hash_wildcard_t *) ((uintptr_t) value & (uintptr_t) ~3);
//返回的是一个指向下一个关键字的哈希表指针,则递归的查找。
value = ngx_hash_find_wc_tail(hwc, &name[i], len - i);
if (value)
{
return value;
}
return hwc->value;
}
return value;
}
return hwc->value;
}
例如要在通配符哈希表中查找www.sina.net.com是否匹配www.sina.net.*,则从前往后查找每一个关键词。则查找过程如图所示:
图:后置通配符查询流程
对于www.sina.net.com,则先在根哈希表中查找www,而www指向一级哈希表。然后在一级哈希表中查找sina, 而sina指向二级哈希表。因此在二级哈希表中查找net, 因为net是叶子节点了,net指向的空间就是用户数据,查找过程结束。