gSOAP工具基于编译器技术为C/C++提供自动的SOAP和XML数据绑定. 该工具使用自动生成代码以及先进的映射方法,简化了基于C/C++的SOAP/XML Web service和XML应用程序的开发。。大多数Web services工具采用以WSDL/SOAP为中心的观点,并且提供一组API,使用这些API必须使用相应的类库来处理特定XML数据结构。这强迫用 户去适应该程序逻辑才能使用这些类库,因为用户在使用该特定厂商的API时必须编写代码去填充XML和抽取XML数据。这往往导致一个脆弱的解决方案,几 乎没有数据一致性、类型安全和XML验证的保证。与其他工具不同的是,gSOAP使用编译器技术为用户隐藏了 WSDL、SOAP、特定XML的实现细节,同时自动提供XML有效性验证、内存管理和类型安全序列化,从而提供透明的解决方案。gSOAP工具可将原有 的数据类型和用户自定义的数据类型映射成等价的XML数据类型,反之亦然。因此,通过一个简单的API得到了完美的SOAP互操作性,从而可使用用户从 WSDL/SOAP/XML的细节中解脱出来,集中精力处理应用程序逻辑。 gSOAP工具支持传统的C/C++代码(以及留有C接口的其他编程语言)、嵌入式系统、那些和其他SOAP程序共享计算资源和信息的实时SOAP/XML应用程序之间的集成,可以跨平台,适用不同的语言环境和穿透防火墙。 gSOAP工具常常使用C/C++实现XML数据绑定。这意味着程序本地数据结构可被自动地用XML编码,而不需要额外去编写转换代码。该工具还为了XML数据绑定生成XML模式,所以外部程序可以基于该模式使用XML数据。
使用gSOAP工具构建Web services应用程序或实现自动化XML数据绑定,你需要具备如下条件:
gSOAP是独立的软件,不需要安装第三方软件(除非你想使用OpenSSL或者你想重新构建soapcpp2工具,见下文). 从SourceForge获取的gSOAP包在gsoap/bin目录下有预构建的工具:(注:SourceForget是开源软件开发者进行开发管理的集中式场所,也是全球最大开源软件开发平台和仓库):
这两个程序支持Windows、Linux和Mac OS 平台,详情参考README文件。.尽管gSOAP为不同平台准备了二进制格式的工具,但他们生成的代码是等价的。这意味着生成的源代码可以移植到其他平台并进行本地化编译. 如果你没有该工具或者你想重新构建,你需要:
Bison和Flex是首选的。两者与gSOAP基于相同的开源标准发布的。
gSOAP引擎可以被构建成libgsoap.a和libgsoap++.a库,后者支持SSL。参照README.txt的指示可以看到如何通过 gSOAP包里的autoconf和automake构建与平台无关的库。或者,你将引擎的源代码stdsoap2.c(或stdsoap2.cpp)直 接编译并链接进你们工程里。(注:说明gSOAP提供两种使用方式 ,一种编译成动态链接库,或者直接将源代码编译进工程)gSOAP包中,在samples目录下有很多例子。执行make命令可以构建这些例子程序。这些例子同时也是用来展示gSOAP不同特性的。比如,在 samples/mtom-streaming中,一个流式的MTOM附件服务端和客户端程序展示高效的文件传输;在samples /webservice中,SSL-secure网络服务端展示可以为Web流览器和Web服务调用生成不同的内容。诸如此类,还有很多。
通过高级XML模式分析器和代码生成器可实现XML数据绑定,这大大减少了构建Web Service程序的难度。wsdl2h工具导入一个或多个WSDL和XML模式可以生成C/C++头文件,该文件定义了Web Service操作以及C/C++数据类型。gsoapcpp2然手根据该头文件生成XML序列化的数据类型、客户端框架代码 (soapClient.cpp)和服务端框架代码(soapServer.cpp)。gSOAP编译器也可以生成WSDL定义文件,用来从头实现一个服务。这个闭环可以使Web services开发基于WSDL文件或者基于C/C++头文件中的一系列选项,不需要用户去分析Web服务细节。你只需要遵循一些步骤执行命令行或Makefile(使用MSVC++ IDE的话参照sample目录下的MSVC++例子)。例如,为了生成计算器Web应用代码,我们通过命令行执行wsdl2h工具,从URL上的 WSDL文件生成头文件,这里使用-o指定输出文件名:
> wsdl2h -o calc.h http://www.genivia.com/calc.wsdl |
这样就生成了描述服务操作定义及数据类型定义的头文件calc.h。接着可以通过soapcpp2将该头文件生成框架代码或XML序列化例程。 calc.h头文件包含所有的说明,你可以使用Doxygen(http://www.doxygen.org)来生成开发文档。用wsdl2h生成的服务定义头文件同样包含如何调用服务的信息。在这个例子中,我们开发一个基于C++的计算器服务。默认情况下,gSOAP假定我们使用C++的STL。如不想使用STL,使用选项-s:
> wsdl2h -s -o calc.h http://www.genivia.com/calc.wsdl |
构建纯C程序,使用-c:
> wsdl2h -c -o calc.h http://www.genivia.com/calc.wsdl |
到现在为止,我们还没有生成C/C++的存根。为了生成它,我们使用soapcpp2编译器:
> soapcpp2 -i -C -Iimport calc.h |
选项-i指示我们希望得到C++代理和包含客户端(服务端)代码的对象。-C指示只生成客户端对象(默认情况下,gsoapcpp2同时生成客户端和服务 端对象)。选项-I指示需要从import目录引入stlvector.h文件,以支持STL容器序列化,import目录也在gSOAP包中。假定我们开发一个C++计算器客户端,我们使用生成的soapcalcProxy类和XML命名空间映射表calc.nsmap来访问Web服务。该soapcalcProxy类是调用服务的一个代理。:
#include "soapcalcProxy.h" #include "calc.nsmap" int main() { calcProxy service; double result; if (service.add(1.0, 2.0, result) == SOAP_OK) std::cout << "The sum of 1.0 and 2.0 is " << result << std::endl; else service.soap_stream_fault(std::cerr); service.destroy(); // 删除数据释放内存 } |
接下来我们编译并链接生成的soapC.cpp和soapcalcProxy.cpp,以及动态链接库-lgsoap++(如果你没有安装该库,可以将stdsoap2.cpp引入到你的代码)。(注:上面就构建完了基于C++的客户端).假定我们开发的是C语言客户端,那么我们可以这样: wsdl2h -c -o calc.h http://www.genivia.com/calc.wsdl and soapcpp2 -C calc.h。 这种情况下,我们的代码使用一个简单的C函数调用服务,我们还需要显式使用soap_end,soap_free删除数据和上下文
#include "soapH.h" #include "calc.nsmap" int main() { struct soap *soap = soap_new(); double result; if (soap_call_ns__add(soap, 1.0, 2.0, &result) == SOAP_OK) printf("The sum of 1.0 and 2.0 is %lg\n", result); else soap_print_fault(soap, stderr); soap_end(soap); soap_free(soap); } |
这个计算器的例子是相当简单,此处只用来说明开发过程。这与大型程序开发流程是相似的。更多的可以参考sample目录下的例子。
开发服务端程序也很简单。这里我们使用CGI,因为这是个简单的机制。(注:CGI英文全拼是Common Gateway Interface,即通用网关接口)。这不是首选的部署机制,因为CGI非常慢而且无国籍,我们建议开发独立的gSOAP HTTP/HTTPS服务(参照本节最后部分注解)或者使用Apache组件,再或者IIS(在gSOAP包的gsoap/mod_gsoap目录下)。假设我们开发一个基于CGI的服务,该服务返回GMT时间。CGI可以非常简单的将服务发布到Web站点。我们以一个gSOAP头文件开始本例,currentTime.h包含服务的定义。如果我们能得到WSDL文件,我们可以使用wsdl2h得到这样的头文件。如果没有WSDL,你可以使用C/C++重头定义一个头文件,然后使用gSOAP工具生成源代码和WSDL。我们的currentTime服务只有一个输出参数,就是当前时间,在currentTime.h文件中定义如下:
// File: currentTime.h //gsoap ns service name: currentTime //gsoap ns service namespace: urn:currentTime //gsoap ns service location: http://www.yourdomain.com/currentTime.cgi int ns__currentTime(time_t& response); |
注意,我们关联一个XML命名空间前缀“ns”和命名空间urn:currentTime到WSDL服务和SOAP/XML信息。gSOAP工具使用特殊 转化方式得到标识符名字:命名空间前缀后跟两个下划线。之所以如此处理命名空间,是为了避免命名冲突。命名空间前缀“ns”通过“//gsoap”指令绑 定到 urn:currentTime命名空间。 //gsoap指令用来设置服务属性,在本例中有 name、 namespace和 location。CGI的服务实现需要在soap上下文上调用soap_serve,soap环境通过soap_new创建。服务的具体实现就像一个函数,该函数由RPC调度器使用soap_servey调用:
// File: currentTime.cpp #include "soapH.h" // include the generated declarations #include "currentTime.nsmap" // include the XML namespace mappings int main() { // create soap context and serve one CGI-based request: return soap_serve(soap_new()); } int ns__currentTime(struct soap *soap, time_t& response) { response = time(0); return SOAP_OK; } |
注意,我们传递带有soap上下文信息的soap结构给服务例程。这非常方便于确定连接的性能,还可以使用soap_malloc(soap, num_bytes)动态申请空间,以及在服务结束时动态删除他们。我们运行soapcpp2编译器,生成服务端代码:
> soapcpp2 -S currentTime.h |
接着编译得到CGI二进制程序:
> c++ -o currentTime.cgi currentTime.cpp soapC.cpp soapServer.cpp stdsoap2.cpp |
stdsoap2.cpp可以在gsoap目录下找到。或者,如果你安装了gsoap,你可以选择链接libgsoap++库,就不用使用 stdsoap2.cpp源码了,像这样:
> c++ -o currentTime.cgi currentTime.cpp soapC.cpp soapServer.cpp -lgsoap++ |
为了激活这个服务,将生成的 currentTime.cgi二进制程序拷贝到bin-cgi目录下,并赋以恰当的权限。soapcpp2工具生成WSDL描述文件currentTime.wsdl。你可以使用这个WSDL发布你的服务。你不需要使用这个WSDL去开发客户端。因为你已经有了 currentTime.h,使用soapcpp2的-C选项可以生成客户端代码。(注:参考1.2节)CGI可以方便的通过标准I/O交换信息。因此,我们使用自动生成的请求样例代码来测试:
> ./currentTime.cgi < currentTime.currentTime.req.xml |
这样,得到的返回也是SOAP XML. 面的过程同样适用于纯C语言。只需要在soapcpp2的-S选项后添加-c选项就可以生成ANSI C代码。当然,在C语言中我们使用指针而不是引用,同时 currentTime.h文件也必须只能使用C类型。另外还有一个更优雅的C++服务端实现:使用soapcpp2的-i(或-j)选项生成C++的客户端和服务端的服务类,使用这个类同时可以构建客户端也可以构建服务端。使用这个选项就不需要 soapClient.cpp和 soapServer.cpp了,因为我们有了客户端和服务端的类实现:
> soapcpp2 -i -S currentTime.h |
这样就会生成 soapcurrentTimeService.h和 soapcurrentTimeService.cpp文件,以及辅助文件 soapStub.h和 currentTime.nsmap 。使用 currentTimeService对象,我们重写CGI服务:
// File: currentTime.cpp #include "soapcurrentTimeService.h" // include the proxy declarations #include "currentTime.nsmap" // include the XML namespace mappings int main() { // create server and serve one CGI-based request: currentTimeService server; server.serve(); server.destroy(); } int currentTimeService::currentTime(time_t& response) { response = time(0); return SOAP_OK; } |
编译
> c++ -o currentTime.cgi currentTime.cpp soapC.cpp soapcurrentTimeService.cpp -lgsoap++ |
接着安装CGI二进制文件。安装方法请查阅Web服务文档。如果想在8080端口上以迭代方式运行服务,可以使用:
while (server.run(8080) != SOAP_TCP_ERROR) server.soap_stream_fault(std::cerr); |
想实现线程服务,请查看7.2.4节。该节讲述了通过多个线程来处理多个SO来请求。线程池也可以使用。7.2.7节有更多的服务端类的描述。7.1.4节有更多的客户端代理类的描述.
换句换说, C / C++ 自动读取和编写XML数据如何映射到schemas(XSD文件。 gsoap 提供自动化的绑定机制可以将C/C++的数据绑定到XML里,任何C/C++的数据类型都可以. 一个XML模式(XSD文件)可转化为一组C或C ++的定义 ,比DOM或者SAX 解析方式更容易让你的程序使用. 从本质上来说, XML架构中的每个XML组件都对应着一个C / C++数据类型. 使用XSD来对class 进行映射优点显而易见:
|
通过这种方式, 不需要DOM遍历和SAX事件就能自动的在符合XML 架构的XML元素到类的成员的创建. 此外, C/C++ 数据绑定XML的操作是安全的. 类型安全就是是说 C/C++ 确保只有有效的XML文档可以解析和生成。 wsdl2h工具自动执行WSDL和XML模型的映射到C/C++. wsdl2h 输出 The output of wsdl2h is an annotated header file that includes comments and documentation on the use of the C/C++ declarations in a SOAP/XML client/server or in a generic application for reading and writing XML using the auto-generated serializers. We will illustrate this further with an example. Suppose we have an XML document with a book record:
<book isbn="1234567890"> <title>Farewell John Doe</title> <publisher>ABC's is our Name</publisher> </book> |
可以用XML 来表示一个书本的元素和类型
<schema ...> <element name="book"> <complexType> <sequence> <element name="title" type="string" minOccurs="1"/> <element name="publisher" type="string" minOccurs="1"/> </sequence> <attribute name="isbn" type="unsignedLong" use="required"/> </complexType> </element> </schema> |
使用 wsdl2h 转换 XML schema 定义book 类型 到一个类,如下:
class book { @ULONG64 isbn; std::string title; std::string publisher; } |
请注意上面的例子,是使用@来表示元素的属性的. 这些特殊的gSOAP注释是soapcpp2 工具生成的,用于对数据序列化读取和编写XML . soapcpp2 工具生成所有必要的代码来解析和生成XML的书对象. 验证如 minOccurs="1" 和 use="required"是否包含在生成的代码里. 用XML表示一本书, 我们首先创建一个SOAP(engine context ),并用它与soap_write_book(由soapcpp2生成的)使用标准输出写XML中的对象:
soap *ctx = soap_new1(SOAP_XML_INDENT); // new context with option book bk; bk.isbn = 1234567890; bk.title = "Farewell John Doe"; bk.publisher = "ABC's is our Name"; if (soap_write_book(ctx, &bk) != SOAP_OK) ... error ... soap_destroy(ctx); // clean up allocated class instances soap_end(ctx); // clean up allocated temporaries soap_free(ctx); // delete context |
ctx gSOAP 上下文引擎 (soap 结构类型) 用于控制设置和状态保持的。 例如XML的格式化, (e.g. SOAP_XML_INDENT), 设置序列化选项, 获取当前状态,设置I/O. 只需要简单的设置输出流 (std::ostream) ctx->os 就能将上下文重定向到string 或文件里.此外,当序列化一个图表,在代码调用soap_put方法以确保输出符合对象图的SOAP编码(你也可以使用一个基于id-ref的编码)之前,先在对象或类型上调用串行器bk.soap_serialize(ctx)。细节见7.5节,解析典型的xml成为一本书对象用:
soap *ctx = soap_new1(SOAP_XML_STRICT); // new context with option book bk; if (soap_read_book(ctx, &bk) != SOAP_OK) ... error ... else cout << bk.isbn << ", " << bk.title << ", " << bk.publisher << endl; ... further use of bk ... soap_destroy(ctx); // delete deserialized objects soap_end(ctx); // delete temporaries soap_free(ctx); // delete context |
Automatic built-in XML validation (enabled with SOAP_XML_STRICT) ensures that data members are present so we can safely print them in this example, thus ensuring consistency of data with the XML schema. Set the ctx->is input stream to read from a file/string stream instead of stdin. The soap_destroy and soap_end calls deallocate the deserialized content, so use with care. In general, memory management is automatic in gSOAP to avoid leaks. The above uses a very simple example schema. The gSOAP toolkit handles all XML schema constructs defined by the XML schema standard. The toolkit is also able to (de)serialize pointer-based C/C++ data structures (including cyclic graphs), structs/classes, unions, enums, STL containers, and even special data types such as struct tm. Therefore, the toolkit works in two directions: from WSDL/schema to C/C++ and from C/C++ to WSDL/schema. The gSOAP toolkit also handles multiple schemas defined in multiple namespaces. Normally the namespace prefixes of XML namespaces are added to the C/C++ type definitions to ensure type uniqueness. For example, if we would combine two schemas in the same application where both schemas define a book object, we need to resolve this conflict. In gSOAP this is done using namespace prefixes, rather than C++ namespaces (research has pointed out that XML namespaces are not equivalent to C++ namespaces). Thus, the book class might actually be bound to an XML namespace and the class would be named ns__book, where ns is bound to the corresponding namespace. The following options are available to control serialization:
soap->encodingStyle = NULL; // to remove SOAP 1.1/1.2 encodingStyle soap_mode(soap, SOAP_XML_TREE); // XML without id-ref (no cycles!) soap_mode(soap, SOAP_XML_GRAPH); // XML with id-ref (including cycles) soap_set_namespaces(soap, struct Namespace *nsmap); //to set xmlns bindings |
其他可用于XML 绑定的表示, 可以看 9.12 章节. 获得更多的XML绑定C/C++ 帮助,看 7.5章节.
gSOAP的亮点: