c++通过gsop调用基于https的webservice接口总结

前言

Web Service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的交互操作的应用程序。(来源于百度百科)。
优点:跨平台,跨语言调用。
由于项目需要调用webservice接口,网上大部分是java调用,c++部分很少,或者说不够系统。所以决定写下一个比较系统的调用过程。

步骤

第一步:生成头文件

webservice接口一般会有一个对外接口文档。比如:http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL
问号后面的参数表示WSDL文档,是一个XML文档,看不懂配置没关系,接下来我们要通过这个文档生成c++头文件。

(1) 下载gsoap工具

gsop工具下载地址
下载完成之后解压,进入gsoap\bin\win32目录下,该目录下有两个文件wsdl2h.exe和soapcpp2.exe。wsdl2h.exe用来生成头文件。光生成头文件是不够的,这个时候要用soapcpp2.exe来生成对应的c++文件结构,可以用于项目中调用(第二步介绍)。先来看看怎么生成头文件。
在gsoap\bin\win32目录下打开cmd命令。输入:

wsdl2h.exe -o head.h http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL

执行完成之后可以看到当前目录下多了一个head.h的头文件。可以打开看看,里面都是一些接口函数。

注意:由webservice在传输过程中默认使用UTF-8编码,当然gsoap在生成头文件的时候默认也是窄字符。比如string或者char*。此时如果再调用过程中参数有中文就会乱码。解决方案有两个。

方法一:在后面调用的时候在代码中加一句(具体加在哪里后面介绍):

soap_set_mode(&m_soap, SOAP_C_UTFSTRING);

方法二:由于默认使用根目录下的typemap.dat进行编译成窄字符。此时我们不适用它,而是在当前目录下新建一个mytypemap.dat。内容如下:xsd__string = | std::wstring | wchar_t* 。然后重新执行cmd命令。

wdsl2h.exe -o head.h -t mytypemap.dat http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL

重新生成之后可以发现head.h中字符串类型都变成了wchar_t * 或者wstring类型。

第二步:生成可调用API

在当前目录下执行cmd命令:

soapcpp2.exe -C -x -I ..\..\import head.h

其中-C是只生成客户端代码。-x表示不生成xml(我们只需要c++代码)-I 是指定import目录。
执行成功之后,当前目录下多了一些文件:
目录结构

第三步:导入到项目

  • 将上面图片中红色框中的文件添加到你的项目中。另外还有需要添加两个文件。gsoap根目录下的stdsoap2.h和stdsoap2.cpp也需要添加到项目中。
  • 对添加进去的3个cpp文件右击->属性->所有配置->c/c+±>预编译头,选择不使用预编译头。如下如所示。

在这里插入图片描述

配置完成之后,就可以开始写代码了。

第四步:编写代码

新建一个main.cpp,引入头文件

#include "WeatherWebServiceSoap.nsmap"
#include "soap.h"

我在项目中引用这两个头文件的时候,编译疯狂报错。仔细检查看了下,全是冲定义,原因是头文件 WeatherWebServiceSoap.nsmap中命名空间和项目中原有的socket库冲突了,解决方法是将#include "WeatherWebServiceSoap.nsmap"写到最前面,如果使用了预编译头,最好写在 stdafx.h文件中。头文件引入之后,再次编译,不报错了,继续。
在soapClient.cpp中可以看到服务的所有接口。函数名是soap_call __ns1_XXX形式。
首先创建soap对象并初始化:

struct soap m_soap;
//SOAP初始化
soap_init(&m_soap);
soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);

其中soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);正是一开始说的为了适配中文字符。如果一开始使用了wstring或者wchar_t * 则可以忽略。然后定义reqXml字符串,调用接口函数。xml字符串中双引号需要转义。

char* _reqXml = "<root>.....</root>";
char* _Return;
soap_call_ns1__XXX(&m_soap, NULL, NULL, G2U(_reqXml), _Return);
string ret = U2G(_Return);

由于上面代码中使用的是窄字符作为参数,即char *,需要转换成UTF-8格式。具体函数如下:

char* U2G(const char* utf8)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
	if (wstr) delete[] wstr;
	return str;
}

char* G2U(const char* gb2312)
{
	int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len);
	len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
	if (wstr) delete[] wstr;
	return str;
}

此时编译应该可以通过了。但是,现实是残酷的。返回的_Return值为NULL,说明出错了。我的地址是https的,如果,我直接用http请求的话会返回301,查找资料说301表示重定向,意思是这里不能像浏览器那样地址栏输入http,浏览器会帮你重定向到https。此时还是得改成https。再次编译。 额~还是报错。。。错误码可以在调试期间,查看m_soap结构体中error字段的值,如果一直是0,说明没问题。具体错误代码可以上网查一下。
改成https再调试可以看到m_soap->error的值是30,查看文档,发现30代表没有进行SSL安全认证。好吧~
在soap_init(&m_soap)后面添加如下代码:

