libcurl + libxml2 + soapUI开发SOAP客户端程序
2010年,我写过一些使用gSOAP开发SOAP应用程序的系列文章,共8篇,详情请参考以前的博文:
(1) 股票信息客户端程序
(2) 股票信息服务端程序
(3) iconv解决中文乱码问题
(4) iconv解决中文乱码问题(续)
(5) libxml2解析SOAP响应消息
(6) 在HP-UX下编译gSOAP-2.7.17
(7) 基于HTTPS的基本认证(Basic Authentication)
(8) 自定义header实现用户名令牌认证(Usernametoken Authentication)
使用gSOAP,基本上可以实现傻瓜相机式的快速开发。不过,大部分操作系统,即使是Linux,都没有自带gSOAP,要使用它就必须首先下载、编译、安装。另外,虽然gSOAP本身是跨平台的,但是它必须基于一系列GNU的组件,如果不满足条件的话,恭喜你啦,可以看看我在HP-UX下使用gSOAP有多么的麻烦。再次,gSOAP封装得太多,使用起来太简单,可能也不能满足部分人士(比如我这种人啦)学习的需要。
那么,Linux下有没有免安装的、更容易实现跨平台的、比gSOAP轻量级的、能够进一步学习和理解HTTP、SOAP的开发工具呢?
答案是肯定的。curl就是RHEL Linux自带的、功能强大的客户端工具,它支持许多协议:HTTP、FTP、TELNET、SMTP、POP3等等,基于HTTP和HTTPS的SOAP更是不在话下了。更妙的是,它同时还提供一套编程接口libcurl,可以把curl命令的各项功能嵌入到你自己的C程序中。所以,除了SOAP客户端,使用libcurl,还可以开发出下载工具和邮件客户端,这是gSOAP无法相比的。
由于SOAP和XML的天然联系,最好使用同样是Linux自带的libxml2来生成SOAP请求和解析SOAP响应。即使有的Unix系统、甚至Windows系统,没有libcurl或者libxml2,下载源代码编译安装基本上也不会遇到什么麻烦,比编译gSOAP的过程轻松多了。Windows下编译libxml2的过程及可能遇到的问题,请参考以前的博文:http://blog.****.net/yui/archive/2011/01/02/6112622.aspx
此外,我们还需要soapUI进行辅助开发,我觉得soapUI最实用的功能是能够测试一个SOAP连接是否成功,并且一字不漏地呈现它的请求格式,包括其HTTP头信息。这样,我们就可以根据一个在soapUI里测试成功的范例,依样画葫芦,使用libcurl把它重现出来。soapUI的官方网站是:http://www.soapui.org/
那么,libcurl+libxml2是否就可以取代gSOAP呢?当然也不是,首先,libcurl只是一个客户端工具,开发不了服务端程序,其次,使用libxml2生成SOAP请求和解析SOAP响应,在大部分场合都比调用gSOAP的接口麻烦,这个也是必须承认的事实。仅仅进行SOAP客户端开发的话,如果是在GNU Linux下,还是推荐gSOAP。libcurl+libxml2方案的亮点是:支持多种协议、有利于跨平台。总之,多备一把刀,总是好的,免得杀鸡的时候被迫用了把牛刀,砍树的时候又被迫用了把小刀。
以下是基于HTTP的SOAP客户端程序的例子,用了http://www.webxml.com.cn/zh_cn/web_services.aspx
提供的Web Service,应该很容易看懂。中文乱码的问题就不在这里多说了,上面gSOAP的系列文章有解释。
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
static void set_value(xmlXPathContextPtr context, const xmlChar *xpath, const xmlChar *value){
xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context);
if ( result ) {
xmlNodeSetPtr nodeset = result->nodesetval;
xmlNodeSetContent(nodeset->nodeTab[0], value);
xmlXPathFreeObject(result);
}
}
static void get_request(char *buffer, int *len, const char *stock_code) {
xmlChar *buff;
xmlDocPtr doc = xmlParseFile("stock.xml");
if ( doc == NULL ) {
printf("xmlParseFile failed\n");
return;
}
xmlXPathContextPtr context = xmlXPathNewContext(doc);
if ( context == NULL ) {
printf("xmlXPathNewContext failed\n");
return;
}
xmlXPathRegisterNs(context, (const xmlChar *)"soapenv", (const xmlChar *)"http://schemas.xmlsoap.org/soap/envelope/");
xmlXPathRegisterNs(context, (const xmlChar *)"web", (const xmlChar *)"http://WebXml.com.cn/");
set_value(context, (const xmlChar *)"//web:theStockCode", (const xmlChar *) stock_code);
xmlDocDumpMemory(doc, &buff, len);
strcpy(buffer, (char *) buff);
printf("%s\n", buffer);
xmlFree(buff);
xmlXPathFreeContext(context);
xmlFreeDoc(doc);
xmlCleanupParser();
}
int main(int argc, char **argv) {
const char *stock_code = argv[1];
char buffer[2048];
int len = 0;
get_request(buffer, &len, stock_code);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: text/xml;charset=UTF-8");
headers = curl_slist_append(headers, "SOAPAction: \"http://WebXml.com.cn/getStockInfoByCode\"");
CURL *curl = curl_easy_init();
if ( curl ) {
curl_easy_setopt(curl, CURLOPT_URL, "http://webservice.webxml.com.cn/WebServices/ChinaStockWebService.asmx");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buffer);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(buffer));
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
}
return 0;
}
程序需要读取stock.xml作为SOAP请求模板,也就是从soapUI测试过程中抓取的内容:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://WebXml.com.cn/">
<soapenv:Header/>
<soapenv:Body>
<web:getStockInfoByCode>
<!--Optional:-->
<web:theStockCode>?</web:theStockCode>
</web:getStockInfoByCode>
</soapenv:Body>
</soapenv:Envelope>
如果需要开发基于HTTPS的SOAP客户端的话,首先要在libcurl添加openssl支持,我的前一篇博文也有介绍:http://blog.****.net/yui/archive/2011/01/31/6170889.aspx
使用的方法基本与前面的程序一样,不过可能需要多加一句:
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
另外,如果在Code::Blocks下,编译的关键是库文件链接次序,请见上一篇博文最后的图。