相关介绍
在构建一个ngx_hash_wildcard_t的时候,需要对通配符的哪些key进行预处理。这个处理起来比较麻烦。而当有一组key,这些里面既有无通配符的key,也有包含通配符的key的时候。我们就需要构建三个hash表,一个包含普通的key的hash表,一个包含前向通配符的hash表,一个包含后向通配符的hash表(或者也可以把这三个hash表组合成一个ngx_hash_combined_t)。在这种情况下,为了让大家方便的构造这些hash表,nginx提供给了此辅助类型。有了这个就可以清晰了解上一篇博文
源码分析
相关结构
ngx_hash_keys_arrays_t就是我们说的辅助结构,辅助我们更容易去构建ngx_hash_wildcard_t
typedef struct {
ngx_uint_t hsize;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
ngx_array_t keys;
ngx_array_t *keys_hash;
ngx_array_t dns_wc_head;
ngx_array_t *dns_wc_head_hash;
ngx_array_t dns_wc_tail;
ngx_array_t *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;
hsize是我们将要构建的hash表的桶的个数
pool 构建hash表使用的pool
temp_pool在构建这个类型以及最终的三个hash表过程中可能用到临时pool
keys存放所有非通配符key的数组
keys_hash这是个二维数组,第一个维度代表的是bucket的编号,那么keys_hash[i]中存放的是所有的key算出来的hash值对hsize取模以后的值为i的key
dns_wc_head放前向通配符key被处理完成以后的值
dns_wc_tail存放后向通配符key被处理完成以后的值
dns_wc_head_hash该值在调用的过程中用来保存和检测是否有冲突的前向通配符的key值,也就是是否有重复
dns_wc_tail_hash该值在调用的过程中用来保存和检测是否有冲突的后向通配符的key值,也就是是否有重复
结构的初始化
ngx_int_t
ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type)
{
ngx_uint_t asize;
//根据type来选择是大号桶的个数还是小号桶的个数
if (type == NGX_HASH_SMALL) {
asize = 4;
ha->hsize = 107;
} else {
asize = NGX_HASH_LARGE_ASIZE;
ha->hsize = NGX_HASH_LARGE_HSIZE;
}
//初始化非通配符的keys数组,数组容量为asize
if (ngx_array_init(&ha->keys, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
//初始化头部有通配符的keys数组,数组容量为asize
if (ngx_array_init(&ha->dns_wc_head, ha->temp_pool, asize,sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
//初始化尾部有通配符的keys数组,数组容量为asize
if (ngx_array_init(&ha->dns_wc_tail, ha->temp_pool, asize, sizeof(ngx_hash_key_t))
!= NGX_OK)
{
return NGX_ERROR;
}
//初始化二维数组keys_hash
ha->keys_hash = ngx_pcalloc(ha->temp_pool, sizeof(ngx_array_t) * ha->hsize);
if (ha->keys_hash == NULL) {
return NGX_ERROR;
}
//初始化头部通配符冲突检测
ha->dns_wc_head_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_head_hash == NULL) {
return NGX_ERROR;
}
//初始化尾部通配符冲突检测
ha->dns_wc_tail_hash = ngx_pcalloc(ha->temp_pool,
sizeof(ngx_array_t) * ha->hsize);
if (ha->dns_wc_tail_hash == NULL) {
return NGX_ERROR;
}
return NGX_OK;
}
ngx_hash_keys_array_init初始化操作。都是一系列分配内存操作。下面我们来看一看ngx_hash_add_key操作。
ngx_int_t
ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key, void *value,ngx_uint_t flags)
{
size_t len;
u_char *p;
ngx_str_t *name;
ngx_uint_t i, k, n, skip, last;
ngx_array_t *keys, *hwc;
ngx_hash_key_t *hk;
last = key->len;
if (flags & NGX_HASH_WILDCARD_KEY) {
/*
* supported wildcards:
* "*.example.com", ".example.com", and "www.example.*"
*/
n = 0;
for (i = 0; i < key->len; i++) {
//如果含有两个*,就return不处理
if (key->data[i] == '*') {
if (++n > 1) {
return NGX_DECLINED;
}
}
//如果有挨着的两个点也不处理
if (key->data[i] == '.' && key->data[i + 1] == '.') {
return NGX_DECLINED;
}
}
//skip是前省略的,last为后面省略的
//如果是'.example.com'
if (key->len > 1 && key->data[0] == '.') {
skip = 1;
goto wildcard;
}
if (key->len > 2)
{
//如果是'*.example.com'
if (key->data[0] == '*' && key->data[1] == '.')
{
skip = 2;
goto wildcard;
}
//'www.example.*'
if (key->data[i - 2] == '.' && key->data[i - 1] == '*')
{
skip = 0;
last -= 2;
goto wildcard;
}
}
//如果含有*就不能当做exact hash处理了
if (n) {
return NGX_DECLINED;
}
}
/* exact hash */
//处理精确hash查找的key
k = 0;
//计算hashcode
for (i = 0; i < last; i++) {
if (!(flags & NGX_HASH_READONLY_KEY)) {
key->data[i] = ngx_tolower(key->data[i]);
}
k = ngx_hash(k, key->data[i]);
}
k %= ha->hsize;
/* check conflicts in exact hash */
name = ha->keys_hash[k].elts;
//查看是否冲突
if (name) {
for (i = 0; i < ha->keys_hash[k].nelts; i++)
{
if (last != name[i].len) {
continue;
}
if (ngx_strncmp(key->data, name[i].data, last) == 0)
{
return NGX_BUSY;
}
}
} else {
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_ERROR;
}
}
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}
*name = *key;
hk = ngx_array_push(&ha->keys);
if (hk == NULL) {
return NGX_ERROR;
}
hk->key = *key;
hk->key_hash = ngx_hash_key(key->data, last);
hk->value = value;
return NGX_OK;
wildcard:
/* wildcard hash */
//根据skip和last截断后计算hashcode
k = ngx_hash_strlow(&key->data[skip], &key->data[skip], last - skip);
k %= ha->hsize;
//处理'.example.com'
if (skip == 1) {
/* check conflicts in exact hash for ".example.com" */
name = ha->keys_hash[k].elts;
//在key_hash中寻找,看是否有重复
if (name) {
//长度为去掉头与尾
len = last - skip;
for (i = 0; i < ha->keys_hash[k].nelts; i++)
{
if (len != name[i].len) {
continue;
}
if (ngx_strncmp(&key->data[1], name[i].data, len) == 0)
{
return NGX_BUSY;
}
}
} else {
//没有救初始化数组,将该元素放进去
if (ngx_array_init(&ha->keys_hash[k], ha->temp_pool, 4,sizeof(ngx_str_t)) != NGX_OK)
{
return NGX_ERROR;
}
}
name = ngx_array_push(&ha->keys_hash[k]);
if (name == NULL) {
return NGX_ERROR;
}
//长度为last减1 只放入点前面的
name->len = last - 1;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}
//拷贝过去。算是深拷贝
ngx_memcpy(name->data, &key->data[1], name->len);
}
if (skip) {
/*
* convert "*.example.com" to "com.example.\0"
* and ".example.com" to "com.example\0"
*/
//对 *.example.com与.example.com做反转处理
p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}
重置n与len
len = 0;
n = 0;
//反转 将*.example.com处理为.example.com
// 将.example.com处理为.com
for (i = last - 1; i; i--) {
if (key->data[i] == '.') {
ngx_memcpy(&p[n], &key->data[i + 1], len);
n += len;
p[n++] = '.';
len = 0;
continue;
}
len++;
}
// 将.com处理为example.com
if (len) {
ngx_memcpy(&p[n], &key->data[1], len);
n += len;
}
//p是普通字符串,在最后补0
p[n] = '\0';
hwc = &ha->dns_wc_head;
keys = &ha->dns_wc_head_hash[k];
} else {
/* convert "www.example.*" to "www.example\0" */
last++;
//处理后置通配符
p = ngx_pnalloc(ha->temp_pool, last);
if (p == NULL) {
return NGX_ERROR;
}
//直接拷贝www.example.到p,由于last的长度限制
//会把最后一个.变成0(详见ngx_cpystrn函数)
ngx_cpystrn(p, key->data, last);
//最后p为'www.example\0'
hwc = &ha->dns_wc_tail;
keys = &ha->dns_wc_tail_hash[k];
}
/* check conflicts in wildcard hash */
//处理是否有冲突
name = keys->elts;
if (name) {
//注意后面有通配符的的last有+1,所以len会以.结尾
//前置通配符会去掉skip的字符
len = last - skip;
//查看是否有重复
for (i = 0; i < keys->nelts; i++) {
if (len != name[i].len) {
continue;
}
if (ngx_strncmp(key->data + skip, name[i].data, len) == 0) {
return NGX_BUSY;
}
}
} else {
if (ngx_array_init(keys, ha->temp_pool, 4, sizeof(ngx_str_t)) != NGX_OK)
{
return NGX_ERROR;
}
}
//没有重复就放进去
name = ngx_array_push(keys);
if (name == NULL) {
return NGX_ERROR;
}
//长度和刚才比较所用的长度相同
name->len = last - skip;
name->data = ngx_pnalloc(ha->temp_pool, name->len);
if (name->data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(name->data, key->data + skip, name->len);
/* add to wildcard hash */
//假如hash keys里面去,以便初始化通配符字符串
hk = ngx_array_push(hwc);
if (hk == NULL) {
return NGX_ERROR;
}
//注意长度为last - 1
//也就是前置会减去一个字符
//后置减去两个
hk->key.len = last - 1;
hk->key.data = p;
hk->key_hash = 0;
hk->value = value;
return NGX_OK;
}
最后说一下本文的例子
*.test.com --------> com.test.
.test.com --------> com.test
yang.test.* --------> yang.test
总结
把本节与上一节联系起来,就会感觉特别清晰。本节中的key都主要用于初始化,和以后的查找通配符hash表。