接下来就是在普通散列表的基础上, 分析更复杂的散列表构造. 因为Nginx作为一个Web服务器, 它的各种散列表中的关键字多以字符串为主, 特别是URI域名, 比如 www.ben.com.
既然是这样, 那么Nginx就需要去支持带有通配符的主机域名, 即带"*"的域名, 包括前置通配符, 如 *.test.com; 或是后置通配符, 如www.ben.*. 注意的是, 不支持通配符在中间.
支持通配符的散列表
文章开头提到Nginx支持具有前置通配符或是后置通配符或是没有通配符的域名.
所谓支持通配符的散列表, 就是把基本散列表中元素的关键字, 用去除通配符以后的字符作为关键字加入. 例如, 对于关键字 "www.ben.*", 这样带通配符的情况, 直接建立一个专用的后置通配符散列表, 存储元素的关键字为"www.ben". 这样, 如果要检索 www.ben.com 是否匹配 www.ben.* , 可以用Nginx提供的方法 ngx_hash_find_wc_tail检索, 此函数会把要查询的 www.ben.com 转化为 www.ben 字符串再开始查询.
同理, 对于关键字为 "*.ben.com"的元素, 也直接建立一个前置通配符的散列表, 存储元素的关键字为 "com.ben." , 如果要检索“smtp.ben.com”是否匹配 ".ben.com", 直接使用Nginx提供的 ngx_hash_find_wc_head方法查询. 该方法会把要查询的 "smtp.ben.com"转化为 "com.ben."再开始查询
散列表的结构体定义如下:
typedef struct {
ngx_hash_t hash;
void *value;
} ngx_hash_wildcard_t;
当使用此支持通配符的散列表作为某容器的元素时, 可以使用这个指针指向用户数据. (这个字段是用来存放某个已经达到末尾的通配符url对应的value值,如果通配符url没有达到末尾,这个字段为NULL.)
可以看到这个结构体只是简单的对基本散列表容器的封装.
下面说明一下Nginx对于server_name主机名通配符的支持规则:
首先选择完全匹配的主机名. 比如"www.ben.com"就先在基本散列表中寻找"www.ben.com"是否存在
如果没有找到, 然后选择通配符在前面的主机名. "*.ben.com"
最后, 还没找到的话, 比如 "www.ben.*"
想要在一个搜索中对3个散列表进行寻找, Nginx提供了一个结构体:
typedef struct {
ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail;
} ngx_hash_combined_t;
这个结构体中就包含了三个散列表, 基本散列表, 通配符在头部的散列表, 通配符在最后的散列表.
这里再次提一下, 前置通配符散列表中元素的关键字, 在把*通配符去掉后, 会按照 "." 符号分隔, 并以倒序的方式作为关键字来存储元素.
在进行查询时, 待查询的关键字name也会以同样的方式被转化, 之后再做递归查询.
进行前置通配符或是后置通配符的查询时, 可以用上面提到的函数进行查询. 在对ngx_hash_combined_t结构体进行查找时, 使用ngx_hash_find_combined进行查找. 查找规则符合上面的顺序
也就是: ngx_hash_find ---> ngx_hash_find_wc_head ---> ngx_hash_find_wc_tail
初始化函数.
既然要使用到支持通配符的散列表, 那么先看其初始化函数.
传入的ngx_hash_key_t中关键字已经被处理过(注释会讲到)
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;
//初始化两个动态数组. 接下来会使用. 其中元素都是 ngx_hash_key_t类型的
//curr_names就是会被最终传到ngx_hash_init的数组
if (ngx_array_init(&curr_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
...
//next_names最终被传到ngx_hash_wildcard_init中去的数组
if (ngx_array_init(&next_names, hinit->temp_pool, nelts,
sizeof(ngx_hash_key_t))
!= NGX_OK)
...
//从整体看, 一个大循环. 目的是构造一个ngx_hash_key_t类型的数组, 传递给ngx_hash_init函数去构造一个哈希表.
//从细节看, 这其中会递归调用ngx_hash_wildcard_init函数. 从而最终生成n级哈希表.
//值得注意的是, 这个循环的使用的变量 n 不是递增的. 而是会随i而变化的. i也是一个循环的变量, 下面会看到
for (n = 0; n < nelts; n = i) {
dot = 0;
//寻找第一个 ".", 这样目的是两个: 1. 看是否存在 "," 2. 找到构建这个散列表的所需关键字长度
for (len = 0; len < names[n].key.len; len++) {
if (names[n].key.data[len] == '.') {
dot = 1;
break;
}