soap_ssl_init();
if (soap_ssl_server_context(&m_soap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) {
	soap_print_fault(&m_soap, stderr);
	exit(-1);
}

其中soap_ssl_server_context参数中如果有SSL证书,和密码就填进去。没有全NULL就完事儿。
再次编译,还是无法通过~检查代码,发现在stdsoap2.h头文件中WITH_OPENSSL没有定义。下面是灰色的。
在这里插入图片描述
解决方法:右击项目属性->C/C+±>预处理器->预处理器定义中添加WITH_OPENSSL
在这里插入图片描述
添加之后,发现灰色没有了。
在这里插入图片描述
编译之后再次报错~~~~好吧,深不见底,一一解决吧 ,错误信息是,报错是soap_ssl_server_context函数无法解析的外部符号。啊,很明显,该函数的实现没有定义。忘记引入openssl库了。下载openssl库,解压,将lib文件夹解压到项目中。
在这里插入图片描述
接着在vs中右击项目->属性->配置属性->VC++目录中包含目录和库目录添加进去,如下图。
在这里插入图片描述
好了,万事大吉了吧~再次编译。。 OK成功啦。


再次更新
发现一个bug,当再次调用的时候,会产生一个错误:
Error 30 fault: SOAP-ENV:Server [no subcode]
“SSL error”
Detail: Can’t setup context
网上查了一下资料,没有相关错误,最后还是google到了。。
https://communities.vmware.com/thread/260000
如果无法访问,我把大致原因和解决方案粘贴出来:

The problem was not in the module (using web service sdk) but in the other module. The problem was in the initialization and cleanup of curl library. Rearranging the initialization and cleanup calls from module level to application level fixed the issue. Previously when the faulty module was unloaded, it releases all the libraries and therefore, VMware module was not able to setup the ssl context.

大致意思是在初始化之前就释放了资源,他通过重新调整初始化和释放资源的顺序就解决了。但是在我的代码中,我发现,并没有对openssl library进行初始化。网上代码,甚至是gsopa的ssl sample里都没有相关说明,不知道为什么。具体分析如下。
打开stdsoap2.cpp直接CTRL+F搜索报Can't setup context的位置,如下图,可以看到在调用SSL_CTX_NEW(SSLV23_method())函数的时候出错了,调试发现,第二次调用的返回NULL,这当然不行。
在这里插入图片描述
解决方法很简单,在调用soap_ssl_client_context()之前对openssl library进行初始化SSL_library_init()。问题解决。

部分源码

/******************************************************************************\
*
*	OpenSSL
*
\******************************************************************************/

#ifdef WITH_OPENSSL

struct CRYPTO_dynlock_value
{
	MUTEX_TYPE mutex;
};

static MUTEX_TYPE *mutex_buf = NULL;

static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
{
	struct CRYPTO_dynlock_value *value;
	(void)file; (void)line;
	value = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value));
	if (value)
		MUTEX_SETUP(value->mutex);
	return value;
}

static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
	(void)file; (void)line;
	if (mode & CRYPTO_LOCK)
		MUTEX_LOCK(l->mutex);
	else
		MUTEX_UNLOCK(l->mutex);
}

static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
	(void)file; (void)line;
	MUTEX_CLEANUP(l->mutex);
	free(l);
}

static void locking_function(int mode, int n, const char *file, int line)
{
	(void)file; (void)line;
	if (mode & CRYPTO_LOCK)
		MUTEX_LOCK(mutex_buf[n]);
	else
		MUTEX_UNLOCK(mutex_buf[n]);
}

static unsigned long id_function()
{
	return (unsigned long)THREAD_ID;
}

int CRYPTO_thread_setup()
{
	int i;
	mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
	if (!mutex_buf)
		return SOAP_EOM;
	for (i = 0; i < CRYPTO_num_locks(); i++)
		MUTEX_SETUP(mutex_buf[i]);
	CRYPTO_set_id_callback(id_function);
	CRYPTO_set_locking_callback(locking_function);
	CRYPTO_set_dynlock_create_callback(dyn_create_function);
	CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
	CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
	return SOAP_OK;
}

void CRYPTO_thread_cleanup()
{
	int i;
	if (!mutex_buf)
		return;
	CRYPTO_set_id_callback(NULL);
	CRYPTO_set_locking_callback(NULL);
	CRYPTO_set_dynlock_create_callback(NULL);
	CRYPTO_set_dynlock_lock_callback(NULL);
	CRYPTO_set_dynlock_destroy_callback(NULL);
	for (i = 0; i < CRYPTO_num_locks(); i++)
		MUTEX_CLEANUP(mutex_buf[i]);
	free(mutex_buf);
	mutex_buf = NULL;
}

#else

/* OpenSSL not used, e.g. GNUTLS is used */

int CRYPTO_thread_setup()
{
	return SOAP_OK;
}

void CRYPTO_thread_cleanup()
{ }

#endif
int main(){
	struct soap m_oSoap;
	SSL_library_init();
	/* set up lSSL ocks */
	if (CRYPTO_thread_setup())
	{
		LOG(LogLevel::INFO_OPT, STRINGIZE(__FILE__), STRINGIZE(__LINE__), "Cannot setup thread mutex 		for OpenSSL\n");
		return;
	}

	/* Init SSL (can skip or call multiple times, engien inits automatically) */
	soap_init(&m_oSoap);
	soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);//设置中文
	if (soap_ssl_client_context(&m_oSoap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL)) 
	{
		soap_print_fault(&m_oSoap, stderr);
		return;
	}
	m_oSoap.connect_timeout = 60;	/* try to connect for 1 minute */
	m_oSoap.send_timeout = m_oSoap.recv_timeout = 30;	/* if I/O stalls, then timeout after 30 seconds */
	char* _Return;
	string _reqXml = "<root>......</root>"
	soap_call_ns1__XXXX(&m_oSoap, NULL, NULL, G2U(_reqXml.c_str()), _Return)
	//close
	soap_destroy(&m_oSoap);
	soap_end(&m_oSoap);
	soap_done(&m_oSoap);
	CRYPTO_thread_cleanup();
}

总结

问题还是比较多的,需要一步步解决。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值