1. 前言
xml_curl 模块提供集中管理 xml 配置文件的特性,FreeSWITCH 通过这个模块可以从外部服务器动态获取到关键的 xml 配置,实现分布式环境下配置的统一维护,感兴趣的读者可点击前往官方传送门 了解使用方式。下图是 FreeSWITCH 中处理 xml 配置文件的源码时序图,下文将对源码进行分析

2. 源码分析
2.1 FreeSWITCH 中 xml 配置的初始化
在 FreeSWITCH 1.10 源码阅读(1)-服务启动及 Event Socket 模块工作原理 中笔者分析了 FreeSWITCH 的启动流程,实际上在 FreeSWITCH 启动过程中也会处理 xml 配置文件,其入口是
switch_xml.c#switch_xml_init()函数。该函数源码如下,核心逻辑是调用switch_xml.c#switch_xml_open_root()函数处理 xml 配置文件SWITCH_DECLARE(switch_status_t) switch_xml_init(switch_memory_pool_t *pool, const char **err) { switch_xml_t xml; XML_MEMORY_POOL = pool; *err = "Success"; switch_mutex_init(&CACHE_MUTEX, SWITCH_MUTEX_NESTED, XML_MEMORY_POOL); switch_mutex_init(&XML_LOCK, SWITCH_MUTEX_NESTED, XML_MEMORY_POOL); switch_mutex_init(&REFLOCK, SWITCH_MUTEX_NESTED, XML_MEMORY_POOL); switch_mutex_init(&FILE_LOCK, SWITCH_MUTEX_NESTED, XML_MEMORY_POOL); switch_core_hash_init(&CACHE_HASH); switch_core_hash_init(&CACHE_EXPIRES_HASH); switch_thread_rwlock_create(&B_RWLOCK, XML_MEMORY_POOL); assert(pool != NULL); if ((xml = switch_xml_open_root(FALSE, err))) { switch_xml_free(xml); return SWITCH_STATUS_SUCCESS; } else { return SWITCH_STATUS_FALSE; } }switch_xml.c#switch_xml_open_root()函数的关键逻辑如下:- 通过宏
XML_OPEN_ROOT_FUNCTION调用函数switch_xml.c#__switch_xml_open_root()开始处理 xml 配置文件 - 创建 SWITCH_EVENT_RELOADXML 事件,并将其投递到事件组件中
SWITCH_DECLARE(switch_xml_t) switch_xml_open_root(uint8_t reload, const char **err) { switch_xml_t root = NULL; switch_event_t *event; switch_mutex_lock(XML_LOCK); if (XML_OPEN_ROOT_FUNCTION) { root = XML_OPEN_ROOT_FUNCTION(reload, err, XML_OPEN_ROOT_FUNCTION_USER_DATA); } switch_mutex_unlock(XML_LOCK); if (root) { if (switch_event_create(&event, SWITCH_EVENT_RELOADXML) == SWITCH_STATUS_SUCCESS) { if (switch_event_fire(&event) != SWITCH_STATUS_SUCCESS) { switch_event_destroy(&event); } } } return root; }- 通过宏
switch_xml.c#__switch_xml_open_root()函数通过switch_xml.c#switch_xml_parse_file()函数打开处理全局属性 SWITCH_GLOBAL_filenames.conf_name 指向的顶层配置文件freeswitch.xmlSWITCH_DECLARE_NONSTD(switch_xml_t) __switch_xml_open_root(uint8_t reload, const char **err, void *user_data) { char path_buf[1024]; uint8_t errcnt = 0; switch_xml_t new_main, r = NULL; if (MAIN_XML_ROOT) { if (!reload) { r = switch_xml_root(); goto done; } } switch_snprintf(path_buf, sizeof(path_buf), "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, SWITCH_GLOBAL_filenames.conf_name); if ((new_main = switch_xml_parse_file(path_buf))) { *err = switch_xml_error(new_main); switch_copy_string(not_so_threadsafe_error_buffer, *err, sizeof(not_so_threadsafe_error_buffer)); *err = not_so_threadsafe_error_buffer; if (!zstr(*err)) { switch_xml_free(new_main); new_main = NULL; errcnt++; } else { *err = "Success"; switch_xml_set_root(new_main); } } else { *err = "Cannot Open log directory or XML Root!"; errcnt++; } if (errcnt == 0) { r = switch_xml_root(); } done: return r; }switch_xml.c#switch_xml_parse_file()函数主要处理是将磁盘上的配置文件加载进内存,关键处理如下:- 通过
switch_xml.c#preprocess()函数预处理freeswitch.xml文件中的配置 - 调用
switch_xml.c#switch_xml_parse_fd()函数处理 xml 文件内容,在内存中组装 xml 树
SWITCH_DECLARE(switch_xml_t) switch_xml_parse_file(const char *file) { int fd = -1; FILE *write_fd = NULL; switch_xml_t xml = NULL; char *new_file = NULL; char *new_file_tmp = NULL; const char *abs, *absw; abs = strrchr(file, '/'); absw = strrchr(file, '\\'); if (abs || absw) { abs > absw ? abs++ : (abs = ++absw); } else { abs = file; } switch_mutex_lock(FILE_LOCK); if (!(new_file = switch_mprintf("%s%s%s.fsxml", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR, abs))) { goto done; } if (!(new_file_tmp = switch_mprintf("%s%s%s.fsxml.tmp", SWITCH_GLOBAL_dirs.log_dir, SWITCH_PATH_SEPARATOR, abs))) { goto done; } if ((write_fd = fopen(new_file_tmp, "w+")) == NULL) { goto done; } setvbuf(write_fd, (char *) NULL, _IOFBF, 65536); if (preprocess(SWITCH_GLOBAL_dirs.conf_dir, file, write_fd, 0) > -1) { fclose(write_fd); write_fd = NULL; unlink (new_file); if ( rename(new_file_tmp,new_file) ) { goto done; } if ((fd = open(new_file, O_RDONLY, 0)) > -1) { if ((xml = switch_xml_parse_fd(fd))) { if (strcmp(abs, SWITCH_GLOBAL_filenames.conf_name)) { xml->free_path = new_file; new_file = NULL; } } close(fd); fd = -1; } } done: switch_mutex_unlock(FILE_LOCK); if (write_fd) { fclose(write_fd); write_fd = NULL; } if (fd > -1) { close(fd); } switch_safe_free(new_file_tmp); switch_safe_free(new_file); return xml; }- 通过
switch_xml.c#preprocess()函数源码比较长,关键处理如下,一个示例的 freeswitch.xml 文件内容已经贴出,读者可以对照理解- 处理 X-pre-process 预处理指令,加载相关属性
- 处理 < include > 等标签,这个步骤还没有到达 xml 解析,仅仅是文本内容的字符串替换
<?xml version="1.0"?> <document type="freeswitch/xml"> <X-PRE-PROCESS cmd="include" data="vars.xml"/> <section name="configuration" description="Various Configuration"> <X-PRE-PROCESS cmd="include" data="autoload_configs/*.xml"/> </section> <section name="dialplan" description="Regex/XML Dialplan"> <X-PRE-PROCESS cmd="include" data="dialplan/*.xml"/> </section> <section name="chatplan" description="Regex/XML Chatplan"> <X-PRE-PROCESS cmd="include" data="chatplan/*.xml"/> </section> <!-- mod_dingaling is reliant on the vcard data in the "directory" section. --> <!-- mod_sofia is reliant on the user data for authorization --> <section name="directory" description="User Directory"> <X-PRE-PROCESS cmd="include" data="directory/*.xml"/> </section> </document>static int preprocess(const char *cwd, const char *file, FILE *write_fd, int rlevel) { FILE *read_fd = NULL; switch_size_t cur = 0, ml = 0; char *q, *cmd, *buf = NULL, *ebuf = NULL; char *tcmd, *targ; int line = 0; switch_size_t len = 0, eblen = 0; if (rlevel > 100) { return -1; } if (!(read_fd = fopen(file, "r"))) { const char *reason = strerror(errno); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't open %s (%s)\n", file, reason); return -1; } setvbuf(read_fd, (char *) NULL, _IOFBF, 65536); for(;;) { char *arg, *e; const char *err = NULL; char *bp; switch_safe_free(ebuf); if ((cur = switch_fp_read_dline(read_fd, &buf, &len)) <= 0) { break; } eblen = len * 2; ebuf = switch_must_malloc(eblen); memset(ebuf, 0, eblen); while (!(bp = expand_vars(buf, ebuf, eblen, &cur, &err))) { eblen *= 2; ebuf = switch_must_realloc(ebuf, eblen); memset(ebuf, 0, eblen); } line++; if (err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error [%s] in file %s line %d\n", err, file, line); } /* we ignore <include> or </include> for the sake of validators as well as <?xml version="1.0"?> type stuff */ if (strstr(buf, "<include>") || strstr(buf, "</include>") || strstr(buf, "<?")) { continue; } if (ml) { if ((e = strstr(buf, "-->"))) { ml = 0; bp = e + 3; cur = strlen(bp); } else { continue; } } if ((tcmd = (char *) switch_stristr("X-pre-process", bp))) { if (*(tcmd - 1) != '<') { continue; } if ((e = strstr(tcmd, "/>"))) { e += 2; *e = '\0'; if (fwrite(e, 1, (unsigned) strlen(e), write_fd) != (int) strlen(e)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write!\n"); } } if (!(tcmd = (char *) switch_stristr("cmd", tcmd))) { continue; } if (!(tcmd = (char *) switch_stristr("=", tcmd))) { continue; } if (!(tcmd = (char *) switch_stristr("\"", tcmd))) { continue; } tcmd++; if ((e = strchr(tcmd, '"'))) { *e++ = '\0'; } if (!(targ = (char *) switch_stristr("data", e))) { continue; } if (!(targ = (char *) switch_stristr("=", targ))) { continue; } if (!(targ = (char *) switch_stristr("\"", targ))) { continue; } targ++; if ((e = strchr(targ, '"'))) { *e++ = '\0'; } if (!strcasecmp(tcmd, "set")) { char *name = (char *) targ; char *val = strchr(name, '='); if (val) { char *ve = val++; while (*val && *val == ' ') { val++; } *ve-- = '\0'; while (*ve && *ve == ' ') { *ve-- = '\0'; } } if (val) { switch_core_set_variable(name, val); } } else if (!strcasecmp(tcmd, "exec-set")) { preprocess_exec_set(targ); } else if (!strcasecmp(tcmd, "stun-set")) { preprocess_stun_set(targ); } else if (!strcasecmp(tcmd, "env-set")) { preprocess_env_set(targ); } else if (!strcasecmp(tcmd, "include")) { preprocess_glob(cwd, targ, write_fd, rlevel + 1); } else if (!strcasecmp(tcmd, "exec")) { preprocess_exec(cwd, targ, write_fd, rlevel + 1); } continue; } if ((cmd = strstr(bp, "<!--#"))) { if (fwrite(bp, 1, (unsigned) (cmd - bp), write_fd) != (unsigned) (cmd - bp)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write!\n"); } if ((e = strstr(cmd, "-->"))) { *e = '\0'; e += 3; if (fwrite(e, 1, (unsigned) strlen(e), write_fd) != (int) strlen(e)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write!\n"); } } else { ml++; } cmd += 5; if ((e = strchr(cmd, '\r')) || (e = strchr(cmd, '\n'))) { *e = '\0'; } if ((arg = strchr(cmd, ' '))) { *arg++ = '\0'; if ((q = strchr(arg, '"'))) { char *qq = q + 1; if ((qq = strchr(qq, '"'))) { *qq = '\0'; arg = q + 1; } } if (!strcasecmp(cmd, "set")) { char *name = arg; char *val = strchr(name, '='); if (val) { char *ve = val++; while (*val && *val == ' ') { val++; } *ve-- = '\0'; while (*ve && *ve == ' ') { *ve-- = '\0'; } } if (val) { switch_core_set_variable(name, val); } } else if (!strcasecmp(cmd, "exec-set")) { preprocess_exec_set(arg); } else if (!strcasecmp(cmd, "stun-set")) { preprocess_stun_set(arg); } else if (!strcasecmp(cmd, "include")) { preprocess_glob(cwd, arg, write_fd, rlevel + 1); } else if (!strcasecmp(cmd, "exec")) { preprocess_exec(cwd, arg, write_fd, rlevel + 1); } } continue; } if (fwrite(bp, 1, (unsigned) cur, write_fd) != (int) cur) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write!\n"); } } switch_safe_free(buf); switch_safe_free(ebuf); fclose(read_fd); return 0; }switch_xml.c#switch_xml_parse_fd()函数的核心是调用switch_xml.c#switch_xml_parse_str()解析 xml 文件内容,至此 xml 配置文件的初始化加载基本结束SWITCH_DECLARE(switch_xml_t) switch_xml_parse_fd(int fd) { switch_xml_root_t root; struct stat st; switch_ssize_t l; void *m; if (fd < 0) return NULL; fstat(fd, &st); if (!st.st_size) { return NULL; } m = switch_must_malloc(st.st_size); if (!(0<(l = read(fd, m, st.st_size))) || !(root = (switch_xml_root_t) switch_xml_parse_str((char *) m, l))) { free(m); return NULL; } root->dynamic = 1; /* so we know to free s in switch_xml_free() */ return &root->xml; }
2.2 mod_xml_curl 模块的加载
- 在 FreeSWITCH 1.10 源码阅读(1)-服务启动及 Event Socket 模块工作原理 中笔者分析了 FreeSWITCH 加载外围模块的流程,此处不再赘述,直接看 mod_xml_curl 模块加载时的回调函数
mod_xml_curl.c#SWITCH_MODULE_LOAD_FUNCTION(mod_xml_curl_load)。该函数比较简练,核心逻辑如下:- 调用
mod_xml_curl.c#do_config()函数加载模块配置 - 调用宏 SWITCH_ADD_API 向核心注册名称为 xml_curl,实现为
mod_xml_curl.c#xml_curl_function()的 api
SWITCH_MODULE_LOAD_FUNCTION(mod_xml_curl_load) { switch_api_interface_t *xml_curl_api_interface; /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); memset(&globals, 0, sizeof(globals)); globals.pool = pool; globals.hash_root = NULL; globals.hash_tail = NULL; if (do_config() != SWITCH_STATUS_SUCCESS) { return SWITCH_STATUS_FALSE; } SWITCH_ADD_API(xml_curl_api_interface, "xml_curl", "XML Curl", xml_curl_function, XML_CURL_SYNTAX); switch_console_set_complete("add xml_curl debug_on"); switch_console_set_complete("add xml_curl debug_off"); /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; } - 调用
mod_xml_curl.c#do_config()主要负责读取xml_curl.conf.xml文件,解析其配置,关键处理如下:- 解析
xml_curl.conf.xml文件中 < binding > 标签 的配置,将绑定的外联 url 和对应的配置名称等关键数据封装到xml_binding_t结构体中 - 通过宏定义
switch_xml.h#switch_xml_bind_search_function()调用switch_xml.c#switch_xml_bind_search_function_ret()函数将绑定的获取 xml 配置的外部接口注册到 xml 组件内部,并指定获取外部 xml 配置的实际处理函数为mod_xml_curl.c#xml_url_fetch()
static switch_status_t do_config(void) { char *cf = "xml_curl.conf"; switch_xml_t cfg, xml, bindings_tag, binding_tag, param; xml_binding_t *binding = NULL; int x = 0; int need_vars_map = 0; switch_hash_t *vars_map = NULL; if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf); return SWITCH_STATUS_TERM; } if (!(bindings_tag = switch_xml_child(cfg, "bindings"))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing <bindings> tag!\n"); goto done; } for (binding_tag = switch_xml_child(bindings_tag, "binding"); binding_tag; binding_tag = binding_tag->next) { char *bname = (char *) switch_xml_attr_soft(binding_tag, "name"); char *url = NULL; char *bind_local = NULL; char *bind_cred = NULL; char *bind_mask = NULL; char *method = NULL; int disable100continue = 1; int use_dynamic_url = 0, timeout = 0; switch_size_t curl_max_bytes = XML_CURL_MAX_BYTES; uint32_t enable_cacert_check = 0; char *ssl_cert_file = NULL; char *ssl_key_file = NULL; char *ssl_key_password = NULL; char *ssl_version = NULL; char *ssl_cacert_file = NULL; uint32_t enable_ssl_verifyhost = 0; char *cookie_file = NULL; hash_node_t *hash_node; long auth_scheme = CURLAUTH_BASIC; need_vars_map = 0; vars_map = NULL; for (param = switch_xml_child(binding_tag, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcasecmp(var, "gateway-url")) { bind_mask = (char *) switch_xml_attr_soft(param, "bindings"); if (val) { url = val; } } else if (!strcasecmp(var, "gateway-credentials")) { bind_cred = val; } else if (!strcasecmp(var, "auth-scheme")) { if (*val == '=') { auth_scheme = 0; val++; } if (!strcasecmp(val, "basic")) { auth_scheme |= CURLAUTH_BASIC; } else if (!strcasecmp(val, "digest")) { auth_scheme |= CURLAUTH_DIGEST; } else if (!strcasecmp(val, "NTLM")) { auth_scheme |= CURLAUTH_NTLM; } else if (!strcasecmp(val, "GSS-NEGOTIATE")) { auth_scheme |= CURLAUTH_GSSNEGOTIATE; } else if (!strcasecmp(val, "any")) { auth_scheme = (long)CURLAUTH_ANY; } } else if (!strcasecmp(var, "disable-100-continue") && !switch_true(val)) { disable100continue = 0; } else if (!strcasecmp(var, "method")) { method = val; } else if (!strcasecmp(var, "timeout")) { int tmp = atoi(val); if (tmp >= 0) { timeout = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't set a negative timeout!\n"); } } else if (!strcasecmp(var, "enable-cacert-check") && switch_true(val)) { enable_cacert_check = 1; } else if (!strcasecmp(var, "ssl-cert-path")) { ssl_cert_file = val; } else if (!strcasecmp(var, "ssl-key-path")) { ssl_key_file = val; } else if (!strcasecmp(var, "ssl-key-password")) { ssl_key_password = val; } else if (!strcasecmp(var, "ssl-version")) { ssl_version = val; } else if (!strcasecmp(var, "ssl-cacert-file")) { ssl_cacert_file = val; } else if (!strcasecmp(var, "enable-ssl-verifyhost") && switch_true(val)) { enable_ssl_verifyhost = 1; } else if (!strcasecmp(var, "cookie-file")) { cookie_file = val; } else if (!strcasecmp(var, "use-dynamic-url") && switch_true(val)) { use_dynamic_url = 1; } else if (!strcasecmp(var, "enable-post-var")) { if (!vars_map && need_vars_map == 0) { if (switch_core_hash_init(&vars_map) != SWITCH_STATUS_SUCCESS) { need_vars_map = -1; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't init params hash!\n"); continue; } need_vars_map = 1; } if (vars_map && val) { if (switch_core_hash_insert(vars_map, val, ENABLE_PARAM_VALUE) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't add %s to params hash!\n", val); } } } else if (!strcasecmp(var, "bind-local")) { bind_local = val; } else if (!strcasecmp(var, "response-max-bytes")) { int tmp = atoi(val); if (tmp >= 0) { curl_max_bytes = tmp; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't set a negative maximum response bytes!\n"); } } } if (!url) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Binding has no url!\n"); if (vars_map) switch_core_hash_destroy(&vars_map); continue; } if (!(binding = switch_core_alloc(globals.pool, sizeof(*binding)))) { if (vars_map) switch_core_hash_destroy(&vars_map); goto done; } memset(binding, 0, sizeof(*binding)); binding->auth_scheme = auth_scheme; binding->timeout = timeout; binding->url = switch_core_strdup(globals.pool, url); switch_assert(binding->url); if (bind_local != NULL) { binding->bind_local = switch_core_strdup(globals.pool, bind_local); } if (method != NULL) { binding->method = switch_core_strdup(globals.pool, method); } else { binding->method = NULL; } if (bind_mask) { binding->bindings = switch_core_strdup(globals.pool, bind_mask); } if (bind_cred) { binding->cred = switch_core_strdup(globals.pool, bind_cred); } binding->disable100continue = disable100continue; binding->use_get_style = method != NULL && strcasecmp(method, "post") != 0; binding->use_dynamic_url = use_dynamic_url; binding->enable_cacert_check = enable_cacert_check; if (ssl_cert_file) { binding->ssl_cert_file = switch_core_strdup(globals.pool, ssl_cert_file); } if (ssl_key_file) { binding->ssl_key_file = switch_core_strdup(globals.pool, ssl_key_file); } if (ssl_key_password) { binding->ssl_key_password = switch_core_strdup(globals.pool, ssl_key_password); } if (ssl_version) { binding->ssl_version = switch_core_strdup(globals.pool, ssl_version); } if (ssl_cacert_file) { binding->ssl_cacert_file = switch_core_strdup(globals.pool, ssl_cacert_file); } binding->enable_ssl_verifyhost = enable_ssl_verifyhost; if (cookie_file) { binding->cookie_file = switch_core_strdup(globals.pool, cookie_file); } binding->vars_map = vars_map; if (vars_map) { switch_zmalloc(hash_node, sizeof(hash_node_t)); hash_node->hash = vars_map; hash_node->next = NULL; if (!globals.hash_root) { globals.hash_root = hash_node; globals.hash_tail = globals.hash_root; } else { globals.hash_tail->next = hash_node; globals.hash_tail = globals.hash_tail->next; } } binding->curl_max_bytes = curl_max_bytes; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Binding [%s] XML Fetch Function [%s] [%s]\n", zstr(bname) ? "N/A" : bname, binding->url, binding->bindings ? binding->bindings : "all"); switch_xml_bind_search_function(xml_url_fetch, switch_xml_parse_section_string(binding->bindings), binding); x++; binding = NULL; } done: switch_xml_free(xml); return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; }- 解析
switch_xml.c#switch_xml_bind_search_function_ret()函数的实现比较简单,可以看到核心逻辑就是将封装外联 url 信息的结构体及其回调函数封装到switch_xml_binding_t结构体,最终添加到 BINDINGS 列表中,至此 xml_curl 模块的加载基本结束SWITCH_DECLARE(switch_status_t) switch_xml_bind_search_function_ret(switch_xml_search_function_t function, switch_xml_section_t sections, void *user_data, switch_xml_binding_t **ret_binding) { switch_xml_binding_t *binding = NULL, *ptr = NULL; assert(function != NULL); if (!(binding = (switch_xml_binding_t *) switch_core_alloc(XML_MEMORY_POOL, sizeof(*binding)))) { return SWITCH_STATUS_MEMERR; } binding->function = function; binding->sections = sections; binding->user_data = user_data; switch_thread_rwlock_wrlock(B_RWLOCK); for (ptr = BINDINGS; ptr && ptr->next; ptr = ptr->next); if (ptr) { ptr->next = binding; } else { BINDINGS = binding; } if (ret_binding) { *ret_binding = binding; } switch_thread_rwlock_unlock(B_RWLOCK); return SWITCH_STATUS_SUCCESS; }
2.3 统一 xml 配置获取的实现
以 FreeSWITCH 查找注册用户的信息为例,这个动作将触发
switch_xml.c#switch_xml_locate_user_merged()函数执行。这个函数的核心逻辑比较清晰,关键点如下:- 首先调用
switch_xml.c#switch_xml_locate_user_cache()尝试从本地缓存中获取用户配置信息 - 缓存中没有找到,则调用
switch_xml.c#switch_xml_locate_user()函数获取用户配置信息,如果该用户配置中有cacheable属性,则将其缓存起来
SWITCH_DECLARE(switch_status_t) switch_xml_locate_user_merged(const char *key, const char *user_name, const char *domain_name, const char *ip, switch_xml_t *user, switch_event_t *params) { switch_xml_t xml, domain, group, x_user, x_user_dup; switch_status_t status = SWITCH_STATUS_FALSE; char *kdup = NULL; char *keys[10] = {0}; int i, nkeys; if (strchr(key, ':')) { kdup = switch_must_strdup(key); nkeys = switch_split(kdup, ':', keys); } else { keys[0] = (char *)key; nkeys = 1; } for(i = 0; i < nkeys; i++) { if ((status = switch_xml_locate_user_cache(keys[i], user_name, domain_name, &x_user)) == SWITCH_STATUS_SUCCESS) { *user = x_user; break; } else if ((status = switch_xml_locate_user(keys[i], user_name, domain_name, ip, &xml, &domain, &x_user, &group, params)) == SWITCH_STATUS_SUCCESS) { const char *cacheable = NULL; x_user_dup = switch_xml_dup(x_user); switch_xml_merge_user(x_user_dup, domain, group); cacheable = switch_xml_attr(x_user_dup, "cacheable"); if (!zstr(cacheable)) { switch_time_t expires = 0; switch_time_t time_now = 0; if (switch_is_number(cacheable)) { int cache_ms = atol(cacheable); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "caching lookup for user %s@%s for %d milliseconds\n", user_name, domain_name, cache_ms); time_now = switch_micro_time_now(); expires = time_now + (cache_ms * 1000); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "caching lookup for user %s@%s indefinitely\n", user_name, domain_name); } switch_xml_user_cache(keys[i], user_name, domain_name, x_user_dup, expires); } *user = x_user_dup; switch_xml_free(xml); break; } } switch_safe_free(kdup); return status; }- 首先调用
switch_xml.c#switch_xml_locate_user()函数的核心处理如下:- 根据函数入参调用
switch_xml.c#switch_xml_locate_domain()查找指定 domain 的 xml 配置 - 处理用户 xml 配置中的标签,调用
switch_xml.c#find_user_in_tag()函数从上一步获取到的 xml 配置中查找指定用户配置
SWITCH_DECLARE(switch_status_t) switch_xml_locate_user(const char *key, const char *user_name, const char *domain_name, const char *ip, switch_xml_t *root, switch_xml_t *domain, switch_xml_t *user, switch_xml_t *ingroup, switch_event_t *params) { switch_status_t status = SWITCH_STATUS_FALSE; switch_event_t *my_params = NULL; switch_xml_t group = NULL, groups = NULL, users = NULL; *root = NULL; *user = NULL; *domain = NULL; if (ingroup) { *ingroup = NULL; } if (!params) { switch_event_create(&my_params, SWITCH_EVENT_REQUEST_PARAMS); switch_assert(my_params); params = my_params; } switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "key", key); if (user_name) { switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "user", user_name); } if (domain_name) { switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "domain", domain_name); } if (ip) { switch_event_add_header_string(params, SWITCH_STACK_BOTTOM, "ip", ip); } if ((status = switch_xml_locate_domain(domain_name, params, root, domain)) != SWITCH_STATUS_SUCCESS) { goto end; } status = SWITCH_STATUS_FALSE; if ((groups = switch_xml_child(*domain, "groups"))) { for (group = switch_xml_child(groups, "group"); group; group = group->next) { if ((users = switch_xml_child(group, "users"))) { if ((status = find_user_in_tag(users, ip, user_name, key, params, user)) == SWITCH_STATUS_SUCCESS) { if (ingroup) { *ingroup = group; } break; } } } } if (status != SWITCH_STATUS_SUCCESS) { if ((users = switch_xml_child(*domain, "users"))) { status = find_user_in_tag(users, ip, user_name, key, params, user); } else { status = find_user_in_tag(*domain, ip, user_name, key, params, user); } } end: if (my_params) { switch_event_destroy(&my_params); } if (status != SWITCH_STATUS_SUCCESS && root && *root) { switch_xml_free(*root); *root = NULL; *domain = NULL; } return status; }- 根据函数入参调用
switch_xml.c#switch_xml_locate_domain()函数的关键处理是调用switch_xml.c#switch_xml_locate()函数定位获取上层需要的 xml 配置SWITCH_DECLARE(switch_status_t) switch_xml_locate_domain(const char *domain_name, switch_event_t *params, switch_xml_t *root, switch_xml_t *domain) { switch_event_t *my_params = NULL; switch_status_t status; *domain = NULL; if (!params) { switch_event_create(&my_params, SWITCH_EVENT_REQUEST_PARAMS); switch_assert(my_params); switch_event_add_header_string(my_params, SWITCH_STACK_BOTTOM, "domain", domain_name); params = my_params; } status = switch_xml_locate("directory", "domain", "name", domain_name, root, domain, params, SWITCH_FALSE); if (my_params) { switch_event_destroy(&my_params); } return status; }switch_xml.c#switch_xml_locate()函数源码如下,可以看到核心有以下几点:- 首先遍历上一节提到的 BINDINGS 列表,根据函数入参查找绑定在目标 section 的外联 url 结构体,通过
binding->function()执行回调函数,最终调用到mod_xml_curl.c#xml_url_fetch()函数去请求外部服务器获取目标 xml 配置。此处一个目标 section 上可能绑定了多个外联 url,故需要遍历请求所有绑定的外部服务器,直至获取到目标配置或者遍历结束才停止 - 如果目标 section 没有绑定外联 url 或者在外部服务器上没有获取到对应配置,则调用
switch_xml.c#switch_xml_root()获取本地内存中 xml 树的头节点,从本地 xml 树中获取目标配置
SWITCH_DECLARE(switch_status_t) switch_xml_locate(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_xml_t *root, switch_xml_t *node, switch_event_t *params, switch_bool_t clone) { switch_xml_t conf = NULL; switch_xml_t tag = NULL; switch_xml_t xml = NULL; switch_xml_binding_t *binding; uint8_t loops = 0; switch_xml_section_t sections = BINDINGS ? switch_xml_parse_section_string(section) : 0; switch_thread_rwlock_rdlock(B_RWLOCK); for (binding = BINDINGS; binding; binding = binding->next) { if (binding->sections && !(sections & binding->sections)) { continue; } if ((xml = binding->function(section, tag_name, key_name, key_value, params, binding->user_data))) { const char *err = NULL; err = switch_xml_error(xml); if (zstr(err)) { if ((conf = switch_xml_find_child(xml, "section", "name", "result"))) { switch_xml_t p; const char *aname; if ((p = switch_xml_child(conf, "result"))) { aname = switch_xml_attr(p, "status"); if (aname && !strcasecmp(aname, "not found")) { switch_xml_free(xml); xml = NULL; continue; } } } break; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error[%s]\n", err); switch_xml_free(xml); xml = NULL; } } } switch_thread_rwlock_unlock(B_RWLOCK); for (;;) { if (!xml) { if (!(xml = switch_xml_root())) { *node = NULL; *root = NULL; return SWITCH_STATUS_FALSE; } } if ((conf = switch_xml_find_child(xml, "section", "name", section)) && (tag = switch_xml_find_child(conf, tag_name, key_name, key_value))) { if (clone) { char *x = switch_xml_toxml(tag, SWITCH_FALSE); switch_assert(x); *node = *root = switch_xml_parse_str_dynamic(x, SWITCH_FALSE); /* x will be free()'d in switch_xml_free() */ switch_xml_free(xml); } else { *node = tag; *root = xml; } return SWITCH_STATUS_SUCCESS; } else { switch_xml_free(xml); xml = NULL; *node = NULL; *root = NULL; if (loops++ > 1) { break; } } } return SWITCH_STATUS_FALSE; }- 首先遍历上一节提到的 BINDINGS 列表,根据函数入参查找绑定在目标 section 的外联 url 结构体,通过
mod_xml_curl.c#xml_url_fetch()方法是请求外部服务器获取 xml 配置的核心处理,其关键点如下,至此从外部获取 xml 配置数据的流程分析告一段落- 创建一个本地临时文件,用于接收请求外联 url 获取的配置数据
- 根据函数入参构建 curl 请求,通过组件函数
switch_curl.c#switch_curl_easy_perform()向外发送请求 - 最终调用
switch_xml.c#switch_xml_parse_file()函数解析接收了远程数据的本地临时文件,读取外部服务器发送过来的 xml 配置
static switch_xml_t xml_url_fetch(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_event_t *params, void *user_data) { char filename[512] = ""; switch_CURL *curl_handle = NULL; switch_CURLcode cc; struct config_data config_data; switch_xml_t xml = NULL; char *data = NULL; switch_uuid_t uuid; char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1]; xml_binding_t *binding = (xml_binding_t *) user_data; char *file_url; switch_curl_slist_t *slist = NULL; long httpRes = 0; switch_curl_slist_t *headers = NULL; char hostname[256] = ""; char basic_data[512]; char *uri = NULL; char *dynamic_url = NULL; strncpy(hostname, switch_core_get_switchname(), sizeof(hostname) - 1); if (!binding) { return NULL; } if ((file_url = strstr(binding->url, "file:"))) { file_url += 5; if (!(xml = switch_xml_parse_file(file_url))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Parsing Result!\n"); } return xml; } switch_snprintf(basic_data, sizeof(basic_data), "hostname=%s§ion=%s&tag_name=%s&key_name=%s&key_value=%s", hostname, section, switch_str_nil(tag_name), switch_str_nil(key_name), switch_str_nil(key_value)); data = switch_event_build_param_string(params, basic_data, binding->vars_map); switch_assert(data); if (binding->use_dynamic_url) { if (!params) { switch_event_create(¶ms, SWITCH_EVENT_REQUEST_PARAMS); switch_assert(params); } switch_event_add_header_string(params, SWITCH_STACK_TOP, "hostname", hostname); switch_event_add_header_string(params, SWITCH_STACK_TOP, "section", switch_str_nil(section)); switch_event_add_header_string(params, SWITCH_STACK_TOP, "tag_name", switch_str_nil(tag_name)); switch_event_add_header_string(params, SWITCH_STACK_TOP, "key_name", switch_str_nil(key_name)); switch_event_add_header_string(params, SWITCH_STACK_TOP, "key_value", switch_str_nil(key_value)); dynamic_url = switch_event_expand_headers(params, binding->url); switch_assert(dynamic_url); } else { dynamic_url = binding->url; } if (binding->use_get_style == 1) { uri = malloc(strlen(data) + strlen(dynamic_url) + 16); switch_assert(uri); sprintf(uri, "%s%c%s", dynamic_url, strchr(dynamic_url, '?') != NULL ? '&' : '?', data); } switch_uuid_get(&uuid); switch_uuid_format(uuid_str, &uuid); switch_snprintf(filename, sizeof(filename), "%s%s%s.tmp.xml", SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, uuid_str); curl_handle = switch_curl_easy_init(); headers = switch_curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); if (!strncasecmp(binding->url, "https", 5)) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0); } memset(&config_data, 0, sizeof(config_data)); config_data.name = filename; config_data.max_bytes = binding->curl_max_bytes; if ((config_data.fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) > -1) { if (!zstr(binding->cred)) { switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, binding->auth_scheme); switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, binding->cred); } switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers); if (binding->method != NULL) switch_curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, binding->method); switch_curl_easy_setopt(curl_handle, CURLOPT_POST, !binding->use_get_style); switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10); if (!binding->use_get_style) switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data); switch_curl_easy_setopt(curl_handle, CURLOPT_URL, binding->use_get_style ? uri : dynamic_url); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, file_callback); switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &config_data); switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-xml/1.0"); switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1); if (binding->timeout) { switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, binding->timeout); } if (binding->disable100continue) { slist = switch_curl_slist_append(slist, "Expect:"); switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, slist); } if (binding->enable_cacert_check) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE); } if (binding->ssl_cert_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, binding->ssl_cert_file); } if (binding->ssl_key_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, binding->ssl_key_file); } if (binding->ssl_key_password) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEYPASSWD, binding->ssl_key_password); } if (binding->ssl_version) { if (!strcasecmp(binding->ssl_version, "SSLv3")) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); } else if (!strcasecmp(binding->ssl_version, "TLSv1")) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); } } if (binding->ssl_cacert_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, binding->ssl_cacert_file); } if (binding->enable_ssl_verifyhost) { switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2); } if (binding->cookie_file) { switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, binding->cookie_file); switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, binding->cookie_file); } if (binding->bind_local) { curl_easy_setopt(curl_handle, CURLOPT_INTERFACE, binding->bind_local); } cc = switch_curl_easy_perform(curl_handle); if (cc && cc != CURLE_WRITE_ERROR) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "CURL returned error:[%d] %s\n", cc, switch_curl_easy_strerror(cc)); } switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpRes); switch_curl_easy_cleanup(curl_handle); switch_curl_slist_free_all(headers); switch_curl_slist_free_all(slist); close(config_data.fd); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening temp file!\n"); } if (config_data.err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error encountered! [%s]\ndata: [%s]\n", binding->url, data); xml = NULL; } else { if (httpRes == 200) { if (!(xml = switch_xml_parse_file(filename))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Parsing Result! [%s]\ndata: [%s]\n", binding->url, data); } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received HTTP error %ld trying to fetch %s\ndata: [%s]\n", httpRes, binding->url, data); xml = NULL; } } /* Debug by leaving the file behind for review */ if (keep_files_around) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "XML response is in %s\n", filename); } else { if (unlink(filename) != 0) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "XML response file [%s] delete failed\n", filename); } } switch_safe_free(data); if (binding->use_get_style == 1) switch_safe_free(uri); if (binding->use_dynamic_url && dynamic_url != binding->url) switch_safe_free(dynamic_url); return xml; }