nginx通配符哈希表

来源:互联网 发布:编辑网页的软件 编辑:程序博客网 时间:2024/06/09 14:49

        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指向的空间就是用户数据,查找过程结束。


1 0