Open vislee opened 3 years ago
http模块提供了map模块,用以把字符串映射成对应的值赋到指定变量。map的提供相当于提供了go语言switch-case部分能力。
switch
case
例如,官方文档的例子,根据UA的不同把结果赋值到mobile变量,其他指令就可以使用mobile变量,例如access_log 指令if条件。
map $http_user_agent $mobile { default 0; "~Opera Mini" 1; }
对应的golang代码:
var mobile int switch { case regexp.MatchString(".*Opera Mini.*", http_user_agent): mobile = 1 default: mobile = 0 }
因为http_user_agent是固定的,所以更像switch http_user_agent {} 形式。
switch http_user_agent {}
map模块提供了3个指令,map_hash_max_size 和 map_hash_bucket_size如果要生效要配置在map前面。
map_hash_max_size
map_hash_bucket_size
map
指令解析:调用模块ctx的函数指针ngx_http_map_create_conf创建保存配置的结构体,该结构体实际上只是保存了hash表的大小。并没有保存map指令数据。解析map指令时会查找并调用ngx_http_map_block函数,该函数先把第一个参数编译保存在ngx_http_map_ctx_t结构体,再把第二个变量编译注册到cmcf->variables_keys数组中(该数组最终被编译为一个hash表),该注册变量回调函数是ngx_http_map_variable,数据是ngx_http_map_ctx_t结构体,其实指令解析最重要的事情就是填充该结构体。因为map指令是个block指令,包含了其他指令的。包含的指令的解析会调用回调函数ngx_http_map解析。
ngx_http_map_create_conf
ngx_http_map_block
cmcf->variables_keys
ngx_http_map_variable
请求处理:如果没有使用map定义的变量(map指令第二个参数),该模块内容即使配置了也不会被使用。所以入口就是ngx_http_map_block函数定义的变量的回调函数ngx_http_map_variable,该函数先调用ngx_http_complex_value获取第一个变量的值,根据该值从解析map 的 block中的指令保存的结构体ngx_http_map_ctx_t中查找匹配的key对应的val,该val可以是常量也可以是个变量,如果是变量则调用ngx_http_complex_value获取变量对应的值。
ngx_http_complex_value
map指令是该模块的重点,该指令接受2个参数,是个block指令。我们先来看下map指令的解析函数ngx_http_map_block:
// 解析map block指令的上下文结构体 typedef struct { ngx_hash_keys_arrays_t keys; // 保存map block里面的key,支持前后缀匹配。 ngx_array_t *values_hash; // 保存map block里面key val的val。 #if (NGX_PCRE) ngx_array_t regexes; // 解析map block里面正则表达式的key,以~ 或 ~*开始的。 #endif ngx_http_variable_value_t *default_value; ngx_conf_t *cf; unsigned hostnames:1; unsigned no_cacheable:1; // 变量是否要缓存(每个请求仅计算一次) } ngx_http_map_conf_ctx_t; // 保存map指令 typedef struct { ngx_http_map_t map; ngx_http_complex_value_t value; // 对应map指令string的内容,也就是switch key。 ngx_http_variable_value_t *default_value; // map block中 default对应的值 ngx_uint_t hostnames; /* unsigned hostnames:1 */ // key是否是hostname } ngx_http_map_ctx_t; // 解析map指令 static char * ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_map_conf_t *mcf = conf; char *rv; ngx_str_t *value, name; ngx_conf_t save; ngx_pool_t *pool; ngx_hash_init_t hash; ngx_http_map_ctx_t *map; ngx_http_variable_t *var; ngx_http_map_conf_ctx_t ctx; ngx_http_compile_complex_value_t ccv; // hash表参数,通过另外2个指令设置。因此想要指令设置的起作用而不是默认,就需先与map设置。 if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) { mcf->hash_max_size = 2048; } if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) { mcf->hash_bucket_size = ngx_cacheline_size; } else { mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size, ngx_cacheline_size); } map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t)); if (map == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &map->value; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } // 变量作为全局变量注册 name = value[2]; if (name.data[0] != '$') { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid variable name \"%V\"", &name); return NGX_CONF_ERROR; } name.len--; name.data++; var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); if (var == NULL) { return NGX_CONF_ERROR; } // 获取变量时的回调函数 var->get_handler = ngx_http_map_variable; var->data = (uintptr_t) map; pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (pool == NULL) { return NGX_CONF_ERROR; } ctx.keys.pool = cf->pool; ctx.keys.temp_pool = pool; if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize); if (ctx.values_hash == NULL) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #if (NGX_PCRE) if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t)) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } #endif ctx.default_value = NULL; ctx.cf = &save; ctx.hostnames = 0; ctx.no_cacheable = 0; save = *cf; cf->pool = pool; cf->ctx = &ctx; cf->handler = ngx_http_map; // 在ngx_conf_parse函数中调用 cf->handler_conf = conf; // 解析map指令block中的指令。 rv = ngx_conf_parse(cf, NULL); *cf = save; if (rv != NGX_CONF_OK) { ngx_destroy_pool(pool); return rv; } if (ctx.no_cacheable) { var->flags |= NGX_HTTP_VAR_NOCACHEABLE; } map->default_value = ctx.default_value ? ctx.default_value: &ngx_http_variable_null_value; map->hostnames = ctx.hostnames; hash.key = ngx_hash_key_lc; hash.max_size = mcf->hash_max_size; hash.bucket_size = mcf->hash_bucket_size; hash.name = "map_hash"; hash.pool = cf->pool; if (ctx.keys.keys.nelts) { hash.hash = &map->map.hash.hash; hash.temp_pool = NULL; if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } } if (ctx.keys.dns_wc_head.nelts) { ngx_qsort(ctx.keys.dns_wc_head.elts, (size_t) ctx.keys.dns_wc_head.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts, ctx.keys.dns_wc_head.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; } if (ctx.keys.dns_wc_tail.nelts) { ngx_qsort(ctx.keys.dns_wc_tail.elts, (size_t) ctx.keys.dns_wc_tail.nelts, sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts, ctx.keys.dns_wc_tail.nelts) != NGX_OK) { ngx_destroy_pool(pool); return NGX_CONF_ERROR; } map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash; } #if (NGX_PCRE) if (ctx.regexes.nelts) { map->map.regex = ctx.regexes.elts; map->map.nregex = ctx.regexes.nelts; } #endif ngx_destroy_pool(pool); return rv; } // 解析map指令block中的指令 static char * ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) { u_char *data; size_t len; ngx_int_t rv; ngx_str_t *value, v; ngx_uint_t i, key; ngx_http_map_conf_ctx_t *ctx; ngx_http_complex_value_t cv, *cvp; ngx_http_variable_value_t *var, **vp; ngx_http_compile_complex_value_t ccv; ctx = cf->ctx; value = cf->args->elts; if (cf->args->nelts == 1 && ngx_strcmp(value[0].data, "hostnames") == 0) { ctx->hostnames = 1; // map block内 匹配key为host,支持前后缀匹配 return NGX_CONF_OK; } if (cf->args->nelts == 1 && ngx_strcmp(value[0].data, "volatile") == 0) { ctx->no_cacheable = 1; // map指令第二个参数的value不缓存 return NGX_CONF_OK; } if (cf->args->nelts != 2) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of the map parameters"); return NGX_CONF_ERROR; } if (ngx_strcmp(value[0].data, "include") == 0) { return ngx_conf_include(cf, dummy, conf); } key = 0; for (i = 0; i < value[1].len; i++) { key = ngx_hash(key, value[1].data[i]); } key %= ctx->keys.hsize; vp = ctx->values_hash[key].elts; if (vp) { for (i = 0; i < ctx->values_hash[key].nelts; i++) { if (vp[i]->valid) { data = vp[i]->data; len = vp[i]->len; } else { cvp = (ngx_http_complex_value_t *) vp[i]->data; data = cvp->value.data; len = cvp->value.len; } if (value[1].len != len) { continue; } if (ngx_strncmp(value[1].data, data, len) == 0) { var = vp[i]; goto found; } } } else { if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4, sizeof(ngx_http_variable_value_t *)) != NGX_OK) { return NGX_CONF_ERROR; } } var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t)); if (var == NULL) { return NGX_CONF_ERROR; } v.len = value[1].len; v.data = ngx_pstrdup(ctx->keys.pool, &value[1]); if (v.data == NULL) { return NGX_CONF_ERROR; } ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = ctx->cf; ccv.value = &v; ccv.complex_value = &cv; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } if (cv.lengths != NULL) { cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_complex_value_t)); if (cvp == NULL) { return NGX_CONF_ERROR; } *cvp = cv; var->len = 0; var->data = (u_char *) cvp; var->valid = 0; } else { var->len = v.len; var->data = v.data; var->valid = 1; } var->no_cacheable = 0; var->not_found = 0; vp = ngx_array_push(&ctx->values_hash[key]); if (vp == NULL) { return NGX_CONF_ERROR; } *vp = var; found: if (ngx_strcmp(value[0].data, "default") == 0) { if (ctx->default_value) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate default map parameter"); return NGX_CONF_ERROR; } ctx->default_value = var; return NGX_CONF_OK; } #if (NGX_PCRE) if (value[0].len && value[0].data[0] == '~') { ngx_regex_compile_t rc; ngx_http_map_regex_t *regex; u_char errstr[NGX_MAX_CONF_ERRSTR]; regex = ngx_array_push(&ctx->regexes); if (regex == NULL) { return NGX_CONF_ERROR; } value[0].len--; value[0].data++; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); if (value[0].data[0] == '*') { value[0].len--; value[0].data++; rc.options = NGX_REGEX_CASELESS; } rc.pattern = value[0]; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; regex->regex = ngx_http_regex_compile(ctx->cf, &rc); if (regex->regex == NULL) { return NGX_CONF_ERROR; } regex->value = var; return NGX_CONF_OK; } #endif if (value[0].len && value[0].data[0] == '\\') { value[0].len--; value[0].data++; } rv = ngx_hash_add_key(&ctx->keys, &value[0], var, (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0); if (rv == NGX_OK) { return NGX_CONF_OK; } if (rv == NGX_DECLINED) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid hostname or wildcard \"%V\"", &value[0]); } if (rv == NGX_BUSY) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "conflicting parameter \"%V\"", &value[0]); } return NGX_CONF_ERROR; }
概述
http模块提供了map模块,用以把字符串映射成对应的值赋到指定变量。map的提供相当于提供了go语言
switch
-case
部分能力。例如,官方文档的例子,根据UA的不同把结果赋值到mobile变量,其他指令就可以使用mobile变量,例如access_log 指令if条件。
对应的golang代码:
因为http_user_agent是固定的,所以更像
switch http_user_agent {}
形式。代码分析
指令解析:调用模块ctx的函数指针
ngx_http_map_create_conf
创建保存配置的结构体,该结构体实际上只是保存了hash表的大小。并没有保存map指令数据。解析map指令时会查找并调用ngx_http_map_block
函数,该函数先把第一个参数编译保存在ngx_http_map_ctx_t结构体,再把第二个变量编译注册到cmcf->variables_keys
数组中(该数组最终被编译为一个hash表),该注册变量回调函数是ngx_http_map_variable
,数据是ngx_http_map_ctx_t结构体,其实指令解析最重要的事情就是填充该结构体。因为map指令是个block指令,包含了其他指令的。包含的指令的解析会调用回调函数ngx_http_map解析。请求处理:如果没有使用map定义的变量(map指令第二个参数),该模块内容即使配置了也不会被使用。所以入口就是
ngx_http_map_block
函数定义的变量的回调函数ngx_http_map_variable
,该函数先调用ngx_http_complex_value
获取第一个变量的值,根据该值从解析map 的 block中的指令保存的结构体ngx_http_map_ctx_t中查找匹配的key对应的val,该val可以是常量也可以是个变量,如果是变量则调用ngx_http_complex_value
获取变量对应的值。map指令是该模块的重点,该指令接受2个参数,是个block指令。我们先来看下map指令的解析函数
ngx_http_map_block
:总结