nginx通配符哈希表

        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,则从后往前查找每一个关键词。则查找过程如图所示:

nginx通配符哈希表_第1张图片

图:前置通配符哈希表查找过程

对于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.*,则从前往后查找每一个关键词。则查找过程如图所示:

nginx通配符哈希表_第2张图片

图:后置通配符查询流程

对于www.sina.net.com,则先在根哈希表中查找www,而www指向一级哈希表。然后在一级哈希表中查找sina, 而sina指向二级哈希表。因此在二级哈希表中查找net, 因为net是叶子节点了,net指向的空间就是用户数据,查找过程结束。


你可能感兴趣的:(nginx通配符哈希表)