FreeSWITCH 1.10 源码阅读(2)-xml_curl 模块原理

1. 前言

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

在这里插入图片描述

2. 源码分析

2.1 FreeSWITCH 中 xml 配置的初始化

  1. 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;
     }
    }
    
  2. switch_xml.c#switch_xml_open_root() 函数的关键逻辑如下:

    1. 通过宏 XML_OPEN_ROOT_FUNCTION 调用函数 switch_xml.c#__switch_xml_open_root() 开始处理 xml 配置文件
    2. 创建 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;
    }
    
  3. switch_xml.c#__switch_xml_open_root() 函数通过 switch_xml.c#switch_xml_parse_file() 函数打开处理全局属性 SWITCH_GLOBAL_filenames.conf_name 指向的顶层配置文件 freeswitch.xml

    SWITCH_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;
    }
    
  4. switch_xml.c#switch_xml_parse_file() 函数主要处理是将磁盘上的配置文件加载进内存,关键处理如下:

    1. 通过 switch_xml.c#preprocess() 函数预处理 freeswitch.xml 文件中的配置
    2. 调用 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;
    }
    
  5. switch_xml.c#preprocess() 函数源码比较长,关键处理如下,一个示例的 freeswitch.xml 文件内容已经贴出,读者可以对照理解

    1. 处理 X-pre-process 预处理指令,加载相关属性
    2. 处理 < 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;
    }
    
  6. 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 模块的加载

  1. FreeSWITCH 1.10 源码阅读(1)-服务启动及 Event Socket 模块工作原理 中笔者分析了 FreeSWITCH 加载外围模块的流程,此处不再赘述,直接看 mod_xml_curl 模块加载时的回调函数 mod_xml_curl.c#SWITCH_MODULE_LOAD_FUNCTION(mod_xml_curl_load)。该函数比较简练,核心逻辑如下:
    1. 调用 mod_xml_curl.c#do_config() 函数加载模块配置
    2. 调用宏 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;
    }
    
  2. mod_xml_curl.c#do_config() 主要负责读取 xml_curl.conf.xml 文件,解析其配置,关键处理如下:
    1. 解析 xml_curl.conf.xml 文件中 < binding > 标签 的配置,将绑定的外联 url 和对应的配置名称等关键数据封装到 xml_binding_t 结构体中
    2. 通过宏定义 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;
    }
    
    
  3. 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 配置获取的实现

  1. 以 FreeSWITCH 查找注册用户的信息为例,这个动作将触发 switch_xml.c#switch_xml_locate_user_merged() 函数执行。这个函数的核心逻辑比较清晰,关键点如下:

    1. 首先调用 switch_xml.c#switch_xml_locate_user_cache() 尝试从本地缓存中获取用户配置信息
    2. 缓存中没有找到,则调用 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;
    
    }
    
  2. switch_xml.c#switch_xml_locate_user() 函数的核心处理如下:

    1. 根据函数入参调用 switch_xml.c#switch_xml_locate_domain() 查找指定 domain 的 xml 配置
    2. 处理用户 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;
    }
    
  3. 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;
    }
    
  4. switch_xml.c#switch_xml_locate() 函数源码如下,可以看到核心有以下几点:

    1. 首先遍历上一节提到的 BINDINGS 列表,根据函数入参查找绑定在目标 section 的外联 url 结构体,通过 binding->function() 执行回调函数,最终调用到 mod_xml_curl.c#xml_url_fetch() 函数去请求外部服务器获取目标 xml 配置。此处一个目标 section 上可能绑定了多个外联 url,故需要遍历请求所有绑定的外部服务器,直至获取到目标配置或者遍历结束才停止
    2. 如果目标 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;
    }
    
  5. mod_xml_curl.c#xml_url_fetch() 方法是请求外部服务器获取 xml 配置的核心处理,其关键点如下,至此从外部获取 xml 配置数据的流程分析告一段落

    1. 创建一个本地临时文件,用于接收请求外联 url 获取的配置数据
    2. 根据函数入参构建 curl 请求,通过组件函数 switch_curl.c#switch_curl_easy_perform() 向外发送请求
    3. 最终调用 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&section=%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(&params, 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;
    }
    

版权声明:本文为weixin_45505313原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。