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; }
例如存在由<www.baidu.com.*>, <www.sina.net.*> 后置通配符,以及<*.domain.com>, <*.example.org>前置通配符; 这4个通配符组成的哈希表布局如下图:
图: 通配符哈希表初始化
从图中可以看出,每一个哈希元素指向的内容又是一个哈希表。直到叶子节点哈希元素,叶子节点哈希元素指向的空间才是用户真实数据
二、前置通配符查询操作
创建通配符哈希表时,采用递归的方式创建哈希表。而查询过程也是一样,递归查询。
函数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指向的空间就是用户数据,查找过程结束。