7 Quick User Guide
这个用户指南提供快速的方式来使用gSOAP。理解本节需要对SOAP协议有一个基本的理解,还需要熟悉C/C++。如果你对SOAP协议没有深入的了 解,你也可以使用gSOAP开发 C/C++的基于SOAP的C/S程序,不过,前提是客户端和服务端一致并且只在小组内通信(即,你不需要担心其他 SOAP实现的互操作性)。本节开始说明gSOAP Web服务端和客户端的实现,进而说明与其他SOAP实现的互操作性,比如Apache Axis、SOAP::Lite和.NET。这需要了解SOAP和WSDL协议细节。
7.1 如何构建SOAP/XML 客户端
一般情况下,SOAP客户端程序的实现需要一个存根(也称做服务代理),该存根提供客户端调用服务的操作。存根的主要职责是处理参数,通过网络向指定的服 务端发送请求,并等待应答,应答到来后再将参数解析出来。客户端只需要调用存根提供的例程,就像调用本地的函数一样。手工编写存根例程是非常麻烦的,特别 是当输入输入参数涉及到复杂数据结构,如对象、结构体、容器、数组及指针指向的图形结构。幸运的是,gSOAP wsdl2h WSDL解析工具和soapcpp2存根生成器和序列化代码生成器可自动开发Web Service客户端和服务端。soapcpp2生成必要的胶水代码(也称作存根或框架)来构建客户端和服务端。soapcpp2输入一个注解服务的C/C++头文件。该头文件可以通过wsdl2h解析工具生成,该工具需要一个WSDL文档,考虑下面的命令:
> wsdl2h -o calc.h http://www.genivia.com/calc.wsdl |
这将生成一个 calc.h 带有注释 C++头文件,该文件描述了服务的定义. The WSDL specification (可能包括多个导入WSDL文件和XSD架构文件) is mapped to C++ using C++ databindings for SOAP/XML. 生成的头文件包含操作service 的数据类型和信息和WSDL和XML模式相关的元信息.要生成一个服务定义头文件去开发一个纯 C 的客户端英语, 使用 -c 选项:
> wsdl2h -c -o calc.h http://www.genivia.com/calc.wsdl |
欲了解更多WSDL解析器及其选项的细节, 看 8. 通过服务定义的 calc.h 让gSOAP soapcpp2 编译器进一步处理,生成的胶合的逻辑代码让客户端去调用Web service. 通过calc.h 我们看到 service 方法 指定的一个函数原型(function prototypes)例如项目, 两个double 类型的相加的函数:
int ns2__add(double a, double b, double& result); |
ns2__add函数使用XML命名空间前缀以区别于其他命名空间中定义的操作, 从而避免名称冲突. gSOAP 约定 操作, 类型,结构(struct) 和类的成员(class members)都需要添加一个XML 命名空间前缀,并wsdl2h 工具自动生成。但它不是强制性的,如转换现有的C/C++ 类型来操作web services. 因此, 定义的头文件可以省略前缀符号跟 soapcpp2 工具生成的客户端和服务器端交换现有的(原始)数据类型。使用gSOAP的soapcpp2 工具来生成这些函数原型的(function prototypes)存根和代理进行远程调用:
|
因此,逻辑生成的存根例程(stub routines),让C和C ++客户端应用程序无缝地与现有的SOAP Web服务交互,下一章节将展示客户端代码示例. SOAP服务操作的输入和输出参数,可以是原始数据类型或复杂的复合数据类型,如容器和基于指针的链接的数据结构. 这些被定义在头文件中,是由WSDL解析器生成或手动所指定的。 gSOAP soapcpp2 工具对数据类型自动生成 XML 序列化(XML serializers)和(XML deserializers),使生成的存根例程在SOAP/ XML服务操作的参数的内容进行编码和解码. Note that 需要注意的是gsoap的soapcpp2工具生成骨架例程 soapServer.cpp 在头文件中指定的每个服务操作. 骨架例程可以很容易地用来实现一个新的SOAP的Web服务中的服务操作中的一个或多个。这些骨架例程在C++中 不用于构建SOAP客户端,虽然它们可以被用来建立混合SOAP客户端/服务器应用程序(点对点应用).
7.1.1 例子
服务操作两个float ( 在上一节介绍 如何使用 wsdl2h工具 获取 calc.h 文件的声明) 值相加. WSDL描述服务提供端点调用该服务的操作和操作所使用的XML命名空间:
|
每一个 service 操作都包含 SOAP action,这是一个可选参数用于标示请求和相应消息的操作. SOAP RPC-encoded 服务的请求和相应消息是通过简单的C 函数的输入参数和输出参数实现的的 . 下面是一个添加操作的绑定细节:
|
这些信息是 wsdl2h 生成的头文件和服务的定义转换的. wsdl2h 工具生成的头文件 calc.h 包含的如下的指令和声明: (实际输出的内容是由发行的版本和使用的选项来实现的):
//gsoap ns2 service name: calc //gsoap ns2 service type: calcPortType //gsoap ns2 service port: http://websrv.cs.fsu.edu/ engelen/calcserver.cgi //gsoap ns2 service namespace: urn:calc //gsoap ns2 service method-protocol: add SOAP //gsoap ns2 service method-style: add rpc //gsoap ns2 service method-encoding: add http://schemas.xmlsoap.org/soap/encoding/ //gsoap ns2 service method-action: add "" int ns2__add(double a, double b, double& result); |
其他计算器操作是相似的,为了清楚起见,这里省略. //gsoap 指令都需要 soapcpp2 工具生成代码 这符合SOAP 协议. 对于该服务使用 SOAP 协议和“RPC 编码风格” . 详细的 //gsoap 指令介绍, 请看 19.2 章节. 服务的操作定义的函数原型和所有的非原始的参数类型都需要在头文件中定义 (在本例子中所有的参数都是原始类型). 计算器 add 操作需要两个 float 类型的 a 和 b, 并且返回两着的和。按照习惯, 除了最后一个,所有的参数都输入参数. 最后一个参数是输出参数. struct 或者 class 包裹着多个输出参数, 看 7.1.9章节 . 最后的一个参数必须是一个指针或者是引用. 相比之下, the 相比之下,输入参数支持按值传递或指针,但不通过C ++引用. 相关的函数原型与服务操作总是返回一个int. 该值指示成功(0或等价SOAP_OK)或失败(任何非零值). 请看 10.2章节来了解关于非零的错误代码. 作用在服务操作的函数原型名称的命名空间(ns2__)的详细细节请看 The 7.1.2. 基本上, 命名空间前缀与函数的名称或类型的名称前面都添加一对下划线, 例如 ns2__add, ns2 是命名空间的前缀而 add 是服务操作的具体名称.这一机制可以确保与服务相关的炒作和类型的唯一性. 强烈建议您选择和设置的自己名称命名空间前缀. This avoids problems when running wsdl2h on multiple WSDLs where the sequence of prefixes ns1, ns2, and so on are arbitrarily assigned to the services. 所有的操作和服务类型要选择一个前缀名, 给计算器服务设置一个前缀 c__ f, 添加下面一行到 typemap.dat:
c = "urn:calc" |
接着运行 wsdl2h. wsdl2h 工具使用 typemap.dat 文件配置特别的服务数据绑定类型和数据类型. 返回的结果是由c__add 具有唯一标示的操作而不是一个非常随意的名字ns2__add. 注意在名称中使用下划线:在XML一个下划线在标识符名称将被翻译成一个破折号, 因为破折号在XML中的频率要比下划线使用的频繁, 具体请看 10.3 。下面. 通过命令行调用 gSOAP soapcpp2 工具来处理calc.h文件里面对服务的一些定义:
> soapcpp2 calc.h |
工具生成service操作的 存根例程(stub routines). 客户端程序可以通过存根例程调用远程的service操作. 生成的存根例程的接口是相同的函数原型 - 在calc.h服务定义文件, but with additional parameters to pass the gSOAP engine's runtime context soap, an endpoint URL (or NULL for the default), and a SOAP action (or NULL for the default):
int soap_call_c__add(struct soap *soap, char *URL, char *action, double a, double b, double& result); |
存根例程(stub routines)保存在 soapClient.cpp.而文件 soapC.cpp 包含数据序列化和反序列化的操作存根例程. 当你确实需要时,可以使用 soapcpp2 -C 选项生成纯C 的代码。注意:soap 参数必须是一个有效的指针指向gSOAP 运行上下文. URL 可以覆盖默认的端点 (WSDL默认的端点). action 参数可以覆盖默认的SOAP action .下面是一个C/C++ 客户端程序使用存根的案例:
#include "soapH.h" // include all interfaces (library and generated) #include "calc.nsmap" // import the generated namespace mapping table int main() { double sum; struct soap soap; // the gSOAP runtime context soap_init(&soap); // initialize the context (only once!) if (soap_call_c__add(&soap, NULL, NULL, 1.0, 2.0, &sum) == SOAP_OK) std::cout << "Sum = " << sum << std::endl; else // an error occurred soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream soap_destroy(&soap); // delete deserialized class instances (for C++) soap_end(&soap); // remove deserialized data and clean up soap_done(&soap); // detach the gSOAP context return 0; } |
返回 SOAP_OK (zero) 为成功而非零都为失败. 当发生错误时可使用soap_print_fault 函数来显示错误信息. 使用 soap_sprint_fault(struct soap*, char *buf, size_t len) 将错误答应道一个字符串里, 再使用 soap_stream_fault(struct soap*, std::ostream&) 将错误信息放入到流中 (仅仅C++实现). 以下函数可用于明确设立了GSOAP的运行时上下文(soap):
|
一个运行上下文(runtime context)可以被客户端调用多次而不需要重复初始化. 每一个新的线程都需要一个新的上下文(context) is required for each new thread to 保证线程独占访问运行时上下文.另外任何客户端调用一个活动的服务都需要一个新的上下文。 soapcpp2 代码生成工具另外还可以使用-i(or -j) 生成C++ 客户端服务代理类(proxy class) (用于服务器应用程序的服务对象)) :
> soapcpp2 -i calc.h |
代理定义在下面文件中:
|
#include "soapcalcProxy.h" // get proxy #include "calc.nsmap" // import the generated namespace mapping table int main() { calcProxy calc(SOAP_XML_INDENT); double sum; if (calc.add(1.0, 2.0, sum) == SOAP_OK) std::cout << "Sum = " << sum << std::endl; else calc.soap_stream_fault(std::cerr); return calc.error; // nonzero when error } |
代理类派生自结构类型struct soap 运行上下文,因此它继承struct soap 运行上下文所有的状态信息。代理的类的构造函数需要一个构造上下文构造模型参数(context mode parameters), e.g.上面的例子使用的是 SOAP_XML_INDENT. 将上述代码与 soapcalcProxy.cpp, soapC.cpp, 和 stdsoap2.cpp (or use libgsoap++.a) 进行编译连接. 代理类的名称来自WSDL内容并不是一直都是这么短,你可以随意进行修改。
//gsoap ns2 service name: calc |
接下来使用新的名称重新运行soapcpp2代码生成器 . 当客户端应用程序调用,执行一个SOAP请求:
POST / engelen/calcserver.cgi HTTP/1.1 Host: websrv.cs.fsu.edu User-Agent: gSOAP/2.7 Content-Type: text/xml; charset=utf-8 Content-Length: 464 Connection: close SOAPAction: "" <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:c="urn:calc"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <c:add> <a>1</a> <b>2</b> </c:add> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
SOAP响应消息:
HTTP/1.1 200 OK Date: Wed, 05 May 2010 16:02:21 GMT Server: Apache/2.0.52 (Scientific Linux) Content-Length: 463 Connection: close Content-Type: text/xml; charset=utf-8 <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:calc"> <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:addResponse> <result>3</result> </ns:addResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
一个客户端可以调用一系列服务操作:
#include "soapcalcProxy.h" // get proxy #include "calc.nsmap" // import the generated namespace mapping table int main() { calcProxy calc(SOAP_IO_KEEPALIVE); // keep-alive improves connection performance double sum = 0.0; double val[] = 5.0, 3.5, 7.1, 1.2 ; for (int i = 0; i < 4; i++) if (calc.add(sum, val[i], sum)) return calc.error; std::cout << "Sum = " << sum << std::endl; return 0; } |
In the above, 直到代理被删除之前没有数据被释放. To deallocate deserialized data between the calls, use:
for (int i = 0; i < 4; i++) { if (calc.add(sum, val[i], sum)) return calc.error; calc.destroy(); } |
在这里进行释放是安全的, 因为浮点型数据复制并保存在 sum. In other scenarios one must make sure data is copied or removed from the deallocation chain with:
soap_unlink(struct soap *soap, const void *data) |
也就是说在每个数据项被保存的时候调用, 在释放数据之前. 当代理类被删除时,其他的反序列化的数据也要被删除 . 使用委托删除另一个上下文。使用:
soap_delegate_deletion(struct soap *soap_from, struct soap *soap_to) |
例如
struct soap soap; soap_init(&soap); { // create proxy calcProxy calc; ... data generated ... soap_delegate_deletion(&calc, &soap); } // proxy deleted ... data used ... soap_destroy(&soap); soap_end(&soap); soap_done(&soap); |
在 C (使用 wsdl2h -c) 里面使用下面的例子:
#include "soapH.h" #include "calc.nsmap" int main() { struct soap soap; double sum = 0.0; double val[] = 5.0, 3.5, 7.1, 1.2 ; int i; for (i = 0; i < 4; i++) soap_init1(&soap, SOAP_IO_KEEPALIVE); if (soap_call_c__add(&soap, NULL, NULL, sum, val[i], &sum)) return soap.error; printf("Sum = %lg\n", sum); soap_end(&soap); soap_done(&soap); return 0; } |
上述代码与 soapClient.c, soapC.c, 和 stdsoap2.c (libgsoap.a) 编译和链接.
7.1.2 XML 命名空间注意事项
声明的 ns2__add 函数原型 (discussed in the previous section) 使用的命名空间前缀 ns2__ 使用的是服务操作的命名空间, 这是以一对下划线来从服务操作名称来分离函数的名称和命名空间前缀。 The purpose of a namespace prefix is to associate a service operation name with a service in order to prevent naming conflicts, e.g. to distinguish identical service operation names used by different services. Note that the XML response of the service example uses a namespace prefix that may be different (e.g. ns) as long as it bound to the same namespace name urn:calc through the xmlns:ns="urn:calc binding. The use of namespace prefixes and namespace names is also required to enable SOAP applications to validate the content of SOAP messages. The namespace name in the service response is verified by the stub routine by using the information supplied in a namespace mapping table that is required to be part of gSOAP client and service application codes. The table is accessed at run time to resolve namespace bindings, both by the generated stub's data structure serializer for encoding the client request and by the generated stub's data structure deserializer to decode and validate the service response. The namespace mapping table should not be part of the header file input to the gSOAP soapcpp2 tool. Service details including namespace bindings may be provided with gSOAP directives in a header file, see Section 19.2. The namespace mapping table is:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name"} {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, // MUST be first {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, // MUST be second {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, // MUST be third {"xsd", "http://www.w3.org/2001/XMLSchema"}, // 2001 XML Schema {"ns2", "urn:calc"}, // given by the service description {NULL, NULL} // end of table }; |
The first four namespace entries in the table consist of the standard namespaces used by the SOAP 1.1 protocol. In fact, the namespace mapping table is explicitly declared to enable a programmer to specify the SOAP encoding style and to allow the inclusion of namespace-prefix with namespace-name bindings to comply to the namespace requirements of a specific SOAP service. For example, the namespace prefix ns2, which is bound to urn:calc by the namespace mapping table shown above, is used by the generated stub routine to encode the add request. This is performed automatically by the gSOAP soapcpp2 tool by using the ns2 prefix of the ns2__add method name specified in the calc.h header file. In general, if a function name of a service operation, struct name, class name, enum name, or field name of a struct or class has a pair of underscores, the name has a namespace prefix that must be defined in the namespace mapping table. The namespace mapping table will be output as part of the SOAP Envelope by the stub routine. For example:
... <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns2="urn:calc" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> ... |
The namespace bindings will be used by a SOAP service to validate the SOAP request.
7.1.3 Example
The incorporation of namespace prefixes into C++ identifier names is necessary to distinguish service operations that share the same name but are provided by separate Web services and/or organizations. It avoids potential name clashes, while sticking to the C syntax. The C++ proxy classes generated with soapcpp2 -i (or -j) drop the namespace prefix from the method names The namespace prefix convention is also be applied to non-primitive types. For example, class names are prefixed to avoid name clashes when the same name is used by multiple XML schemas. This ensures that the XML databinding never suffers from conflicting schema content. For example:
class e__Address // an electronic address from schema 'e' { char *email; char *url; }; class s__Address // a street address from schema 's' { char *street; int number; char *city; }; |
The namespace prefix is separated from the name of a data type by a pair of underscores (__). An instance of e__Address is encoded by the generated serializer for this type as an Address element with namespace prefix e:
<e:Address xsi:type="e:Address"> <email xsi:type="string">me@home</email> <url xsi:type="string">www.me.com</url> </e:Address> |
While an instance of s__Address is encoded by the generated serializer for this type as an Address element with namespace prefix s:
<s:Address xsi:type="s:Address"> <street xsi:type="string">Technology Drive</street> <number xsi:type="int">5</number> <city xsi:type="string">Softcity</city> </s:Address> |
The namespace mapping table of the client program must have entries for e and s that refer to the XML Schemas of the data types:
struct Namespace namespaces[] = { ... {"e", "http://www.me.com/schemas/electronic-address"}, {"s", "http://www.me.com/schemas/street-address"}, ... |
This table is required to be part of the client application to allow access by the serializers and deserializers of the data types at run time.
7.1.4 How to Generate C++ Client Proxy Classes
Proxy classes for C++ client applications are automatically generated by the gSOAP soapcpp2 tool, as was shown in Section 7.1.1. There is a new and improved code generation capability for proxy classes, which is activated with the soapcpp2 -i (or j) option. These new proxy classes are derived from the soap structure, have a cleaner interface and offer more capabilites. With C++, you can also use wsdl2h option -qname to generate the proxy in a C++ namespace name. This is very useful if you want to create multiple proxies for services by repeated use of wsdl2h and combine them in one code. Alternatively, you can run wsdl2h just once on all service WSDLs and have soapcpp2 generate multiple proxies for you. The latter approach does not use C++ namespaces and may reduce the overall amount of code. To illustrate the generation of a "standard" (old-style) proxy class, the calc.h header file example of the previous section is augmented with the appropriate directives to enable the gSOAP soapcpp2 tool to generate the proxy class. Directives are included in the generated header file by the wsdl2h WSDL importer:
// Content of file "calc.h": //gsoap ns2 service name: calc //gsoap ns2 service port: http://websrv.cs.fsu.edu/ engelen/calcserver.cgi //gsoap ns2 service protocol: SOAP1.1 //gsoap ns2 service style: rpc //gsoap ns2 service encoding: encoded //gsoap ns2 service namespace: urn:calc //gsoap ns2 service method-protocol: add SOAP //gsoap ns2 service method-style: add rpc //gsoap ns2 service method-encoding: add encoded //gsoap ns2 service method-action: add "" int ns2__add(double a, double b, double& result); //gsoap ns2 service method-protocol: sub SOAP //gsoap ns2 service method-style: sub rpc //gsoap ns2 service method-encoding: sub encoded //gsoap ns2 service method-action: sub "" int ns2__sub(double a, double b, double& result); //gsoap ns2 service method-protocol: mul SOAP //gsoap ns2 service method-style: mul rpc //gsoap ns2 service method-encoding: mul encoded //gsoap ns2 service method-action: mul "" int ns2__mul(double a, double b, double& result); ... |
The first three directives provide the service details, which is used to name the proxy class, the service location port (endpoint), and the XML namespace. The subsequent groups of three directives per method define the operation's SOAP style (RPC) and encoding (SOAP encoded), and SOAP action string. These directives can be provided for each service operation when the SOAPAction is required, such as with SOAP1.1 RPC encoded and when WS-Addressing is used. In this example, the service protocol is set by default for all operations to use SOAP 1.1 RPC encoding. For //gsoap directive details, see Section 19.2. The soapcpp2 tool takes this header file and generates a proxy soapcalcProxy.h with the following contents (not using option -i):
#include "soapH.h" class calc { public: struct soap *soap; const char *endpoint; calc() { ... }; ~calc() { ... }; virtual int ns2__add(double a, double b, double& result) { return soap ? soap_call_ns2__add(soap, endpoint, NULL, a, b, result) : SOAP_EOM; }; virtual int ns2__sub(double a, double b, double& result) { return soap ? soap_call_ns2__sub(soap, endpoint, NULL, a, b, result) : SOAP_EOM; }; virtual int ns2__mul(double a, double b, double& result) { return soap ? soap_call_ns2__mul(soap, endpoint, NULL, a, b, result) : SOAP_EOM; }; ... }; |
The gSOAP context and endpoint are declared public to enable access. This generated proxy class can be included into a client application together with the generated namespace table as shown in this example:
#include "soapcalcProxy.h" // get proxy #include "calc.nsmap" // get namespace bindings int main() { calc s; double r; if (s.ns2__add(1.0, 2.0, r) == SOAP_OK) std::cout << r << std::endl; else soap_print_fault(s.soap, stderr); return 0; } |
The constructor allocates and initializes a gSOAP context for the instance. You can use soapcpp2 option -n together with -p to create a local namespaces table to avoid link conflicts when you need multiple namespace tables or need to combine multiple clients, see also Sections 9.1 and 19.36, and you can use a C++ code namespace to create a namespace qualified proxy class, see Section 19.35. The soapcpp2 -i option to generate proxy classes derived from the base soap structure. In addition, these classes offer more functionality as illustrated in Section 7.1.1.
7.1.5 XSD Type Encoding Considerations
Many SOAP services require the explicit use of XML Schema types in the SOAP payload. The default encoding, which is also adopted by the gSOAP soapcpp2 tool, assumes SOAP RPC encoding which only requires the use of types to handle polymorphic cases. Nevertheless, the use of XSD typed messages is advised to improve interoperability. XSD types are introduced with typedef definitions in the header file input to the gSOAP soapcpp2 tool. The type name defined by a typedef definition corresponds to an XML Schema type (XSD type). For example, the following typedef declarations define various built-in XSD types implemented as primitive C/C++ types:
// Contents of header file: ... typedef char *xsd__string; // encode xsd__string value as the xsd:string schema type typedef char *xsd__anyURI; // encode xsd__anyURI value as the xsd:anyURI schema type typedef float xsd__float; // encode xsd__float value as the xsd:float schema type typedef long xsd__int; // encode xsd__int value as the xsd:int schema type typedef bool xsd__boolean; // encode xsd__boolean value as the xsd:boolean schema type typedef unsigned long long xsd__positiveInteger; // encode xsd__positiveInteger value as the xsd:positiveInteger schema type ... |
This easy-to-use mechanism informs the gSOAP soapcpp2 tool to generate serializers and deserializers that explicitly encode and decode the primitive C++ types as built-in primitive XSD types when the typedefed type is used in the parameter signature of a service operation (or when used nested within structs, classes, and arrays). At the same time, the use of typedef does not force any recoding of a C++ client or Web service application as the internal C++ types used by the application are not required to be changed (but still have to be primitive C++ types, see Section 11.3.2 for alternative class implementations of primitive XSD types which allows for the marshalling of polymorphic primitive types).
7.1.6 Example
Reconsider the calculator example, now rewritten with explicit XSD types to illustrate the effect:
// Contents of file "calc.h": typedef double xsd__double; int ns2__add(xsd__string a, xsd__double b, xsd__double &Result); |
When processed by the gSOAP soapcpp2 tool it generates source code for the function soap_call_ns2__add, which is identical to the C-style SOAP call:
int soap_call_ns2__add(struct soap *soap, char *URL, char *action, double a, double b, double& result); |
The client application does not need to be rewritten and can still call the proxy using the "old" C-style function signatures. In contrast to the previous implementation of the stub however, the encoding and decoding of the data types by the stub has been changed to explicitly use the XSD types in the message payload. For example, when the client application calls the proxy, the proxy produces a SOAP request with an xsd:double (the xsi:type is shown when the soapcpp2 -t option is used):
... <SOAP-ENV:Body> <ns2:add> <a xsi:type="xsd:string">1.0</a> <b xsi:type="xsd:string">2.0</b> </ns2:add> </SOAP-ENV:Body> ... |
The service response is:
... <soap:Body> <n:addResponse xmlns:n="urn:calc"> <result xsi:type="xsd:double">3.0</result> </n:addResponse> </soap:Body> ... |
The validation of this service response by the stub routine takes place by matching the namespace names (URIs) that are bound to the xsd namespace prefix. The stub also expects the addResponse element to be associated with URI urn:calc through the binding of the namespace prefix ns2 in the namespace mapping table. The service response uses namespace prefix n for the addResponse element. This namespace prefix is bound to the same URI urn:calc and therefore the service response is valid. When the XML is not well formed or does not pass validation, the response is rejected and a SOAP fault is generated. The validation level can be increased with the SOAP_XML_STRICT flag, but this is not advised for SOAP RPC encoded messaging.
7.1.7 How to Change the Response Element Name
There is no standardized convention for the response element name in a SOAP RPC encoded response message, although it is recommended that the response element name is the method name ending with "Response". For example, the response element of add is addResponse. The response element name can be specified explicitly using a struct or class declaration in the header file. This name must be qualified by a namespace prefix, just as the operation name should use a namespace prefix. The struct or class name represents the SOAP response element name used by the service. Consequently, the output parameter of the service operation must be declared as a field of the struct or class. The use of a struct or a class for the service response is fully SOAP 1.1 compliant. In fact, the absence of a struct or class indicates to the soapcpp2 tool to automatically generate a struct for the response which is internally used by a stub.
7.1.8 Example
Reconsider the calculator service operation specification which can be rewritten with an explicit declaration of a SOAP response element as follows:
// Contents of "calc.h": typedef double xsd__double; struct ns2__addResponse {xsd__double result;}; int ns2__add(xsd__string a, xsd__double b, struct ns2__addResponse &r); |
The SOAP request and response messages are the same as before:
... <SOAP-ENV:Body> <ns2:add> <a xsi:type="xsd:string">1.0</a> <b xsi:type="xsd:string">2.0</b> </ns2:add> </SOAP-ENV:Body> ... |
The difference is that the service response is required to match the specified addResponse name and its namespace URI:
... <soap:Body> <n:addResponse xmlns:n='urn:calc'> <result xsi:type="xsd:double">3.0</result> </n:addResponse> </soap:Body> ... |
This use of a struct or class enables the adaptation of the default SOAP response element name and/or namespace URI when required.
7.1.9 How to Specify Multiple Output Parameters
The gSOAP soapcpp2 tool compiler uses the convention that the last parameter of the function prototype declaration of a service operation in a header file is also the only single output parameter of the method. All other parameters are considered input parameters of the service operation. To specify a service operation with multiple output parameters, a struct or class must be declared for the service operation response, see also 7.1.7. The name of the struct or class must have a namespace prefix, just as the service method name. The fields of the struct or class are the output parameters of the service operation. Both the order of the input parameters in the function prototype and the order of the output parameters (the fields in the struct or class) is not significant. However, the SOAP 1.1 specification states that input and output parameters may be treated as having anonymous parameter names which requires a particular ordering, see Section 7.1.13.
7.1.10 Example
As an example, consider a hypothetical service operation getNames with a single input parameter SSN and two output parameters first and last. This can be specified as:
// Contents of file "getNames.h": int ns3__getNames(char *SSN, struct ns3__getNamesResponse {char *first; char *last;} &r); |
The gSOAP soapcpp2 tool takes this header file as input and generates source code for the function soap_call_ns3__getNames. When invoked by a client application, the proxy produces the SOAP request:
... <SOAP-ENV:Envelope ... xmlns:ns3="urn:names" ...> ... <ns3:getNames> <SSN>999 99 9999</SSN> </ns3:getNames> ... |
The response by a SOAP service could be:
... <m:getNamesResponse xmlns:m="urn:names"> <first>John</first> <last>Doe</last> </m:getNamesResponse> ... |
where first and last are the output parameters of the getNames service operation of the service. As another example, consider a service operation copy with an input parameter and an output parameter with identical parameter names (this is not prohibited by the SOAP 1.1 protocol). This can be specified as well using a response struct:
// Content of file "copy.h": int X_rox__copy_name(char *name, struct X_rox__copy_nameResponse {char *name;} &r); |
The use of a struct or class for the service operation response enables the declaration of service operations that have parameters that are passed both as input and output parameters. The gSOAP soapcpp2 compiler takes the copy.h header file as input and generates the soap_call_X_rox__copy_name proxy. When invoked by a client application, the proxy produces the SOAP request:
... <SOAP-ENV:Envelope ... xmlns:X-rox="urn:copy" ...> ... <X-rox:copy-name> <name>SOAP</name> </X-rox:copy-name> ... |
The response by a SOAP copy service could be something like:
... <m:copy-nameResponse xmlns:m="urn:copy"> <name>SOAP</name> </m:copy-nameResponse> ... |
The name will be parsed and decoded by the proxy and returned in the name field of the struct X_rox__copy_nameResponse &r parameter.
7.1.11 How to Specify Output Parameters With struct/class Compound Data Types
If the single output parameter of a service operation is a complex data type such as a struct or class it is necessary to specify the response element of the service operation as a struct or class at all times. Otherwise, the output parameter will be considered the response element (!), because of the response element specification convention used by gSOAP, as discussed in 7.1.7.
7.1.12 Example
This is best illustrated with an example. The Flighttracker service by ObjectSpace provides real time flight information for flights in the air. It requires an airline code and flight number as parameters. The service operation name is getFlightInfo and the method has two string parameters: the airline code and flight number, both of which must be encoded as xsd:string types. The method returns a getFlightResponse response element with a return output parameter that is of complex type FlightInfo. The type FlightInfo is represented by a class in the header file, whose field names correspond to the FlightInfo accessors:
// Contents of file "flight.h": typedef char *xsd__string; class ns2__FlightInfo { public: xsd__string airline; xsd__string flightNumber; xsd__string altitude; xsd__string currentLocation; xsd__string equipment; xsd__string speed; }; struct ns1__getFlightInfoResponse {ns2__FlightInfo return_;}; int ns1__getFlightInfo(xsd__string param1, xsd__string param2, struct ns1__getFlightInfoResponse &r); |
The response element ns1__getFlightInfoResponse is explicitly declared and it has one field: return_ of type ns2__FlightInfo. Note that return_ has a trailing underscore to avoid a name clash with the return keyword, see Section 10.3 for details on the translation of C++ identifiers to XML element names. The gSOAP soapcpp2 compiler generates the soap_call_ns1__getFlightInfo proxy. Here is an example fragment of a client application that uses this proxy to request flight information:
struct soap soap; ... soap_init(&soap); ... soap_call_ns1__getFlightInfo(&soap, "testvger.objectspace.com/soap/servlet/rpcrouter", "urn:galdemo:flighttracker", "UAL", "184", r); ... struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC","http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns1", "urn:galdemo:flighttracker"}, {"ns2", "http://galdemo.flighttracker.com"}, {NULL, NULL} }; |
When invoked by a client application, the proxy produces the SOAP request:
POST /soap/servlet/rpcrouter HTTP/1.1 Host: testvger.objectspace.com Content-Type: text/xml Content-Length: 634 SOAPAction: "urn:galdemo:flighttracker" <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:galdemo:flighttracker" xmlns:ns2="http://galdemo.flighttracker.com" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getFlightInfo xsi:type="ns1:getFlightInfo"> <param1 xsi:type="xsd:string">UAL</param1> <param2 xsi:type="xsd:string">184</param2> </ns1:getFlightInfo> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
The Flighttracker service responds with:
HTTP/1.1 200 ok Date: Thu, 30 Aug 2001 00:34:17 GMT Server: IBM_HTTP_Server/1.3.12.3 Apache/1.3.12 (Win32) Set-Cookie: sesessionid=2GFVTOGC30D0LGRGU2L4HFA;Path=/ Cache-Control: no-cache="set-cookie,set-cookie2" Expires: Thu, 01 Dec 1994 16:00:00 GMT Content-Length: 861 Content-Type: text/xml; charset=utf-8 Content-Language: en <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getFlightInfoResponse xmlns:ns1="urn:galdemo:flighttracker" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xmlns:ns2="http://galdemo.flighttracker.com" xsi:type="ns2:FlightInfo"> <equipment xsi:type="xsd:string">A320</equipment> <airline xsi:type="xsd:string">UAL</airline> <currentLocation xsi:type="xsd:string">188 mi W of Lincoln, NE</currentLocation> <altitude xsi:type="xsd:string">37000</altitude> <speed xsi:type="xsd:string">497</speed> <flightNumber xsi:type="xsd:string">184</flightNumber> </return> </ns1:getFlightInfoResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
The proxy returns the service response in variable r of type struct ns1__getFlightInfoResponse and this information can be displayed by the client application with the following code fragment:
cout << r.return_.equipment << " flight " << r.return_.airline << r.return_.flightNumber << " traveling " << r.return_.speed << " mph " << " at " << r.return_.altitude << " ft, is located " << r.return_.currentLocation << endl; |
This code displays the service response as:
A320 flight UAL184 traveling 497 mph at 37000 ft, is located 188 mi W of Lincoln, NE |
Note: the flight tracker service is no longer available since 9/11/2001. It is kept in the documentation as an example to illustrate the use of structs/classes and response types.
7.1.13 How to Specify Anonymous Parameter Names
The SOAP RPC encoding protocol allows parameter names to be anonymous. That is, the name(s) of the output parameters of a service operation are not strictly required to match a client's view of the parameters names. Also, the input parameter names of a service operation are not strictly required to match a service's view of the parameter names. The gSOAP soapcpp2 compiler can generate stub and skeleton routines that support anonymous parameters. Parameter names are implicitly anonymous by omitting the parameter names in the function prototype of the service operation. For example:
// Contents of "calc.h": typedef double xsd__double; int ns2__add(xsd__string, xsd__double, xsd__double &); |
To make parameter names explicitly anonymous on the receiving side (client or service), the parameter names should start with an underscore (_) in the function prototype in the header file. For example:
// Contents of "calc.h": typedef double xsd__double; int ns2__add(xsd__string _a, xsd__double _b, xsd__double & _return); |
In this example, the _a, _b, and _return are anonymous parameters. As a consequence, the service response to a request made by a client created with gSOAP using this header file specification may include any name for the output parameter in the SOAP payload. The input parameters may also be anonymous. This affects the implementation of Web services in gSOAP and the matching of parameter names by the service. Caution: when anonymous parameter names are used, the order of the parameters in the function prototype of a service operation is significant.
7.1.14 How to Specify a Method with No Input Parameters
To specify a service operation that has no input parameters, just provide a function prototype with one parameter which is the output parameter (some C/C++ compilers will not compile and complain about an empty struct: use compile flag -DWITH_NOEMPTYSTRUCT to compile the generated code for these cases). This struct is generated by gSOAP to contain the SOAP request message. To fix this, provide one input parameter of type void* (gSOAP can not serialize void* data). For example:
struct ns3__SOAPService { public: int ID; char *name; char *owner; char *description; char *homepageURL; char *endpoint; char *SOAPAction; char *methodNamespaceURI; char *serviceStatus; char *methodName; char *dateCreated; char *downloadURL; char *wsdlURL; char *instructions; char *contactEmail; char *serverImplementation; }; struct ArrayOfSOAPService {struct ns3__SOAPService *__ptr; int __size;}; int ns__getAllSOAPServices(void *_, struct ArrayOfSOAPService &_return); |
The ns__getAllSOAPServices method has one void* input parameter which is ignored by the serializer to produce the request message. Most C/C++ compilers allow empty structs and therefore the void* parameter is not required.
7.1.15 How to Specify a Method with No Output Parameters
To specify a service operation that has no output parameters, just define a function prototype with a response struct that is empty. For example:
enum ns__event { off, on, stand_by }; int ns__signal(enum ns__event in, struct ns__signalResponse { } *out); |
Since the response struct is empty, no output parameters are specified. Some SOAP resources refer to SOAP RPC with empty responses as one way SOAP messaging. However, we refer to one-way massaging by asynchronous explicit send and receive operations as described in Section 7.3. The latter view of one-way SOAP messaging is also in line with Basic Profile 1.0.
7.2 How to Build SOAP/XML Web Services
The gSOAP soapcpp2 compiler generates skeleton routines in C++ source form for each of the service operations specified as function prototypes in the header file processed by the gSOAP soapcpp2 compiler. The skeleton routines can be readily used to implement the service operations in a new SOAP Web service. The compound data types used by the input and output parameters of service operations must be declared in the header file, such as structs, classes, arrays, and pointer-based data structures (graphs) that are used as the data types of the parameters of a service operation. The gSOAP soapcpp2 compiler automatically generates serializers and deserializers for the data types to enable the generated skeleton routines to encode and decode the contents of the parameters of the service operations. The gSOAP soapcpp2 compiler also generates a service operation request dispatcher routine that will serve requests by calling the appropriate skeleton when the SOAP service application is installed as a CGI application on a Web server.
7.2.1 Example
The following example specifies three service operations to be implemented by a new SOAP Web service:
// Contents of file "calc.h": typedef double xsd__double; int ns__add(xsd__double a, xsd__double b, xsd__double &result); int ns__sub(xsd__double a, xsd__double b, xsd__double &result); int ns__sqrt(xsd__double a, xsd__double &result); |
The add and sub methods are intended to add and subtract two double floating point numbers stored in input parameters a and b and should return the result of the operation in the result output parameter. The qsrt method is intended to take the square root of input parameter a and to return the result in the output parameter result. The xsd__double type is recognized by the gSOAP soapcpp2 compiler as the xsd:double XSD Schema data type. The use of typedef is a convenient way to associate primitive C types with primitive XML Schema data types. To generate the skeleton routines, the gSOAP soapcpp2 compiler is invoked from the command line with:
> soapcpp2 calc.h |
The compiler generates the skeleton routines for the add, sub, and sqrt service operations specified in the calc.h header file. The skeleton routines are respectively, soap_serve_ns__add, soap_serve_ns__sub, and soap_serve_ns__sqrt and saved in the file soapServer.cpp. The generated file soapC.cpp contains serializers and deserializers for the skeleton. The compiler also generates a service dispatcher: the soap_serve function handles client requests on the standard input stream and dispatches the service operation requests to the appropriate skeletons to serve the requests. The skeleton in turn calls the service operation implementation function. The function prototype of the service operation implementation function is specified in the header file that is input to the gSOAP soapcpp2 compiler. Here is an example Calculator service application that uses the generated soap_serve routine to handle client requests:
// Contents of file "calc.cpp": #include "soapH.h" #include < math.h > // for sqrt() int main() { return soap_serve(soap_new()); // use the service operation request dispatcher } // Implementation of the "add" service operation: int ns__add(struct soap *soap, double a, double b, double &result) { result = a + b; return SOAP_OK; } // Implementation of the "sub" service operation: int ns__sub(struct soap *soap, double a, double b, double &result) { result = a - b; return SOAP_OK; } // Implementation of the "sqrt" service operation: int ns__sqrt(struct soap *soap, double a, double &result) { if (a > = 0) { result = sqrt(a); return SOAP_OK; } else return soap_receiver_fault(soap, "Square root of negative number", "I can only take the square root of a non-negative number"); } // As always, a namespace mapping table is needed: struct Namespace namespaces[] = { // {"ns-prefix", "ns-name"} {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:simple-calc"}, // bind "ns" namespace prefix {NULL, NULL} }; |
Note that the service operations have an extra input parameter which is a pointer to the gSOAP runtime context. The implementation of the service operations MUST return a SOAP error code. The code SOAP_OK denotes success, while SOAP_FAULT denotes an exception with details that can be defined by the user. The exception description can be assigned to the soap->fault->faultstring string and details can be assigned to the soap->fault->detail string. This is SOAP 1.1 specific. SOAP 1.2 requires the soap->fault->SOAP_ENV__Reason and the soap->fault->SOAP_ENV__Detail strings to be assigned. Better is to use the soap_receiver_fault function that allocates a fault struct and sets the SOAP Fault string and details regardless of the SOAP 1.1 or SOAP 1.2 version used. The soap_receiver_fault function returns SOAP_FAULT, i.e. an application-specific fault. The fault exception will be passed on to the client of this service. This service application can be readily installed as a CGI application. The service description would be:
|
The soapcpp2 compile generates a WSDL file for this service, see Section 7.2.9. Unless the CGI application inspects and checks the environment variable SOAPAction which contains the SOAP action request by a client, the SOAP action is ignored by the CGI application. SOAP actions are specific to the SOAP protocol and provide a means for routing requests and for security reasons (e.g. firewall software can inspect SOAP action headers to grant or deny the SOAP request. Note that this requires the SOAP service to check the SOAP action header as well to match it with the service operation.) The header file input to the gSOAP soapcpp2 compiler does not need to be modified to generate client stubs for accessing this service. Client applications can be developed by using the same header file as for which the service application was developed. For example, the soap_call_ns__add stub routine is available from the soapClient.cpp file after invoking the gSOAP soapcpp2 compiler on the calc.h header file. As a result, client and service applications can be developed without the need to know the details of the SOAP encoding used.
7.2.2 MSVC++ Builds
- Win32 builds need winsock2 (MS Visual C++ "ws2_32.lib") To do this in Visual C++ 6.0, go to "Project", "settings", select the "Link" tab (the project file needs to be selected in the file view) and add "ws2_32.lib" to the "Object/library modules" entry.
- Use files with extension .cpp only (don't mix .c with .cpp).
- Turn pre-compiled headers off.
- When creating a new project, you can specify a custom build step to automatically invoke the gSOAP soapcpp2 compiler on a gSOAP header file. In this way you can incrementally build a new service by adding new operations and data types to the header file. To specify a custom build step, select the "Project" menu item "Settings" and select the header file in the File view pane. Select the "Custom Build" tab and enter 'soapcpp2.exe "$(inputPath)"' in the "Command" pane. Enter 'soapStub.h soapH.h soapC.cpp soapClient.cpp soapServer.cpp'. Don't forget to add the soapXYZProxy.h soapXYZObject.h files that are generated for C++ class proxies and server objects named XYZ. Click "OK". Run soapcpp2 once to generate these files (you can simply do this by selecting your header file and select "Compile"). Add the files to your project. Each time you make a change to the header file, the project sources are updated automatically.
- You may want to use the WinInet interface available in the mod_gsoap directory of the gSOAP package to simplify Internet access and deal with encryption, proxies, and authentication. API instructions are included in the source.
- For the PocketPC, run the wsdl2h WSDL parser with option -s to prevent the generation of STL code. In addition, time_t serialization is not supported, which means that you should add the following line to typemap.dat indicating a mapping of xsd__dateTime to char*: xsd__dateTime = - char* - char*.
7.2.3 How to Create a Stand-Alone Server
The deployment of a Web service as a CGI application is an easy means to provide your service on the Internet. gSOAP services can also run as stand-alone services on any port by utilizing the built-in HTTP and TCP/IP stacks. The stand-alone services can be run on port 80 thereby providing Web server capabilities restricted to SOAP RPC. To create a stand-alone service, only the main routine of the service needs to be modified as follows. Instead of just calling the soap_serve routine, the main routine is changed into:
int main() { struct soap soap; int m, s; // master and slave sockets soap_init(&soap); m = soap_bind(&soap, "machine.genivia.com", 18083, 100); if (m < 0) soap_print_fault(&soap, stderr); else { fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for (int i = 1; ; i++) { s = soap_accept(&soap); if (s < 0) { soap_print_fault(&soap, stderr); break; } fprintf(stderr, "%d: accepted connection from IP=%d.%d.%d.%d socket=%d", i, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF, s); if (soap_serve(&soap) != SOAP_OK) // process RPC request soap_print_fault(&soap, stderr); // print error fprintf(stderr, "request served\n"); soap_destroy(&soap); // clean up class instances soap_end(&soap); // clean up everything and close socket } } soap_done(&soap); // close master socket and detach context } |
The soap_serve dispatcher handles one request or multiple requests when HTTP keep-alive is enabled (with the SOAP_IO_KEEPALIVE flag see Section 19.11). The gSOAP functions that are frequently used for server-side coding are:
|
The host name in soap_bind may be NULL to indicate that the current host should be used. The soap.accept_timeout attribute of the gSOAP runtime context specifies the timeout value for a non-blocking soap_accept(&soap) call. See Section 19.19 for more details on timeout management. See Section 9.13 for more details on memory management. A client application connects to this stand-alone service with the endpoint machine.genivia.com:18083. A client may use the http:// prefix. When absent, no HTTP header is sent and no HTTP-based information will be communicated to the service.
7.2.4 How to Create a Multi-Threaded Stand-Alone Service
Multi-threading a Web Service is essential when the response times for handling requests by the service are (potentially) long or when keep-alive is enabled, see Section 19.11. In case of long response times, the latencies introduced by the unrelated requests may become prohibitive for a successful deployment of a stand-alone service. When HTTP keep-alive is enabled, a client may not close the socket on time, thereby preventing other clients from connecting. gSOAP 2.0 and higher is thread safe and supports the implementation of multi-threaded stand-alone services in which a thread is used to handle a request. The following example illustrates the use of threads to improve the quality of service by handling new requests in separate threads:
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { soap.send_timeout = 60; // 60 seconds soap.recv_timeout = 60; // 60 seconds soap.accept_timeout = 3600; // server stops after 1 hour of inactivity soap.max_keep_alive = 100; // max keep-alive sequence void *process_request(void*); struct soap *tsoap; pthread_t tid; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); for (;;) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); exit(1); } fprintf(stderr, "server timed out\n"); break; } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); tsoap = soap_copy(&soap); // make a safe copy if (!tsoap) break; pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap); } } soap_done(&soap); // detach soap struct return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); // dealloc C++ data soap_end((struct soap*)soap); // dealloc data and clean up soap_done((struct soap*)soap); // detach soap struct free(soap); return NULL; } |
Note: the code does not wait for threads to join the main thread upon program termination. The soap_serve dispatcher handles one request or multiple requests when HTTP keep-alive is set with SOAP_IO_KEEPALIVE. The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed, which is important to avoid a client from holding a thread indefinitely. The send and receive timeouts are set to avoid (intentionally) slow clients from holding a socket connection too long. The accept timeout is used to let the server terminate automatically after a period of inactivity. The following example uses a pool of servers to limit the machine's resource utilization:
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog #define MAX_THR (10) // Max. threads to serve requests int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { struct soap *soap_thr[MAX_THR]; // each thread needs a runtime context pthread_t tid[MAX_THR]; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; int i; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); for (i = 0; i < MAX_THR; i++) soap_thr[i] = NULL; for (;;) { for (i = 0; i < MAX_THR; i++) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); continue; // retry } else { fprintf(stderr, "Server timed out\n"); break; } } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); if (!soap_thr[i]) // first time around { soap_thr[i] = soap_copy(&soap); if (!soap_thr[i]) exit(1); // could not allocate } else// recycle soap context { pthread_join(tid[i], NULL); fprintf(stderr, "Thread %d completed\n", i); soap_destroy(soap_thr[i]); // deallocate C++ data of old thread soap_end(soap_thr[i]); // deallocate data of old thread } soap_thr[i]->socket = s; // new socket fd pthread_create(&tid[i], NULL, (void*(*)(void*))soap_serve, (void*)soap_thr[i]); } } for (i = 0; i < MAX_THR; i++) if (soap_thr[i]) { soap_done(soap_thr[i]); // detach context free(soap_thr[i]); // free up } } return 0; } |
The following functions can be used to setup a gSOAP runtime context (struct soap):
|
A new context is initiated for each thread to guarantee exclusive access to runtime contexts. For clean termination of the server, the master socket can be closed and callbacks removed with soap_done(struct soap *soap). The advantage of the code shown above is that the machine cannot be overloaded with requests, since the number of active services is limited. However, threads are still started and terminated. This overhead can be eliminated using a queue of requests (open sockets) as is shown in the code below.
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog #define MAX_THR (10) // Size of thread pool #define MAX_QUEUE (1000) // Max. size of request queue SOAP_SOCKET queue[MAX_QUEUE]; // The global request queue of sockets int head = 0, tail = 0; // Queue head and tail void *process_queue(void*); int enqueue(SOAP_SOCKET); SOAP_SOCKET dequeue(); pthread_mutex_t queue_cs; pthread_cond_t queue_cv; int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { struct soap *soap_thr[MAX_THR]; // each thread needs a runtime context pthread_t tid[MAX_THR]; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; int i; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); pthread_mutex_init(&queue_cs, NULL); pthread_cond_init(&queue_cv, NULL); for (i = 0; i < MAX_THR; i++) { soap_thr[i] = soap_copy(&soap); fprintf(stderr, "Starting thread %d\n", i); pthread_create(&tid[i], NULL, (void*(*)(void*))process_queue, (void*)soap_thr[i]); } for (;;) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); continue; // retry } else { fprintf(stderr, "Server timed out\n"); break; } } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); while (enqueue(s) == SOAP_EOM) sleep(1); } for (i = 0; i < MAX_THR; i++) { while (enqueue(SOAP_INVALID_SOCKET) == SOAP_EOM) sleep(1); } for (i = 0; i < MAX_THR; i++) { fprintf(stderr, "Waiting for thread %d to terminate... ", i); pthread_join(tid[i], NULL); fprintf(stderr, "terminated\n"); soap_done(soap_thr[i]); free(soap_thr[i]); } pthread_mutex_destroy(&queue_cs); pthread_cond_destroy(&queue_cv); } soap_done(&soap); return 0; } void *process_queue(void *soap) { struct soap *tsoap = (struct soap*)soap; for (;;) { tsoap->socket = dequeue(); if (!soap_valid_socket(tsoap->socket)) break; soap_serve(tsoap); soap_destroy(tsoap); soap_end(tsoap); fprintf(stderr, "served\n"); } return NULL; } int enqueue(SOAP_SOCKET sock) { int status = SOAP_OK; int next; pthread_mutex_lock(&queue_cs); next = tail + 1; if (next > = MAX_QUEUE) next = 0; if (next == head) status = SOAP_EOM; else { queue[tail] = sock; tail = next; } pthread_cond_signal(&queue_cv); pthread_mutex_unlock(&queue_cs); return status; } SOAP_SOCKET dequeue() { SOAP_SOCKET sock; pthread_mutex_lock(&queue_cs); while (head == tail) pthread_cond_wait(&queue_cv, &queue_cs); sock = queue[head++]; if (head > = MAX_QUEUE) head = 0; pthread_mutex_unlock(&queue_cs); return sock; } |
Note: the plugin/threads.h and plugin/threads.c code can be used for a portable implementation. Instead of POSIX calls, use MUTEX_LOCK, MUTEX_UNLOCK, and COND_WAIT. These are wrappers for Win API calls or POSIX calls.
7.2.5 How to Pass Application Data to Service Methods
The void *soap.user field can be used to pass application data to service methods. This field should be set before the soap_serve() call. The service method can access this field to use the application-dependent data. The following example shows how a non-static database handle is initialized and passed to the service methods:
{ ... struct soap soap; database_handle_type database_handle; soap_init(&soap); soap.user = (void*)database_handle; ... soap_serve(&soap); // call the service operation dispatcher to handle request ... } int ns__myMethod(struct soap *soap, ...) { ... fetch((database_handle_type*)soap->user); // get data ... return SOAP_OK; } |
Another way to pass application data around in a more organized way is accomplished with plugins, see Section 19.38.
7.2.6 Web Service Implementation Aspects
The same client header file specification issues apply to the specification and implementation of a SOAP Web service. Refer to
- 7.1.2 for namespace considerations.
- 7.1.5 for an explanation on how to change the encoding of the primitive types.
- 7.1.7 for a discussion on how the response element format can be controlled.
- 7.1.9 for details on how to pass multiple output parameters from a service operation.
- 7.1.11 for passing complex data types as output parameters.
- 7.1.13 for anonymizing the input and output parameter names.
7.2.7 How to Generate C++ Server Object Classes
Server object classes for C++ server applications are automatically generated by the gSOAP soapcpp2 compiler. There are two modes for generating classes. Use soapcpp2 option -i (or -j) to generate improved class definitions where the class' member functions are the service methods. The older examples (without the use of soapcpp2 option -i and -j) use a C-like approach with globally defined service methods, which is illustated here with a calculator example:
// Content of file "calc.h": //gsoap ns service name: Calculator //gsoap ns service protocol: SOAP //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://www.cs.fsu.edu/~engelen/calc.cgi //gsoap ns schema namespace: urn:calc //gsoap ns service method-action: add "" int ns__add(double a, double b, double &result); int ns__sub(double a, double b, double &result); int ns__mul(double a, double b, double &result); int ns__div(double a, double b, double &result); |
The first three directives provide the service name which is used to name the service class, the service location (endpoint), and the schema. The fourth directive defines the optional SOAPAction for the method, which is a string associated with SOAP 1.1 operations. Compilation of this header file with soapcpp2 -i creates a new file soapCalculatorObject.h with the following contents:
#include "soapH.h" class CalculatorObject : public soap { public: Calculator() { ... }; ~Calculator() { ... }; int serve() { return soap_serve(this); }; }; |
This generated server object class can be included into a server application together with the generated namespace table as shown in this example:
#include "soapCalculatorObject.h" // get server object #include "Calculator.nsmap" // get namespace bindings int main() { CalculatorObject c; return c.serve(); // calls soap_serve to serve as CGI application (using stdin/out) } // C-style global functions implement server operations (soapcpp2 w/o option -i) int ns__add(struct soap *soap, double a, double b, double &result) { result = a + b; return SOAP_OK; } ... sub(), mul(), and div() implementations ... |
You can use soapcpp2 option -n together with -p to create a local namespace table to avoid link conflict when you need to combine multiple tables and/or multiple servers, see also Sections 9.1 and 19.36, and you can use a C++ code namespace to create a namespace qualified server object class, see Section 19.35. The example above serves requests over stdin/out. Use the bind and accept calls to create a stand-alone server to service inbound requests over sockets, see also 7.2.3. A better alternative is to use the soapcpp2 option -i. The C++ proxy and server objects are derived from the soap context struct, which simplifies the proxy invocation and service operation implementations. Compilation of the above header file with the gSOAP compiler soapcpp2 option -i creates new files soapCalculatorService.h and soapCalculatorService.cpp (rather than the C-style soapServer.cpp). This generated server object class can be included into a server application together with the generated namespace table as shown in this example:
#include "soapCalculatorService.h" // get server object #include "Calculator.nsmap" // get namespace bindings int main() { soapCalculatorService c; return c.serve(); // calls soap_serve to serve as CGI application (using stdin/out) } // The 'add' service method (soapcpp2 w/ option -i) int soapCalculatorService::add(double a, double b, double &result) { result = a + b; return SOAP_OK; } ... sub(), mul(), and div() implementations ... |
Note that the service operation does not need a prefix (ns__) and there is no soap context struct passed to the service operation since the service object itself is the context (it is derived from the soap struct).
7.2.8 How to Chain C++ Server Classes to Accept Messages on the Same Port
When combining multiple services into one application, you can run wsdl2h on multiple WSDLs to generate the single all-inclusive service definitions header file. This header file is then processed with soapcpp2, for example to generate server class objects with option -i and -q to separate the service codes with C++ namespaces, see Section 19.35. This works well, but the problem is that we end up with multiple classes, each for a collection of service operations the class is supposed to implement. But what if we need to provide one endpoint port for all services and operations? In this case invoking the server object's serve method is not sufficient, since only one service can accept requests while we want multiple services to listen to the same port. The approach is to chain the service dispatchers, as shown below:
Abc::soapABCService abc; // generated with soapcpp2 -i -S -qAbc Uvw::soapUVWService uvw; // generated with soapcpp2 -i -S -qUvw Xyz::soapXYZService xyz; // generated with soapcpp2 -i -S -qXyz ... abc.bind(NULL, 8080, 100); ... abc.accept(); // when using SSL: ssl_accept(&abc); ... if(soap_begin_serve(&abc)) // available in 2.8.2 and later abc.soap_stream_fault(std::cerr); elseif (abc.dispatch() == SOAP_NO_METHOD) { soap_copy_stream(&uvw, &abc); if (uvw.dispatch() == SOAP_NO_METHOD) { soap_copy_stream(&xyz, &uvw); if (xyz.dispatch()) { soap_send_fault(&xyz); // send fault to client xyz.soap_stream_fault(std::cerr); } soap_free_stream(&xyz); // free the copy xyz.destroy(); } else { soap_send_fault(&uvw); // send fault to client uvw.soap_stream_fault(std::cerr); } soap_free_stream(&uvw); // free the copy uvw.destroy(); } else abc.soap_stream_fault(std::cerr); abc.destroy(); ... |
The dispatch method parses the SOAP/XML request and invokes the service operations, unless there is no matching operation and SOAP_NO_METHOD is returned. The soap_copy_stream ensures that the service object uses the currently open socket. The copied streams are freed with soap_free_stream. Do not enable keep-alive support, as the socket may stay open indefinitely afterwards as a consequence. Also, the dispatch method does not send a fault to the client, which has to be explicitly done with the soap_send_fault operation when an error occurs. In this way, multiple services can be chained to accept messages on the same port. This approach also works with SSL for HTTPS services. However, this approach is not recommended for certain plugins, because plugins must be registered with all service objects and some plugins require state information to be used across the service objects, which will add significantly to the complexity. When plugin complications arise, it is best to have all services share the same context. This means that soapcpp2 option -j should be used instead of option -i. Each service class has a pointer member to a soap struct context. This member pointer should point to the same soap context. With option -j and -q the code to chain the services is as follows, based on a single struct soap engine context:
struct soap *soap = soap_new(); Abc::soapABCService abc(soap); // generated with soapcpp2 -j -S -qAbc Uvw::soapUVWService uvw(soap); // generated with soapcpp2 -j -S -qUvw Xyz::soapXYZService xyz(soap); // generated with soapcpp2 -j -S -qXyz soap_bind(soap, NULL, 8080, 100); soap_accept(soap); if (soap_begin_serve(soap)) ... error else if (abc.dispatch() == SOAP_NO_METHOD) { if (uvw.dispatch() == SOAP_NO_METHOD) { if (xyz.dispatch() == SOAP_NO_METHOD) ... error } } soap_destroy(soap); soap_end(soap); soap_free(soap); // only safe when abc, uvw, xyz are also deleted |
7.2.9 How to Generate WSDL Service Descriptions
The gSOAP stub and skeleton compiler soapcpp2 generates WSDL (Web Service Description Language) service descriptions and XML Schema files when processing a header file. The tool produces one WSDL file for a set of service operations, which must be provided. The names of the function prototypes of the service operations must use the same namespace prefix and the namespace prefix is used to name the WSDL file. If multiple namespace prefixes are used to define service operations, multiple WSDL files will be created and each file describes the set of service operations belonging to a namespace prefix. In addition to the generation of the ns.wsdl file, a file with a namespace mapping table is generated by the gSOAP compiler. An example mapping table is shown below:
struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance", \"http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", \"http://www.w3.org/*/XMLSchema"}, {"ns", "http://tempuri.org"}, {NULL, NULL} }; |
This file can be incorporated in the client/service application, see Section 10.4 for details on namespace mapping tables. To deploy a Web service, copy the compiled CGI service application to the designated CGI directory of your Web server. Make sure the proper file permissions are set (chmod 755 calc.cgi for Unix/Linux). You can then publish the WSDL file on the Web by placing it in the appropriate Web server directory. The gSOAP soapcpp2 compiler also generates XML Schema files for all C/C++ complex types (e.g. structs and classes) when declared with a namespace prefix. These files are named ns.xsd, where ns is the namespace prefix used in the declaration of the complex type. The XML Schema files do not have to be published as the WSDL file already contains the appropriate XML Schema definitions. To customize the WSDL output, it is essential to use //gsoap directives to declare the service name, the endpoint port, and namespace:
//gsoap ns service name: example //gsoap ns servire port: http://www.mydomain.com/example //gsoap ns service namespace: urn:example |
These are minimal settings. More details and settings for the service operations should be declared as well. See Section 19.2 for more details.
7.2.10 Example
For example, suppose the following methods are defined in the header file:
typedef double xsd__double; int ns__add(xsd__double a, xsd__double b, xsd__double &result); int ns__sub(xsd__double a, xsd__double b, xsd__double &result); int ns__sqrt(xsd__double a, xsd__double &result); |
Then, one WSDL file will be created with the file name ns.wsdl that describes all three service operations:
<?xml version="1.0" encoding="UTF-8"?> <definitions name="Service" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://location/Service.wsdl" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:WSDL="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:tns="http://location/Service.wsdl" xmlns:ns="http://tempuri.org"> <types> <schema xmlns="http://www.w3.org/2000/10/XMLSchema" targetNamespace="http://tempuri.org" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"> <complexType name="addResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> <complexType name="subResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> <complexType name="sqrtResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> </schema> </types> <message name="addRequest"> <part name="a" type="xsd:double"/> <part name="b" type="xsd:double"/> </message> <message name="addResponse"> <part name="result" type="xsd:double"/> </message> <message name="subRequest"> <part name="a" type="xsd:double"/> <part name="b" type="xsd:double"/> </message> <message name="subResponse"> <part name="result" type="xsd:double"/> </message> <message name="sqrtRequest"> <part name="a" type="xsd:double"/> </message> <message name="sqrtResponse"> <part name="result" type="xsd:double"/> </message> <portType name="ServicePortType"> <operation name="add"> <input message="tns:addRequest"/> <output message="tns:addResponse"/> </operation> <operation name="sub"> <input message="tns:subRequest"/> <output message="tns:subResponse"/> </operation> <operation name="sqrt"> <input message="tns:sqrtRequest"/> <output message="tns:sqrtResponse"/> </operation> </portType> <binding name="ServiceBinding" type="tns:ServicePortType"> <SOAP:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="add"> <SOAP:operation soapAction="http://tempuri.org#add"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sub"> <SOAP:operation soapAction="http://tempuri.org#sub"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sqrt"> <SOAP:operation soapAction="http://tempuri.org#sqrt"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> </binding> <service name="Service"> <port name="ServicePort" binding="tns:ServiceBinding"> <SOAP:address location="http://location/Service.cgi"/> </port> </service> </definitions> |
The above uses all default settings for the service name, port, and namespace which should be set in the header file with //gsoap directives (Section 19.2).
7.2.11 How to Use Client Functionalities Within a Service
A gSOAP service implemented with CGI may make direct client calls to other services from within its service operations, without setting up a new context. A stand-alone service application must setup a new soap struct context, e.g. using soap_copy and delete it after the call. The server-side client call is best illustrated with an example. The following example is a more sophisticated example that combines the functionality of two Web services into one new SOAP Web service. The service provides a currency-converted stock quote. To serve a request, the service in turn requests the stock quote and the currency-exchange rate from two XMethods services (these services are no longer available by XMethods, but are used here as an example). In addition to being a client of two XMethods services, this service application can also be used as a client of itself to test the implementation. As a client invoked from the command-line, it will return a currency-converted stock quote by connecting to a copy of itself installed as a CGI application on the Web to retrieve the quote after which it will print the quote on the terminal. The header file input to the gSOAP soapcpp2 compiler is given below. The example is for illustrative purposes only (the XMethods services are not operational):
// Contents of file "quotex.h": int ns1__getQuote(char *symbol, float &result); // XMethods delayed stock quote service service operation int ns2__getRate(char *country1, char *country2, float &result); // XMethods currency-exchange service service operation int ns3__getQuote(char *symbol, char *country, float &result); // the new currency-converted stock quote service |
The quotex.cpp client/service application source is:
// Contents of file "quotex.cpp": #include "soapH.h" // include generated proxy and SOAP support int main(int argc, char **argv) { struct soap soap; float q; soap_init(&soap); if (argc < = 2) soap_serve(&soap); else if (soap_call_ns3__getQuote(&soap, "http://www.cs.fsu.edu/\symbol{126}engelen/quotex.cgi", "", argv[1], argv[2], q)) soap_print_fault(&soap, stderr); else printf("\nCompany %s: %f (%s)\n", argv[1], q, argv[2]); return 0; } int ns3__getQuote(struct soap *soap, char *symbol, char *country, float &result) { float q, r; int socket = soap->socket; // save socket (stand-alone service only, does not support keep-alive) if (soap_call_ns1__getQuote(soap, "http://services.xmethods.net/soap", "", symbol, &q) == 0 && soap_call_ns2__getRate(soap, "http://services.xmethods.net/soap", NULL, "us", country, &r) == 0) { result = q*r; soap->socket = socket; return SOAP_OK; } soap->socket = socket; return SOAP_FAULT; // pass soap fault messages on to the client of this app } /* Since this app is a combined client-server, it is put together with * one header file that describes all service operations. However, as a consequence we * have to implement the methods that are not ours. Since these implementations are * never called (this code is client-side), we can make them dummies as below. */ int ns1__getQuote(struct soap *soap, char *symbol, float &result) { return SOAP_NO_METHOD; } // dummy: will never be called int ns2__getRate(struct soap *soap, char *country1, char *country2, float &result) { return SOAP_NO_METHOD; } // dummy: will never be called struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema"}, {"ns1", "urn:xmethods-delayed-quotes"}, {"ns2", "urn:xmethods-CurrencyExchange"}, {"ns3", "urn:quotex"}, {NULL, NULL} }; |
To compile:
> soapcpp2 quotex.h > c++ -o quotex.cgi quotex.cpp soapC.cpp soapClient.cpp soapServer.cpp stdsoap2.cpp -lsocket -lxnet -lnsl |
Note: under Linux and Mac OS X you can often omit the -l libraries. The quotex.cgi executable is installed as a CGI application on the Web by copying it in the designated directory specific to your Web server. After this, the executable can also serve to test the service. For example
> quotex.cgi IBM uk |
returns the quote of IBM in uk pounds by communicating the request and response quote from the CGI application. See http://xmethods.com/detail.html?id=5 for details on the currency abbreviations. When combining clients and service functionalities, it is required to use one header file input to the compiler. As a consequence, however, stubs and skeletons are available for all service operations, while the client part will only use the stubs and the service part will use the skeletons. Thus, dummy implementations of the unused service operations need to be given which are never called. Three WSDL files are created by gSOAP: ns1.wsdl, ns2.wsdl, and ns3.wsdl. Only the ns3.wsdl file is required to be published as it contains the description of the combined service, while the others are generated as a side-effect (and in case you want to develop these separate services).
7.3 Asynchronous One-Way Message Passing
SOAP RPC client-server interaction is synchronous: the client blocks until the server responds to the request. gSOAP also supports asynchronous one-way message passing and the interoperable synchronous one-way message passing over HTTP. The two styles are similar, but only the latter is interoperable and is compliant to Basic Profile 1.0. The interoperable synchronous one-way message passing style over HTTP is discussed in Section 7.4 below. SOAP messaging routines are declared as function prototypes, just like service operations for SOAP RPC. However, the output parameter is a void type to indicate the absence of a return value. For example, the following header file specifies an event message for SOAP messaging:
int ns__event(int eventNo, void); |
The gSOAP soapcpp2 tool generates the following functions in soapClient.cpp:
int soap_send_ns__event(struct soap *soap, const char URL, const char action, int event); int soap_recv_ns__event(struct soap *soap, struct ns__event *dummy); |
The soap_send_ns__event function transmits the message to the destination URL by opening a socket and sending the SOAP encoded message. The socket will remain open after the send and has to be closed with soap_closesock(). The open socket connection can also be used to obtain a service response, e.g. with a soap_recv function call. The soap_recv_ns__event function waits for a SOAP message on the currently open socket (soap.socket) and fills the struct ns__event with the ns__event parameters (e.g. int eventNo). The struct ns__event is automatically created by gSOAP and is a mirror image of the ns__event parameters:
struct ns__event { int eventNo; } |
The gSOAP generated soapServer.cpp code includes a skeleton routine to accept the message. (The skeleton routine does not respond with a SOAP response message.)
int soap_serve_ns__event(struct soap *soap); |
The skeleton routine calls the user-implemented ns__event(struct soap *soap, int eventNo) routine (note the absence of the void parameter!). As usual, the skeleton will be automatically called by the service operation request dispatcher that handles both the service operation requests (RPCs) and messages:
int main() { soap_serve(soap_new()); } int ns__event(struct soap *soap, int eventNo) { ... // handle event return SOAP_OK; } |
7.4 Implementing Synchronous One-Way Message Passing over HTTP
One-way SOAP message passing over HTTP as defined by the SOAP specification and Basic Profile 1.0 is synchrounous, meaning that the server must respond with an HTTP OK header (or HTTP 202 Accepted) and an empty body. To implement synchrounous one-way messaging, the same setup for asynchrounous one-way messaing discussed in Section 7.3 is used, but with one simple addition at the client and server side for HTTP transfer. At the server side, we have to return an empty HTTP OK response. Normally with one-way messaging the gSOAP engine closes the socket when the service operation is finished, which is not desirable for synchronous one-way message exchanges over HTTP: an HTTP response should be send. This is accomplished as follows. For each one-way operation implemented in C/C++, we replace the return SOAP_OK with:
int ns__event(struct soap *soap, int eventNo) { ... // handle event return soap_send_empty_response(soap, SOAP_OK); // SOAP_OK: return HTTP 202 ACCEPTED } |
At the client side, the empty response header must be parsed as follows:
if (soap_send_ns__event(soap, eventNo) != SOAP_OK || soap_recv_empty_response(soap) != SOAP_OK) soap_print_fault(soap, stderr); ... |
The synchronous (and asynchronous) one-way messaging supports HTTP keep-alive and chunking.
7.5 How to Use the SOAP Serializers and Deserializers to Save and Load Application Data using XML Data Bindings
The gSOAP XML databindings for C and C++ allow a seamless integration of XML in C and C++ applications. Data can be serialized in XML and vice versa. WSDL and XML schema files can be converted to C or C++ definitions. C and C++ definitions can be translated to WSDL and schemas to support legacy ANSI C applications for example.
7.5.1 Mapping XML Schema to C/C++ with wsdl2h
Command:
> wsdl2h [options] XSD and WSDL files ... |
The WSDL 1.1 and 2.0 standards are supported. If you have trouble with WSDL 2.0 please contact the author. The entire XML schema 1.1 standard is supported, except XPath expressions and assertions. This covers all of the following schema components with their optional [ attributes ] shown:
<xs:any [minOccurs, maxOccurs] > <xs:anyAttribute> <xs:all> <xs:choice [minOccurs, maxOccurs] > <xs:sequence [minOccurs, maxOccurs] > <xs:group [name, ref] > <xs:attributeGroup [name, ref] > <xs:attribute [name, ref, type, use, default, fixed, form, wsdl:arrayType] > <xs:element [name, ref, type, default, fixed, form, nillable, abstract, substitutionGroup, minOccurs, maxOccurs] > <xs:simpleType [name] > <xs:complexType [name, abstract, mixed] > |
The supported facets are:
<xs:enumeration> <xs:simpleContent> <xs:complexContent> <xs:list> <xs:extension> <xs:restriction> <xs:length> <xs:minLength> <xs:maxLength> <xs:minInclusive> <xs:maxInclusive> <xs:minExclusive> <xs:maxExclusive> <xs:precision> maps to float/double, content not validated yet <xs:scale> maps to float/double, content not validated yet <xs:totalDigits> content not automatically validated yet <xs:pattern> content not automatically validated yet <xs:union> maps to string, content not validated yet |
Other:
<xs:import> <xs:include> <xs:redefine> <xs:annotation> |
All primitive XSD types are supported (with the default mapping shown):
xsd:string maps to string (char*,wchar_t*,std::string,std::wstring) xsd:boolean maps to bool (C++) or enum xsd__boolean (C) xsd:float maps to float xsd:double maps to double xsd:decimal maps to string, or use "#import "custom/decimal.h" xsd:precisionDecimal maps to string xsd:duration maps to string, or use "#import "custom/duration.h" xsd:dateTime maps to time_t, or use "#import "custom/struct_tm.h" xsd:time maps to string xsd:date maps to string xsd:gYearMonth maps to string xsd:gYear maps to string xsd:gMonth maps to string xsd:hexBinary maps to struct xsd__hexBinary xsd:base64Bianry maps to struct xsd__base64Binary xsd:anyURI maps to string xsd:QName maps to _QName (string normalization rules apply) xsd:NOTATION maps to string |
Note: string targets are defined in the typemap.dat file used by wsdl2h to map XSD types. This allows the use of char*, wsha_t*, std::string, and std::wstring string types for all XSD types mapped to strings. All non-primitive XSD types are supported (with the default mapping shown):
xsd:normalizedString maps to string xsd:token maps to string xsd:language maps to string xsd:IDREFS maps to string xsd:ENTITIES maps to string xsd:NMTOKEN maps to string xsd:NMTOKENS maps to string xsd:Name maps to string xsd:NCName maps to string xsd:ID maps to string xsd:IDREF maps to string xsd:ENTITY maps to string xsd:integer maps to string xsd:nonPositiveInteger maps to string xsd:negativeInteger maps to string xsd:long maps to LONG64 xsd:int maps to int xsd:short maps to short xsd:byte maps to byte xsd:nonNegativeInteger maps to string xsd:unsignedLong maps to ULONG64 xsd:unsignedInt maps to unsigned int xsd:unsignedShort maps to unsigned short xsd:unsignedByte maps to unsigned byte xsd:positiveInteger maps to string xsd:yearMonthDuration maps to string xsd:dayTimeDuration maps to string xsd:dateTimeStamp maps to string |
There are several initialization flags to control XML serialization at runtime:
- XML content validation is enforced with SOAP_XML_STRICT.
- XML namespaces are supported, unless disabled with SOAP_XML_IGNORENS.
- XML exclusive canonicalization is enabled with SOAP_XML_CANONICAL.
- XML default xmlns="..." namespace bindings are used with SOAP_XML_DEFAULTNS.
- XML is indented for enhanced readability with SOAP_XML_INDENT.
- XML xsi:nil for NULL elements is serialized with SOAP_XML_NIL.
To obtain C and/or C++ type definitions for XML schema components, run wsdl2h on the schemas to generate a header file. This header file defines the C/C++ type representations of the XML schema components. The header file is then processed by the soapcpp2 tool to generate the serializers for these types. See Section 1.4 for an overview to use wsdl2h and soapcpp2 to map schemas to C/C++ types to obtain XML data bindings.
7.5.2 Mapping C/C++ to XML Schema with soapcpp2
To generate serialization code, execute:
> soapcpp2 [options] header_file.h |
The following C/C++ types are supported in the header file:
bool enum, enum* ('enum*' indicates serialized as a bitmask) (unsigned) char, short, int, long, long long (also LONG64), size_t float, double, longdouble(#import "custom/long_double.h") std::string, std::wstring, char[], char*, wchar_t* _XML (a char* type to hold literal XML string content) _QName (a char* type with normalized QName content of the form prefix:name) struct, class (with single inheritance) std::vector, std::list, std::deque, std::set (#import "import/stl.h") union (requires preceding discriminant member field) typedef time_t template < > class(requires begin(), end(), size(), and insert() methods) void* (requires a preceding __type field to indicate the object pointed to) struct xsd__hexBinary (special pre-defined type to hold binary content) struct xsd__base64Binary (special pre-defined type to hold binary content) struct tm (#import "custom/struct_tm.h") struct timeval (#import "custom/struct_timeval.h") pointers to any of the above (any pointer-linked structures are serializable, including cyclic graphs) fixed-size arrays of all of the above |
Additional features and potential limitations:
- A header file should not include any code statements, only data type declarations.
- Nested classes and nested types are unnested.
- Use #import "file.h" instead of #include to import other header files. The #include and #define directives are accepted, but deferred to the generated code.
- C++ namespaces are supported (must cover entire header file content)
- Optional DOM support can be used to store mixed content or literal XML content. Otherwise, mixed content may be lost. Use soapcpp2 option -d for DOM support.
- Types are denoted transient using the 'extern' qualifier, which prevents serialization as desired:
extern class name; // class 'name' is not serialized
struct name { extern char *name; int num; }; // 'name' is not serialized - Only public members of a class can be serialized:
class name { private: char *secret; }; // 'secret' is not serialized - Types are denoted "volatile", which means that they are declared elsewhere in the source code and should not be redeclared in the generated code nor augmented by the soapcpp2 tool:
volatile class name { ... }; // defined here just to generate the serializers - struct/class members are serialized as attributes when qualified with '@':
struct record { @char *name; int num; }; // attribute name, element num - Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is possible by enabling the SOAP_C_UTFSTRING flag. When enabled, all std::string and char* strings MUST contain UTF8.
The soapcpp2 tool generates serializers and deserializers for all wsdl2h-generated or user-defined data structures that are specified in the header file input to the compiler. The serializers and deserializers can be found in the generated soapC.cpp file. These serializers and deserializers can be used separately by an application without the need to build a full client or service application. This is useful for applications that need to save or export their data in XML or need to import or load data stored in XML format.
7.5.3 Serializing C/C++ Data to XML
We assume that the wsdl2h tool was used to map XML schema types to C/C++ data types. The soapcpp2 tool then generates the (de)serializers for the C/C++ types. You can also use soapcpp2 directly on a header file that declares annotated C/C++ data types to serialize. The following attributes can be set to control the destination and source for serialization and deserialization:
|
The following initializing and finalizing functions can be used:
|
These operations do not open or close the connections. The application should open and close connections or files and set the soap.socket, soap.os or soap.sendfd, soap.is or soap.recvfd streams or descriptors. When soap.socket < 0 and none of the streams and descriptors are set, then the standard input and output will be used. 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 |
See also Section 9.12 to control the I/O buffering and content encoding such as compression and DIME encoding. We assume that the wsdl2h tool was used to map XML schema types to C/C++ data types. The soapcpp2 tool then generates the (de)serializers for the C/C++ types. To serialize data to an XML stream, two functions should be called to prepare for serialization of the data and to send the data, respectively. The first function, soap_serialize, analyzes pointers and determines if multi-references are required to encode the data and if cycles are present the object graph. The second function, soap_put, produces the XML output on a stream. The soap_serialize and soap_put (and both combined by soap_write) functions are statically generated specific to a data type. For example, soap_serialize_float(&soap, &d) is called to serialize an float value and soap_put_float(&soap, &d, "number", NULL) is called to output the floating point value in SOAP tagged with the name <number>. The soap_write_float(&soap, &d) conveniently combines the initialization of output, writing the data, and finalizing the output. To initialize data, the soap_default function of a data type can be used. For example, soap_default_float(&soap, &d) initializes the float to 0.0. The soap_default functions are useful to initialize complex data types such as arrays, structs, and class instances. Note that the soap_default functions do not need the gSOAP runtime context as a first parameter. The following table lists the type naming conventions used by gSOAP:
|
Consider for example the following C code with a declaration of p as a pointer to a struct ns__Person:
struct ns__Person { char *name; } *p; |
To serialize p, its address is passed to the function soap_serialize_PointerTons__Person generated for this type by the gSOAP soapcpp2 compiler:
soap_serialize_PointerTons__Person(&soap, &p); |
The address of p is passed, so the serializer can determine whether p was already serialized and to discover co-referenced objects and cycles in graph data structures that require SOAP encoding with id-ref serialization. To generate the output, the address of p is passed to the function soap_put_PointerTons__Person together with the name of an XML element and an optional type string (to omit a type, use NULL):
soap_begin_send(&soap); soap_put_PointerTons__Person(&soap, &p, "ns:element-name", "ns:type-name"); soap_end_send(&soap); |
or the shorthand for the above (without the xsi type):
soap_write_PointerTons__Person(&soap, &p); |
This produces:
<ns:element-name xmlns:SOAP-ENV="..." xmlns:SOAP-ENC="..." xmlns:ns="..." ... xsi:type="ns:type-name"> <name xsi:type="xsd:string">...</name> </ns:element-name> |
The serializer is initialized with the soap_begin_send(soap) function and closed with soap_end_send(soap). All temporary data structures and data structures deserialized on the heap are destroyed with the soap_destroy and soap_end functions (in this order). The soap_done function should be used to reset the context, i.e. the last use of the context. To detach and deallocate the context, use soap_free. To remove the temporary data only and keep the deserialized data on the heap, use soap_free_temp. Temporary data structures are only created if the encoded data uses pointers. Each pointer in the encoded data has an internal hash table entry to determine all multi-reference parts and cyclic parts of the complete data structure. You can assign an output stream to soap.os or a file descriptor to soap.sendfd. For example
soap.sendfd = open(file, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); soap_serialize_PointerTons__Person(&soap, &p); soap_begin_send(&soap); soap_put_PointerTons__Person(&soap, &p, "ns:element-name", "ns:type-name"); soap_end_send(&soap); |
The above can be abbreviated to
soap.sendfd = open(file, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); soap_write_PointerTons__Person(&soap, &p); |
The soap_serialize function is optional. It MUST be used when the object graph contains cycles. It MUST be called to preserve the logical coherence of pointer-based data structures, where pointers may refer to co-referenced objects. By calling soap_serialize, data structures shared through pointers are serialized only once and referenced in XML using id-refs attributes. The actual id-refs used depend on the SOAP encoding. To turn off SOAP encoding, remove or avoid using the SOAP-ENV and SOAP-ENC namespace bindings in the namespace table. In addition, the SOAP_XML_TREE and SOAP_XML_GRAPH flags can be used to control the output by restricting serialization to XML trees or by enabling multi-ref graph serialization with id-ref attribuation. To save the data as an XML tree (with one root) without any id-ref attributes, use the SOAP_XML_TREE flag. The data structure MUST NOT contain pointer-based cycles. To preserve the exact structure of the data object graph and create XML with one root, use the SOAP_XML_GRAPH output-mode flag (see Section 9.12). Use this flag and the soap_serialize function to prepare the serialization of data with in-line id-ref attributes. Using the SOAP_XML_GRAPH flag assures the preservation of the logical structure of the data For example, to encode the contents of two variables var1 and var2 that may share data through pointer structures, the serializers are called before the output routines:
T1 var1; T2 var2; struct soap soap; ... soap_init(&soap); // initialize [soap_omode(&soap, flags);] // set output-mode flags (e.g. SOAP_ENC_XML|SOAP_ENC_ZLIB) soap_begin(&soap); // start new (de)serialization phase soap_set_omode(&soap, SOAP_XML_GRAPH); soap_serialize_Type1(&soap, &var1); soap_serialize_Type2(&soap, &var2); ... [soap.socket = a_socket_file_descriptor;] // when using sockets [soap.os = an_output_stream;] // C++ [soap.sendfd = an_output_file_descriptor;] // C soap_begin_send(&soap); soap_put_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); soap_put_Type2(&soap, &var2, "[namespace-prefix:]element-name2", "[namespace-prefix:]type-name2"); ... soap_end_send(&soap); // flush soap_destroy(&soap); // remove deserialized C++ objects soap_end(&soap); // remove deserialized data structures soap_done(&soap); // finalize last use of this context ... |
where Type1 is the type name of T1 and Type2 is the type name of T2 (see table above). The strings [namespace-prefix:]type-name1 and [namespace-prefix:]type-name2 describe the schema types of the elements. Use NULL to omit this type information. For serializing class instances, method invocations MUST be used instead of function calls, for example obj.soap_serialize(&soap) and obj.soap_put(&soap, "elt", "type"). This ensures that the proper serializers are used for serializing instances of derived classes. You can serialize a class instance to a stream as follows:
struct soap soap; myClass obj; soap_init(&soap); // initialize soap_begin(&soap); // start new (de)serialization phase soap_set_omode(&soap, SOAP_XML_GRAPH); obj.serialize(&soap); soap.os = &cout; // send to cout soap_begin_send(&soap); obj.put(&soap, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); soap_end_send(&soap); // flush ... soap_destroy(&soap); // remove deserialized C++ objects soap_end(&soap); // remove deserialized data soap_done(&soap); // finalize last use of this context |
When you declare a soap struct pointer as a data member in a class, you can overload the << operator to serialize the class to streams:
ostream &operator<<(ostream &o, const myClass &e) { if (!e.soap) ... error: need a soap struct to serialize (could use global struct) ... else { ostream *os = e.soap->os; e.soap->os = &o; soap_set_omode(e.soap, SOAP_XML_GRAPH); e.serialize(e.soap); soap_begin_send(e.soap); e.put(e.soap, "myClass", NULL); soap_end_send(e.soap); e.soap->os = os; soap_clr_omode(e.soap, SOAP_XML_GRAPH); } return o; } |
Of course, when you construct an instance you must set its soap struct to a valid context. Deserialized class instances with a soap struct data member will have their soap structs set automatically, see Section 9.13.2. In principle, XML output for a data structure can be produced with soap_put without calling the soap_serialize function first. In this case, the result is similar to SOAP_XML_TREE which means that no id-refs are output. Cycles in the data structure will crash the serialization algorithm, even when the SOAP_XML_GRAPH is set. Consider the following struct:
// Contents of file "tricky.h": struct Tricky { int *p; int n; int *q; }; |
The following fragment initializes the pointer fields p and q to the value of field n:
struct soap soap; struct Tricky X; X.n = 1; X.p = &X.n; X.q = &X.n; soap_init(&soap); soap_begin(&soap); soap_serialize_Tricky(&soap, &X); soap_put_Tricky(&soap, &X, "Tricky", NULL); soap_end(&soap); // Clean up temporary data used by the serializer |
What is special about this data structure is that n is 'fixed' in the Tricky structure, and p and q both point to n. The gSOAP serializers strategically place the id-ref attributes such that n will be identified as the primary data source, while p and q are serialized with ref/href attributes. The resulting output is:
<Tricky xsi:type="Tricky"> <p href="#2"/> <n xsi:type="int">1</n> <q href="#2"/> <r xsi:type="int">2</r> </Tricky> <id id="2" xsi:type="int">1</id> |
which uses an independent element at the end to represent the multi-referenced integer, assuming the SOAP-ENV and SOAP-ENC namespaces indicate SOAP 1.1 encoding. With the SOAP_XML_GRAPH flag the output is:
<Tricky xsi:type="Tricky"> <p href="#2"/> <n id="2" xsi:type="int">1</n> <q href="#2"/> </Tricky> |
In this case, the XML is self-contained and multi-referenced data is accurately serialized. The gSOAP generated deserializer for this data type will be able to accurately reconstruct the data from the XML (on the heap).
7.5.4 Deserializing C/C++ Data from XML
We assume that the wsdl2h tool was used to map XML schema types to C/C++ data types. The soapcpp2 tool then generates the (de)serializers for the C/C++ types. You can also use soapcpp2 directly on a header file that declares annotated C/C++ data types to serialize. To deserialize a data type from XML, the soap_get (or the simpler soap_read) function for the data type to be deserialized is used. The outline of a program that deserializes two variables var1 and var2 is for example:
T1 var1; T2 var2; struct soap soap; ... soap_init(&soap); // initialize at least once [soap_imode(&soap, flags);] // set input-mode flags soap_begin(&soap); // begin new decoding phase [soap.is = an_input_stream;] // C++ [soap.recvfd = an_input_file_desriptpr;] // C soap_begin_recv(&soap); // if HTTP/MIME/DIME/GZIP headers are present, parse them if (!soap_get_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1")) ... error ... if (!soap_get_Type2(&soap, &var2, "[namespace-prefix:]element-name2", "[namespace-prefix:]type-name1")) ... error ... ... soap_end_recv(&soap); // check consistency of id/hrefs soap_destroy(&soap); // remove deserialized C++ objects soap_end(&soap); // remove deserialized data soap_done(&soap); // finalize last use of the context |
The strings [namespace-prefix:]type-name1 and [namespace-prefix:]type-name2 are the schema types of the elements and should match the xsi:type attribute of the receiving message. To omit the match, use NULL as the type. For class instances, method invocation can be used instead of a function call if the object is already instantiated, i.e. obj.soap_get(&soap, "...", "..."). The soap_begin call resets the deserializers. The soap_destroy and soap_end calls remove the temporary data structures and the decoded data that was placed on the heap. To remove temporary data while retaining the deserialized data on the heap, the function soap_free_temp should be called instead of soap_destroy and soap_end. One call to the soap_get_Type function of a type Type scans the entire input to process its XML content and to capture SOAP 1.1 independent elements (which contain multi-referenced objects). As a result, soap.error will set to SOAP_EOF. Also storing multiple objects into one file will fail to decode them properly with multiple soap_get calls. A well-formed XML document should only have one root anyway, so don't save multiple objects into one file. If you must save multiple objects, create a linked list or an array of objects and save the linked list or array. You could use the soap_in_Type function instead of the soap_get_Type function. The soap_in_Type function parses one XML element at a time. You can deserialize class instances from a stream as follows:
myClass obj; struct soap soap; soap_init(&soap); // initialize soap.is = &cin; // read from cin soap_begin_recv(&soap); // if HTTP header is present, parse it if (soap_get_myClass(&soap, &obj, "myClass", NULL) == NULL) ... error ... soap_end_recv(&soap); // check consistency of id/hrefs ... soap_destroy(&soap); // remove deserialized C++ objects soap_end(&soap); // remove deserialized data soap_done(&soap); // finalize last use of the context |
This can be abbreviated to:
myClass obj; struct soap soap; soap_init(&soap); // initialize soap.is = &cin; // read from cin if (soap_read_myClass(&soap, &obj, NULL) != SOAP_OK) ... error ... ... soap_destroy(&soap); // remove deserialized C++ objects soap_end(&soap); // remove deserialized data soap_done(&soap); // finalize last use of the context |
When declaring a soap struct pointer as a data member in a class, you can overload the >> operator to parse and deserialize a class instance from a stream:
istream &operator>>(istream &i, myClass &e) { if (!e.soap) ... error: need soap struct to deserialize (could use global struct)... istream *is = e.soap->is; e.soap->is = &i; if (soap_read_myClass(e.soap, &e) != SOAP_OK) ... error ... e.soap->is = is; return i; } |
7.5.5 Example
As an example, consider the following data type declarations:
// Contents of file "person.h": typedef char *xsd__string; typedef char *xsd__Name; typedef unsigned int xsd__unsignedInt; enum ns__Gender {male, female}; class ns__Address { public: xsd__string street; xsd__unsignedInt number; xsd__string city; }; class ns__Person { public: xsd__Name name; enum ns__Gender gender; ns__Address address; ns__Person *mother; ns__Person *father; }; |
The following program uses these data types to write to standard output a data structure that contains the data of a person named "John" living at Downing st. 10 in Londen. He has a mother "Mary" and a father "Stuart". After initialization, the class instance for "John" is serialized and encoded in XML to the standard output stream using gzip compression (requires the Zlib library, compile sources with -DWITH_GZIP):
// Contents of file "person.cpp": #include "soapH.h" int main() { struct soap soap; ns__Person mother, father, john; mother.name = "Mary"; mother.gender = female; mother.address.street = "Downing st."; mother.address.number = 10; mother.address.city = "London"; mother.mother = NULL; mother.father = NULL; father.name = "Stuart"; father.gender = male; father.address.street = "Main st."; father.address.number = 5; father.address.city = "London"; father.mother = NULL; father.father = NULL; john.name = "John"; john.gender = male; john.address = mother.address; john.mother = &mother; john.father = &father; soap_init(&soap); soap_omode(&soap, SOAP_ENC_ZLIB | SOAP_XML_GRAPH); // see 9.12 soap_begin(&soap); soap_begin_send(&soap); john.soap_serialize(&soap); john.soap_put(&soap, "johnnie", NULL); soap_end_send(&soap); soap_destroy(&soap); soap_end(&soap); soap_done(&soap); } struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC","http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the "Person" data type {NULL, NULL} }; |
The header file is processed and the application compiled on Linux/Unix with:
> soapcpp2 person.h > c++ -DWITH_GZIP -o person person.cpp soapC.cpp stdsoap2.cpp -lsocket -lxnet -lnsl -lz |
(Depending on your system configuration, the libraries libsocket.a, libxnet.a, libnsl.a are required. Compiling on Linux typically does not require the inclusion of those libraries.) See 19.27 for details on compression with gSOAP. Running the person application results in the compressed XML output:
<johnnie xsi:type="ns:Person" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:person" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <name xsi:type="xsd:Name">John</name> <gender xsi:type="ns:Gender">male</gender> <address xsi:type="ns:Address"> <street id="3" xsi:type="xsd:string">Dowling st.</street> <number xsi:type="unsignedInt">10</number> <city id="4" xsi:type="xsd:string">London</city> </address> <mother xsi:type="ns:Person"> <name xsi:type="xsd:Name">Mary</name> <gender xsi:type="ns:Gender">female</gender> <address xsi:type="ns:Address"> <street href="#3"/> <number xsi:type="unsignedInt">5</number> <city href="#4"/> </address> </mother> <father xsi:type="ns:Person"> <name xsi:type="xsd:Name">Stuart</name> <gender xsi:type="ns:Gender">male</gender> <address xsi:type="ns:Address"> <street xsi:type="xsd:string">Main st.</street> <number xsi:type="unsignedInt">13</number> <city href="#4"/> </address> </father> </johnnie> |
The following program fragment decodes this content from standard input and reconstructs the original data structure on the heap:
#include "soapH.h" int main() { struct soap soap; ns__Person *mother, *father, *john = NULL; soap_init(&soap); soap_imode(&soap, SOAP_ENC_ZLIB); // optional: gzip is detected automatically soap_begin(&soap); if ((john = soap_get_ns__Person(&soap, NULL, NULL, NULL)) == NULL) ... error ... mother = john->mother; father = john->father; ... soap_end_recv(&soap); soap_free_temp(&soap); // Clean up temporary data but keep deserialized data } struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC","http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the "Person" data type {NULL, NULL} }; |
It is REQUIRED to either pass NULL to the soap_get routine, or a valid pointer to a data structure that can hold the decoded content. If the data john was already allocated then it does not need to be allocated again as the following demonstrates. The following program fragment decodes the SOAP content in a struct ns__Person allocated on the stack:
#include "soapH.h" int main() { struct soap soap; ns__Person *mother, *father, john; soap_init(&soap); soap_default_ns__Person(&soap, &john); soap_imode(&soap, SOAP_ENC_ZLIB); // optional soap_begin(&soap); soap_begin_recv(&soap); if (soap_get_ns__Person(&soap, &john, "johnnie", NULL) == NULL) ... error ... ... } struct Namespace namespaces[] = ... |
Note the use of soap_default_ns__Person. This routine is generated by the gSOAP soapcpp2 tool and assigns default values to the fields of john.
7.5.6 Serializing and Deserializing Class Instances to Streams
C++ applications can define appropriate stream operations on objects for (de)serialization of objects on streams. This is best illustrated with an example. Section 7.5.3 gives details on serializing types in general. Consider the class
class ns__person { public: char *name; struct soap *soap; // we need this, see below ns__person(); ~ns__person(); }; |
The struct soap member is used to bind the instances to a gSOAP context for (de)serialization. We use the gSOAP soapcpp2 compiler from the command prompt to generate the class (de)serializers (assuming that person.h contains the class declaration):
> soapcpp2 person.h |
gSOAP generates the (de)serializers and an instantiation function for the class soap_new_ns__person(struct soap *soap, int num) to instantiate one or more objects and associate them with a gSOAP context for deallocation with soap_destroy(soap). To instantiate a single object, omit the num parameter or set to -1. To instantiate an array of objects, set num ≥ 0.
#include "soapH.h" #include "ns.nsmap" ... struct soap *soap = soap_new(); ns__person *p = soap_new_ns__person(soap); ... cout << p; // serialize p in XML ... in >> p; // parse XML and deserialize p ... soap_destroy(soap); // deletes p too soap_end(soap); soap_done(soap); |
The stream operations are implemented as follows
ostream &operator<<(ostream &o, const ns__person &p) { if (!p.soap) return o; // need a gSOAP context to serialize p.soap->os = &o; soap_omode(p.soap, SOAP_XML_TREE); // XML tree or graph p.soap_serialize(p.soap); soap_begin_send(p.soap); if (p.soap_put(p.soap, "person", NULL) | | soap_end_send(p.soap)) ; // handle I/O error return o; } istream &operator>>(istream &i, ns__person &p) { if (!p.soap) return o; // need a gSOAP context to parse XML and deserialize p.soap->is = &i; if (soap_begin_recv(p.soap) | | p.soap_in(p.soap, NULL, NULL) | | soap_end_recv(p.soap)) ; // handle I/O error return i; } |
7.5.7 How to Specify Default Values for Omitted Data
The gSOAP soapcpp2 compiler generates soap_default functions for all data types. The default values of the primitive types can be easily changed by defining any of the following macros in the stdsoap2.h file:
#define SOAP_DEFAULT_bool #define SOAP_DEFAULT_byte #define SOAP_DEFAULT_double #define SOAP_DEFAULT_float #define SOAP_DEFAULT_int #define SOAP_DEFAULT_long #define SOAP_DEFAULT_LONG64 #define SOAP_DEFAULT_short #define SOAP_DEFAULT_string #define SOAP_DEFAULT_time #define SOAP_DEFAULT_unsignedByte #define SOAP_DEFAULT_unsignedInt #define SOAP_DEFAULT_unsignedLong #define SOAP_DEFAULT_unsignedLONG64 #define SOAP_DEFAULT_unsignedShort #define SOAP_DEFAULT_wstring |
Instead of adding these to stdsoap2.h, you can also compile with option -DWITH_SOAPDEFS_H and include your definitions in file userdefs.h. The absence of a data value in a receiving SOAP message will result in the assignment of a default value to a primitive type upon deserialization. Default values can also be assigned to individual struct and class fields of primitive type. For example,
struct MyRecord { char *name = "Unknown"; int value = 9999; enum Status { active, passive } status = passive; } |
Default values are assigned to the fields on receiving a SOAP/XML message in which the data values are absent. Because method requests and responses are essentially structs, default values can also be assigned to method parameters. The default parameter values do not control the parameterization of C/C++ function calls, i.e. all actual parameters must be present when calling a function. The default parameter values are used in case an inbound request or response message lacks the XML elements with parameter values. For example, a Web service can use default values to fill-in absent parameters in a SOAP/XML request:
int ns__login(char *uid = "anonymous", char *pwd = "guest", bool granted); |
When the request message lacks uid and pwd parameters, e.g.:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="http://tempuri.org"> <SOAP-ENV:Body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:login> </ns:login> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
then the service uses the default values. In addition, the default values will show up in the SOAP/XML request and response message examples generated by the gSOAP compiler.