The gSOAP toolkit provides a unique SOAP-to-C/C++ language binding for the development of SOAP Web Services and clients. Other SOAP C++ implementations adopt a SOAP-centric view and offer SOAP APIs for C++ that require the use of class libraries for SOAP-like data structures. This often forces a user to adapt the application logic to these libraries. In contrast, gSOAP provides a C/C++ transparent SOAP API through the use of compiler technology that hides irrelevant SOAP-specific details from the user. The gSOAP stub and skeleton compiler automatically maps native and user-defined C and C++ data types to semantically equivalent SOAP data types and vice-versa. As a result, full SOAP interoperability is achieved with a simple API relieving the user from the burden of SOAP details and enables him or her to concentrate on the application-essential logic. The compiler enables the integratation of (legacy) C/C++ and Fortran codes (through a Fortran-to-C interface), embedded systems, and real-time software in SOAP applications that share computational resources and information with other SOAP applications, possibly across different platforms, language environments, and disparate organizations located behind firewalls.
gSOAP minimizes application adaptation for building SOAP clients and Web Services. The gSOAP compiler generates SOAP marshalling routines that (de)serialize application-specific C/C++ data structures. gSOAP includes a WSDL generator to generate Web service descriptions for your Web services. The gSOAP WSDL importer "closes the circle" in that it enables client development without the need for users to analyze Web service details to implement a client.
Some of the highlights of gSOAP are:
The typographical conventions used by this document are:
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC-2119.
gSOAP versions 2.0 and higher are redesigned and thread-safe.
All files in the gSOAP 2.X distribution are renamed to avoid confusion with gSOAP version 1.X files:
|
The gSOAP runtime environment is stored in a struct soap type. A struct was chosen to support application development in
C without the need for a separate gSOAP implementation. An object-oriented approach with a class for the gSOAP runtime environment would have prohibited the implementation of pure C applications.
Before a client can invoke remote methods or before a service can accept requests, a runtime environment need to be allocated and
initialized.
Two new functions are added to gSOAP 2.X:
|
int main() { struct soap soap; ... soap_init(&soap); // initialize runtime environment ... soap_call_ns__method1(&soap, ...); // make a remote call ... soap_call_ns__method2(&soap, ...); // make another remote call ... soap_end(&soap); // clean up ... } |
int main() { struct soap *soap; ... soap = soap_new(); // allocate and initialize runtime environment if (!soap) // couldn't allocate: stop ... soap_call_ns__method1(soap, ...); // make a remote call ... soap_call_ns__method2(soap, ...); // make another remote call ... soap_end(soap); // clean up ... free(soap); // deallocate runtime environment } |
int main() { struct soap soap; soap_init(&soap); soap_serve(&soap); } |
int main() { soap_serve(soap_new()); } |
int main() { struct soap soap1, soap2; pthread_t tid; ... soap_init(&soap1); if (soap_bind(&soap1, host, port, backlog) < 0) exit(-1); if (soap_accept(&soap1) < 0) exit(-1); pthread_create(&tid, NULL, (void*(*)(void*))soap_serve, (void*)&soap1]); ... soap_init(&soap2); soap_call_ns__method(&soap2, ...); // make a remote call ... soap_end(&soap2); ... pthread_join(tid); // wait for thread to terminate soap_end(&soap1); // release its data } |
Section 5.2.3 presents a multi-threaded stand-alone Web Service that handles multiple SOAP requests by spawning a thread for each request.
gSOAP interoperability has been verified with the following SOAP implementations and toolkits:
This user guide offers a quick way to get started with gSOAP. This section requires a basic understanding of the SOAP 1.1 protocol and some familiarity with C and/or C++. In principle, SOAP clients and SOAP Web services can be developed in C and C++ with the gSOAP stub and skeleton compiler without a detailed understanding of the SOAP protocol when the applications are build as an ensamble and only communicate within this group (i.e. meaning that you don't have to worry about interoperability with other SOAP implementations). This section is intended to illustrate the implementation of SOAP C/C++ Web services and clients that connect to other existing SOAP implementations such as Apache SOAP and SOAP::Lite for which some details of the SOAP protocol need to be understood.
In general, the implementation of a SOAP client application requires a stub routine for each remote method that the client application needs to invoke. The primary stub's responsibility is to marshall the input data, send the request to the designated SOAP service over the wire, to wait for the response, and to demarshall the output data when it arrives. The client application invokes the stub routine for a remote method as if it would invoke a local method. To write a stub routine in C or C++ by hand is a tedious task, especially if the input and/or output data structures of a remote method are complex data types such as records, arrays, and graphs.
The generation of stub routines for a SOAP client is fully automated with gSOAP. The gSOAP stub and skeleton compiler is a preprocessor that generates the necessary C++ sources to build SOAP C++ clients. The input to the gSOAP stub and skeleton compiler consists of a standard C/C++ header file. The header file can be generated from a WSDL (Web Service Description Language) documentation of a service with the gSOAP WSDL importer, see 5.2.7. The SOAP remote methods are specified in this header file as function prototypes. Stub routines in C/C++ source form are automatically generated by the gSOAP compiler for these function prototypes of remote methods. The resulting stub routines allow C and C++ client applications to seamlessly interact with existing SOAP Web services.
The gSOAP stub and skeleton compiler also generates skeleton routines for each of the remote methods specified in the header file. The skeleton routines can be readily used to implement one or more of the remote methods in a new SOAP Web service. These skeleton routines are not used for building SOAP clients in C++, although they can be used to build mixed SOAP client/server applications (peer applications).
The input and output parameters of a SOAP remote method may be simple data types or complex data types. The necessary type declarations of C/C++ user-defined data structures such as structs, classes, enumerations, arrays, and pointer-based data structures (graphs) are to be provided in the header file. The gSOAP stub and skeleton compiler automatically generates serializers and deserializers for the data types to enable the generated stub routines to encode and decode the contents of the parameters of the remote methods.
The remote method name and its parameterization can be found with a SOAP Web service description, typically in the form of an XML schema. There is an almost one-to-one correspondence between the XML schema description of a SOAP remote method and the C++ type declarations required to build a client application for the Web service. The schemas are typically part of the WSDL specification of a SOAP Web service. The gSOAP WSDL importer converts WSDL service descriptions into header files.
The getQuote remote method of XMethods Delayed Stock Quote service provides a delayed stock quote for a given ticker name,
see http://xmethods.com/detail.html?id=2 for details. The WSDL description of the Delayed Stock Quote service provides the
following details:
|
// Content of file "getQuote.h": int ns1__getQuote(char *symbol, float &Result); |
The Delayed Stock Quote service description requires that the input parameter of the getQuote remote method is a symbol parameter of type string. The description also indicates that the Result output parameter is a floating point number that represents the current unit price of the stock in dollars. The gSOAP compiler uses the convention the last parameter of the function prototype must be the output parameter of the remote method, which is required to be passed by reference using the reference operator (&) or by using a pointer type. All other parameters except the last are input parameters of the remote method, which are required to be passed by value or passed using a pointer to a value (by reference is not allowed). The function prototype associated with a remote method is required to return an int, whose value indicates to the caller whether the connection to a SOAP Web service was successful or resulted in an exception, see Section 7.2 for the error codes.
The use of the namespace prefix ns1__ in the remote method name in the function prototype declaration is discussed in detail in 5.1.2. Basically, a namespace prefix is distinghuished by a pair of underscores in the function name, as in ns1__getQuote where ns1 is the namespace prefix and getQuote is the remote method name. (A single underscore in an identifier name will be translated to a dash in the XML output, see Section 7.3.)
The gSOAP compiler is invoked from the command line with:
soapcpp2 getQuote.h |
int soap_call_ns1__getQuote(struct soap *soap, char *URL, char *action, char *symbol, float &Result); |
Note that the parameters of the soap_call_ns1__getQuote proxy are identical to the ns1__getQuote function prototype with three additional input parameters: soap must be a valid pointer to a gSOAP runtime environment, URL is the SOAP Web service endpoint URL passed as a string, and action is a string that denotes the SOAP action required by the Web service.
The following example C++ client program invokes the proxy to retrieve the latest AOL stock quote from the XMethods Delayed Stock
Quote service:
#include "soapH.h" // obtain the generated proxy int main() { struct soap soap; // gSOAP runtime environment float quote; soap_init(&soap); // initialize runtime environment (only once) if (soap_call_ns1__getQuote(&soap, "http://services.xmethods.net:80/soap", "", "AOL", quote) == SOAP_OK) cout << "Current AOL Stock Quote = " << quote; else // an error occurred soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream soap_end(&soap); // clean up } |
When the example client application is invoked, the SOAP request is performed by the proxy routine soap_call_ns1__getQuote, which
generates the following SOAP request message:
POST /soap HTTP/1.1 Host: services.xmethods.net Content-Type: text/xml Content-Length: 529 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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:ns1="urn:xmethods-delayed-quotes" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getQuote> <symbol>AOL</symbol> </ns1:getQuote> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
HTTP/1.1 200 OK Date: Sat, 25 Aug 2001 19:28:59 GMT Content-Type: text/xml Server: Electric/1.0 Connection: Keep-Alive Content-Length: 491 <?xml version='1.0' encoding='UTF-8'?> <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/1999/XMLSchema-instance' xmlns:xsd='http://www.w3.org/1999/XMLSchema' xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/' soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'> <soap:Body> <n:getQuoteResponse xmlns:n='urn:xmethods-delayed-quotes'> <Result xsi:type='xsd:float'>41.81</Result> </n:getQuoteResponse> </soap:Body> </soap:Envelope> |
A client program can invoke a remote method at any time and multiple times if necessary. Consider for example:
... struct soap soap; float quotes[3]; char *myportfolio[] = {"IBM", "AOL", "MSDN"}; soap_init(&soap); // need to initialize only once for (int i = 0; i < 3; i++) if (soap_call_ns1__getQuote(&soap, "http://services.xmethods.net:80/soap", "", myportfolio[i], quotes[i]) != SOAP_OK) break; if (soap.error) // an error occurred soap_print_fault(&soap, stderr); soap_end(&soap); // clean up all deserialized data ... |
This example demonstrated how easy it is to build a SOAP client with gSOAP once the details of a Web service are available in the form of a WSDL document.
The declaration of the ns1__getQuote function prototype (discussed in the previous section) uses the namespace prefix ns1__ of the remote method namespace, which is distinghuished by a pair of underscores in the function name to separate the namespace prefix from the remote method name. The purpose of a namespace prefix is to associate a remote method name with a service in order to prevent naming conflicts, e.g. to distinguish identical remote method names used by different services.
Note that the XML response of the XMethods Delayed Stock Quote service example uses the namespace prefix n which is associated with the namespace URI urn:xmethods-delayed-quotes through the xmlns:n="urn:xmethods-delayed-quotes binding. The use of namespace prefixes and namespace URIs is also required to enable SOAP applications to validate the content of a client's request and vice versa. The namespace URI 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 the client program. 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 stub and skeleton compiler.
The namespace mapping table for the Delayed Stock Quote client 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 {"ns1", "urn:xmethods-delayed-quotes"}, // given by the service description {NULL, NULL} // end of 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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:ns1="urn:xmethods-delayed-quotes" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> ... |
The incorporation of namespace prefixes into C++ identifier names is necessary to distinguish remote methods that
share the same name but are implemented in different Web services. Consider for example:
// Contents of file "getQuote.h": int ns1__getQuote(char *symbol, float &Result); int ns2__getQuote(char *ticker, char *"e); |
This example enables a client program to connect to a (hypothetical) Stock Quote service with remote methods that can only be distinghuished by their namespaces. Consequently, two different namespace prefixes have been used as part of the remote method names.
The namespace prefix convention can also be applied to class declarations that contain SOAP compound values
that share the same name but have different namespaces that refer to different XML schemas. For example:
class e__Address // an electronic address { char *email; char *url; }; class s__Address // a street address { char *street; int number; char *city; }; |
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> |
<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> |
struct Namespace namespaces[] = { ... {"e", "http://www.me.com/schemas/electronic-address"}, {"s", "http://www.me.com/schemas/street-address"}, ... |
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 stub and skeleton compiler, assumes SOAP encoding. This can be easily changed by using typedef definitions in
the header file input to the gSOAP compiler. The type name defined by a typedef definition corresponds to an XML schema
type and may include an optional namespace prefix. For example, the following typedef declarations, when part of the header
file input to the gSOAP compiler, defines various built-in XML schema 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 ... |
Reconsider the getQuote example, now rewritten with explicit XML schema types to illustrate the effect:
// Contents of file "getQuote.h": typedef char *xsd__string; typedef float xsd__float; int ns1__getQuote(xsd__string symbol, xsd__float &Result); |
int soap_call_ns1__getQuote(struct soap *soap, char *URL, char *action, char *symbol, float &Result); |
For example, when the client application calls the proxy, the proxy produces a SOAP request with xsd:string:
... <SOAP-ENV:Body> <ns1:getQuote><symbol xsi:type="xsd:string">AOL</symbol> </ns1:getQuote> </SOAP-ENV:Body> ... |
... <soap:Body> <n:getQuoteResponse xmlns:n='urn:xmethods-delayed-quotes'> <Result xsi:type='xsd:float'>41.81</Result> </n:getQuoteResponse> </soap:Body> ... |
There is no explicit standard convention for the response element name in SOAP, although it is recommended that the response element name is the method name ending with ``Response''. For example, the response element of getQuote is getQuoteResponse.
The response element name can be specified explicitly using a struct or class declaration in the header file. The struct or class name represents the SOAP response element name used by the service. Consequently, the output parameter of the remote method 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 gSOAP compiler to automatically generate a struct for the response which is internally used by a stub.
Reconsider the getQuote remote method specification which can be rewritten with an explicit declaration of a SOAP response
element as follows:
// Contents of "getQuote.h": typedef char *xsd__string; typedef float xsd__float; struct ns1__getQuoteResponse {xsd__float Result;}; int ns1__getQuote(xsd__string symbol, struct ns1__getQuoteResponse &r); |
... <SOAP-ENV:Body> <ns1:getQuote><symbol xsi:type="xsd:string">AOL</symbol> </ns1:getQuote> </SOAP-ENV:Body> ... |
... <soap:Body> <n:getQuoteResponse xmlns:n='urn:xmethods-delayed-quotes'> <Result xsi:type='xsd:float'>41.81</Result> </n:getQuoteResponse> </soap:Body> ... |
Note that the struct (or class) declaration may appear within the function prototype declaration. For example:
// Contents of "getQuote.h": typedef char *xsd__string; typedef float xsd__float; int ns1__getQuote(xsd__string symbol, struct ns1__getQuoteResponse {xsd__float Result;} &r); |
The gSOAP stub and skeleton compiler uses the convention that the single output parameter of a remote method is the last parameter of the function prototype declaration in a header file. All other parameters are considered input parameters of the remote method. To specify a remote method with multiple output parameters, a struct or class must be declared for the remote method response, see also 5.1.6. The fields of the struct or class are the output parameters of the remote method. 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 5.1.12.
As an example, consider a hypothetical remote method 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); |
... <SOAP-ENV:Envelope ... xmlns:ns3="urn:names" ...> ... <ns3:getNames> <SSN>999 99 9999</SSN> </ns3:getNames> ... |
... <m:getNamesResponse xmlns:m="urn:names"> <first>John</first> <last>Doe</last> </m:getNamesResponse> ... |
As another example, consider a remote method 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:
// Contente of file "copy.h": int X_rox__copy_name(char *name, struct X_rox__copy_nameResponse {char *name;} &r); |
The gSOAP 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> ... |
... <m:copy-nameResponse xmlns:m="urn:copy"> <name>SOAP</name> </m:copy-nameResponse> ... |
If the output parameter of a remote method is a complex data type such as a struct or class it is necessary to specify the response element of the remote method 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 5.1.6.
This is 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, see
http://xmethods.com/detail.html?id=86 for details. The remote method 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 SOAP C++ stub and skeleton 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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns1", "urn:galdemo:flighttracker"}, {"ns2", "http://galdemo.flighttracker.com"}, {NULL, NULL} }; |
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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/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> |
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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/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> |
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; |
A320 flight UAL184 traveling 497 mph at 37000 ft, is located 188 mi W of Lincoln, NE |
The SOAP 1.1 protocol allows parameter names to be anonymous. That is, the name(s) of the output parameters of a remote method are not strictly required to match a client's view of the parameters names. Also, the input parameter names of a remote method are not striclty required to match a service's view of the parameter names. Although this convention is likely to be deprecated in SOAP 1.2, the gSOAP compiler can generate stub and skeleton routines that support anonymous parameters. To make parameter names 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 "getQuote.h": typedef char *xsd__string; typedef float xsd__float; int ns1__getQuote(xsd__string symbol, &_return); |
// Contents of "getQuote.h": typedef char *xsd__string; typedef float xsd__float; struct ns1__getQuoteResponse {xsd__float _return;}; int ns1__getQuote(xsd__string symbol, struct ns1__getQuoteResponse &r); |
Caution: when anonymous parameter names are used, the order of the parameters in the function prototype of a remote method is significant.
To specify a remote method that has no input parameters, just provide a function prototype with one parameter which is the output
parameter. However, some C/C++ compilers (notably Visual C++TM) will not compile and complain about an empty
struct. 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); |
Most C/C++ compilers allow empty structs and therefore the void* parameter is not required.
The gSOAP stub and skeleton compiler generates skeleton routines in C++ source form for each of the remote methods specified as function prototypes in the header file processed by the gSOAP compiler. The skeleton routines can be readily used to implement the remote methods in a new SOAP Web service. The compound data types used by the input and output parameters of SOAP remote methods 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 remote method. The gSOAP 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 remote methods. The gSOAP compiler also generates a remote method 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.
The following example specifies three remote methods 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); |
To generate the skeleton routines, the gSOAP compiler is invoked from the command line with:
soapcpp2 calc.h |
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() main() { soap_serve(soap_new()); // use the remote method request dispatcher } // Implementation of the "add" remote method: int ns__add(struct soap *soap, double a, double b, double &result) { result = a + b; return SOAP_OK; } // Implementation of the "sub" remote method: int ns__sub(struct soap *soap, double a, double b, double &result) { result = a - b; return SOAP_OK; } // Implementation of the "sqrt" remote method: int ns__sqrt(struct soap *soap, double a, double &result); { if (a > = 0) { result = sqrt(a); return SOAP_OK; } else { soap_fault(soap); // allocate space for fault (if necessary) soap->fault->faultstring = "Square root of negative number"; soap->fault->detail = "I can only take the square root of a non-negative number"; return SOAP_FAULT; } } // 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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns", "urn:simple-calc"}, // bind "ns" namespace prefix {NULL, NULL} }; |
This service application can be readily installed as a CGI application. The service description would be:
|
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 remote method.)
The header file input to the gSOAP 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 proxy is available from the soapClient.cpp file after invoking the gSOAP 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.
The deployment of a Web service as a CGI application is an easy means to provide your service on the Internet. Services can also run as stand-alone services on intranets where client-service interactions are not blocked by firewalls.
To create a stand-alone service, only the main routine of the service needs to be modified. 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.cs.fsu.edu", 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); soap_serve(&soap); // process RPC skeletons 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 } |
|
The soap.accept_timeout attribute of the gSOAP run-time environment specifies the timeout value for a non-blocking soap_accept(&soap) call. See Section 12.10 for more details on timeout management.
See Section 6.7 for more details on memory management.
A client application connects to this stand-alone service with the endpoint machine.cs.fsu.edu:18083. A client may use the http:// prefix. When absent, no HTTP header is send and no HTTP-based information will be communicated to the service.
Multi-threading a Web Service is essential when the response times for handling requests by the service are (potentially) long. 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.
gSOAP 2.0 is thread safe and allows the implementation of multi-threaded stand-alone services. Multiple threads can be used to handle requests.
Here is an example of a multi-threaded Web Service:
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog #define MAX_THR (8) // Max. threads to serve requests int main(int argc, char **argv) { structsoap soap; soap_init(&soap); if (argc < 3) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_end(&soap); // cleanup } else { struct soap *soap_thr[MAX_THR]; // each thread needs a runtime environment pthread_t tid[MAX_THR]; char *host = argv[1]; int port = atoi(argv[2]); int m, s, i; m = soap_bind(&soap, host, port, BACKLOG); if (m < 0) 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 (s < 0) 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_new(); if (!soap_thr[i]) exit(-1); // could not allocate } else\ // recycle soap environment { pthread_join(tid[i], NULL); fprintf(stderr, "Thread %d completed\n", i); soap_end(soap_thr[i]); // deallocate data of old thread } soap_thr[i]->socket = s; pthread_create(&tid[i], NULL, (void*(*)(void*))soap_serve, (void*)soap_thr[i]); } } } return 0; } |
#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 < 3) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_end(&soap); // cleanup } else { void *process_request(void*); struct soap *tsoap; pthread_t tid; char *host = argv[1]; int port = atoi(argv[2]); int m, s; m = soap_bind(&soap, host, port, BACKLOG); if (m < 0) exit(-1); fprintf(stderr, "Socket connection successful %d\n", m); for (;;) { s = soap_accept(&soap); if (s < 0) 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_new(); if (!tsoap) break; tsoap->socket = s; pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap); } } return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_end((struct soap*)soap); free(soap); return NULL; } |
The same client header file specification issues apply to the specification and implementation of a SOAP Web service. Refer to
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 compiler produces one WSDL file for a set of remote methods. The names of the function prototypes of the remote methods 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 remote methods, multiple WSDL files will be created and each file describes the set of remote methods belonging to a namespace prefix.
To publish the WSDL service description, the %{}%-patterns that appear in the generated WSDL file have to be filled in with the
following information:
|
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", "%{URI}%"}, {NULL, NULL} }; |
To deploy a Web service, copy the compiled CGI service application to the designated CGI directory of your Web server. Make sure the file permissions are set right (chmod 755 calc.cgi for Unix/Linux). You can then publish the WSDL file on the Web.
The gSOAP 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 types.
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); |
<?xml version="1.0" encoding="UTF-8"?> <definitions name="%{Service}%" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="%{URL}%/%{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="%{URL}%/%{Service}%.wsdl" xmlns:ns="%{URL}%/ns.xsd"> <types> <schema xmlns="http://www.w3.org/2000/10/XMLSchema" targetNamespace="%{URL}%/ns.xsd" 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="%{Service}%PortType"> <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="%{Service}%Binding" type="tns:%{Service}%PortType"> <SOAP:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="add"> <SOAP:operation soapAction="%{URI}%#add"/> <input> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sub"> <SOAP:operation soapAction="%{URI}%#sub"/> <input> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sqrt"> <SOAP:operation soapAction="%{URI}%#sqrt"/> <input> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="%{URI}%" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> </binding> <service name="%{Service}%"> <port name="%{Service}%Port" binding="tns:%{Service}%Binding"> <SOAP:address location="%{URL}%/%{Service}%.cgi"/> </port> </service> </definitions> |
|
The creation of SOAP Web Service clients from a WSDL service description is a two-step process.
First, execute java wsdlcpp file.wsdl which generates the a header file file.h and a C-source file file.c with an example client program template. Modify the client program template to your needs.
Second, the header file file.h is to be processed by the gSOAP compiler by executing soapcpp2 file.h. This creates the C-source files to build a client application, see 5.1.
The following limitations are specific to the WSDL importer tool. The limitations are not general limitations of the gSOAP toolkit and the gSOAP stub and skeleton compiler. Future releases of the WSDL import tool will address these limitations.
By default, gSOAP generates WSDL and schemas with minOccurs=1 and maxOccurs=1 for non-array types, and
minOccurs=0 and maxOccurs=unbounded for array types.
The minOccurs and maxOccurs attribute values of fields in struct and class types are specified as
Type fieldname [minOccurs[:maxOccurs]] [= value] |
For example
struct ns__MyRecord { int n; int m 0; int __size 0:10; int *item; } |
<complexType name="MyRecord» <all> <element name="n" type="xsd:int" minOccurs="1" maxOccurs="1"/> <element name="m" type="xsd:int" minOccurs="0" maxOccurs="1"/> <element name="item" type="xsd:int" minOccurs="0" maxOccurs="10"/> </all> </complexType> |
This 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.
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 compiler is given below:
// Contents of file "quotex.h": int ns1__getQuote(char *symbol, float &result); // XMethods delayed stock quote service remote method int ns2__getRate(char *country1, char *country2, float &result); // XMethods currency-exchange service remote method int ns3__getQuote(char *symbol, char *country, float &result); // the new currency-converted stock quote service |
// 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(); else if (soap_call_ns3__getQuote(&soap, "http://www.cs.fsu.edu/~engelen/quotex.cgi", NULL, 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; if (soap_call_ns1__getQuote(soap, "http://services.xmethods.net/soap", NULL, symbol, q) == 0 && soap_call_ns2__getRate(soap, "http://services.xmethods.net/soap", NULL, "us", country, r) == 0) { result = q*r; return SOAP_OK; } else 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 remote methods. 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(structsoap *soap, char *symbol, float &result) { return SOAP_NO_METHOD; } // dummy: will never be called int ns2__getRate(structsoap *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} }; |
soapcpp2 quotex.h g++ -o quotex.cgi quotex.cpp soapC.cpp soapClient.cpp soapServer.cpp stdsoap2.cpp -lsocket -lxnet -lnsl -lm |
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 AOL uk |
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 remote methods, while the client part will only use the stubs and the service part will use the skeletons. Thus, dummy implementations of the unused remote methods 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).
The default gSOAP client-server interaction is synchonous: the client waits for the server to respond to the request. gSOAP also supports ``one-way'' SOAP messaging. SOAP messaging routines are declared as function prototypes, just like remote methods 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 a event message for SOAP messaging:
int ns__event(int eventNo, void dummy); |
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_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; } |
int soap_serve_ns__event(struct soap *soap); |
As usual, the skeleton will be automatically called by the remote method request dispatcher that handles both the remote method
requests (RPCs) and messages:
main() { soap_serve(soap_new()); } int ns__event(struct soap *soap, int eventNo) { ... // handle event return SOAP_OK; } |
The gSOAP stub and skeleton compiler generates serializers and deserializers for all 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 data in XML format that is possibly saved by other applications.
The following attributres can be set to control the destination and source for serialization and deserialization:
|
|
To serialize a data type, two functions need to be called to process the data. The first function (soap_serialize) analyzes pointers and determines if multi-references are required to encode the data and if the data contains cycles. The second function (soap_put) generates the SOAP encoding output for that data type.
The function names are 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>. 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 environment as a first parameter.
The following table lists the type naming conventions used:
|
struct ns__Person { char *name; } *p; |
soap_serialize_PointerTons__Person(&soap, &p); |
soap_put_PointerTons__Person(&soap, &p, "ns:element-name", "ns:type-name"); |
<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> |
If more than one data structure is to be serialized and parts of those data structures are shared through pointers, then the soap_serialize functions MUST to be called first before any of the soap_put functions. This is necessary to ensure that multi-reference data shared by the data structures is encoded as multi-reference.
For example, to encode the contents of two variables var1 and var2 the serializers are called before the output routines:
T1 var1; T2 var2; struct soap soap; ... soap_init(&soap); // initialize at least once soap_begin(&soap); // start new serialization phase soap.enable_embedding = 1; // do not use independent elements soap_serialize_Type1(&soap, &var1); soap_serialize_Type2(&soap, &var2); ... [soap.socket = a_socket_file_descriptor;] [soap.sendfd = an_output_file_descriptor;] [soap_begin_send(&soap);] // use buffered socket output soap_put_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); soap_put_Type2(&soap, &var2, "[namespace-prefic:]element-name2", "[namespace-prefix:]type-name2"); ... [soap_end_send(&soap);] // flush buffered socket output soap_end(&soap); // remove temporary data structures ... |
For serializing class instances, method invocations MUST be used instead of function calls, for example var.soap_serialize(&soap) and var.soap_put(&soap, "elt", "type"). This ensures that the proper serializers are used for serializing instances of derived classes.
In principle, encoding MAY take place without calling the soap_serialize functions. However, as the following example demonstrates the resulting encoding is not SOAP 1.1 compliant. However, the messages can still be used with gSOAP to save and restore data.
Consider the following struct:
// Contents of file "tricky.h": struct Tricky { int *p; int n; int *q; }; |
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 |
<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> |
To preserve the exact structure of the data, use the setting soap.enable_embedding=1 (see Section 6.6)
to serialize multi-referenced data
embedded in the structure which assures the preservation of structure but is not SOAP 1.1 compliant.
For example, the resulting output is:
<Tricky xsi:type="Tricky"> <p href="#2"/> <n id="2" xsi:type="int">1</n> <q href="#2"/> </Tricky> |
To deserialize a data type, its soap_get function 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_begin(&soap); // begin new decoding phase [soap.recvfd = an_input_stream;] [soap_begin_recv(&soap);] // if HTTP header is present, parse it soap_get_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); soap_get_Type2(&soap, &var2, "[namespace-prefix:]element-name2", "[namespace-prefix:]type-name1"); ... [soap_end_recv(&soap);] // check consistancy of id/hrefs soap_end(&soap); // remove temporary data, including the decoded data on the heap |
The soap_begin call resets the deserializers. The soap_end call removes the temporary data structures and the decoded data that was placed on the heap. Temporary data is created only if the SOAP content includes id and href attributes. An internal hash table is used by the deserializer to bound the id with the href names to reconstruct the shape of the data structure.
To remove temporary data while retaining the deserilzed data on the heap, the function soap_free should be called instead of soap_end.
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; }; |
// Contents of file "person.cpp": #include "soapH.h" int main() { struct soap soap; ns__Person mother, father, john; soap.enable_embedding = 1; // see 6.6 mother.name = "Mary"; mother.gender = female; mother.address.street = "Dowling 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_begin(&soap); john.soap_serialize(&soap); john.soap_put(&soap, "johnnie", NULL); soap_end(&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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the ``Person'' data type {NULL, NULL} }; |
soapcpp2 person.h g++ -o person person.cpp soapC.cpp stdsoap2.cpp -lsocket -lxnet -lnsl -lm |
Running the person application results in the SOAP 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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/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> |
#include "soapH.h" int main() { struct soap soap; ns__Person *mother, *father, *john = NULL; soap_init(&soap); soap_begin(&soap); soap_get_ns__Person(&soap, john, "johnnie", NULL); mother = john->mother; father = john->father; ... soap_free(&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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the ``Person'' data type {NULL, NULL} }; |
john = soap_get_ns__Person(&soap, NULL, "johnnie", NULL); |
Alternatively, the SOAP content can be decoded within an existing allocated data structure.
The following program fragment decodes the SOAP content in a struct ns__Person allocated on the stack:
#include "soapH.h" main() { struct soap soap; ns__Person *mother, *father, john; soap_init(&soap); soap_begin(&soap); soap_default_ns__Person(&soap, &john); soap_get_ns__Person(&soap, &john, "johnnie", NULL); mother = john->mother; father = john->father; ... soap_free(&soap); } struct Namespace namespaces[] = ... |
The gSOAP 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 |
Default values can also be assigned to individual struct and class fields of primitive type. For example,
struct MyRecord { char *name = Ünknown"; int value = 9999; enum Status { active, passive } status = passive; } |
The gSOAP stub and skeleton compiler is invoked from the command line and optionally takes the name of a header file as an
argument or, when the file name is absent, parses the standard input:
soapcpp2 [aheaderfile.h] |
|
The following files are part of the gSOAP package and are required to build client and service applications:
|
The compiler supports the following options:
|
soapcpp2 -cd '../projects' -pmy file.h |
../projects/myH.h ../projects/myC.c ../projects/myClient.c ../projects/myServer.c ../projects/myStub.h |
soapcpp2 /cd '..\projects' /pmy file.h |
After invoking the gSOAP stub and skeleton compiler on a header file description of a service, the client application can be compiled on a Linux machine as follows:
g++ -o myclient myclient.cpp stdsoap2.cpp soapC.cpp soapClient.cpp |
g++ -o myclient myclient.cpp stdsoap2.cpp soapC.cpp soapClient.cpp -lsocket -lxnet -lm |
The myclient.cpp file must include soapH.h and must define a global namespace mapping table. A typical client program layout with namespace mapping table is shown below:
// Contents of file "myclient.cpp" #include "soapH.h"; ... // A remote method invocation: soap_call_some_remote_method(...); ... 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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns1", "urn:my-remote-method"}, {NULL, NULL} }; ... |
After invoking the gSOAP stub and skeleton compiler on a header file description of the service, the server application can be compiled on a Linux machine as follows:
g++ -o myserver myserver.cpp stdsoap2.cpp soapC.cpp soapServer.cpp |
g++ -o myserver myserver.cpp stdsoap2.cpp soapC.cpp soapServer.cpp -lsocket -lxnet -lm |
The myserver.cpp file must include soapH.h and must define a global namespace mapping table. A typical service program layout with namespace mapping table is shown below:
// Contents of file "myserver.cpp" #include "soapH.h"; int main() { soap_serve(soap_new()); } ... // Implementations of the remote methods as C++ functions ... 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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns1", "urn:my-remote-method"}, {NULL, NULL} }; ... |
The gSOAP compiler can be used to create C (instead of C++) Web services and clients. The gSOAP stub and skeleton compiler
soapcpp2 generates .cpp files by default. However, these files only use C syntax and data types if the header
file input to soapcpp2 uses C syntax and data types. Therefore, a C compiler can be used to compile the .cpp files
(e.g. by renaming the extensions to .c) to create C Web service and client executables.
For example, with symbolic links on Unix/Linux:
ln -s soapC.cpp soapC.c ln -s soapClient.cpp soapClient.c ln -s soapServer.cpp soapServer.c soapcpp2 quote.h gcc quote.c stdsoap2.c soapC.c soapClient.c |
gSOAP is fully SOAP 1.1 (and partly SOAP 1.2) compliant and supports all SOAP 1.1 RPC features.
From the perspective of the C/C++ language, a few C++ language features are not supported by gSOAP and these features cannot be used in the specification of SOAP remote methods.
The following C++ language constructs cannot be used by the header file input to the SOAP C++ stub and skeleton compiler:
The gSOAP runtime environment flag variables can be set (i.e. 1, where default is 0 which means off) to control the SOAP XML
serialization of data with gSOAP.
Although gSOAP is fully SOAP 1.1 compliant, some SOAP implementations may have trouble accepting
multi-reference data and implicit null data so these flags can be used to put gSOAP in ``safe mode''. In addition, the embedding of multi-reference data is a feature that is likely to be adopted
in future SOAP specifications which gSOAP already supports (turned off by default).
The flags are:
|
Caution: Disabling hrefs (multi-reference data output) can be used to improve interoperability with SOAP implementations that are not fully SOAP 1.1 compliant. However, disabling hrefs will crash the serializer for cyclic data structures.
Understanding gSOAP's run-time memory management is important to optimize client and service applications by eliminating memory leaks and/or dangling references.
There are two forms of dynamic (heap) allocations made by gSOAP's runtime for serialization and deserialization of data. Temporary data is created by the runtime such as hash tables to keep pointer reference information for serialization and hash tables to keep XML id/href information for multi-reference object deserialization. Deserialized data is created upon receiving SOAP messages. This data is stored on the heap and involves calls to the malloc library function and new to create class instances. All such allocations are tracked by gSOAP's runtime by linked lists for later deallocation. The linked list for malloc allocations uses some extra space in each malloced block to form a chain of pointers through the malloced blocks. A separate malloced linked list is used to keep track of class instance allocations.
gSOAP does not enforce a deallocation policy and the user can adopt a deallocation policy that works best for a particular application. As a consequence, deserialized data is never deallocated by the gSOAP runtime unless the user explicitly forces deallocation by calling deallocation functions.
The deallocation functions are:
|
There are three situations to consider for memory deallocation policies for class instances:
class X { ... }; ns__remoteMethod(X *in, ...); |
class X { ... }; class ns__remoteMethodResponse { ... }; ns__remoteMethod(X *in, ns__remoteMethodResponse &out); |
typedef int xsd__int; class X { ... }; class ArrayOfint { xsd__int *__ptr; int __size; }; ns__remoteMethod(X *in, ArrayOfint *out); |
typedef int xsd__int; class X { ... }; class ArrayOfint { xsd__int *__ptr; int __size; }; ns__remoteMethod(X *in, ArrayOfint *&out); |
|
Space allocated with soap_malloc will be released with the soap_end and soap_dealloc functions.
The objects instantiated with soap_new_X are removed with soap_destroy.
For example, the following service uses temporary data in the remote method implementation:
int main() { ... struct soap soap; soap_init(&soap); soap_serve(&soap); soap_end(&soap); ... } |
int ns__itoa(struct soap *soap, int i, char **a) { *a = (char*)soap_malloc(soap, 11); sprintf(*a, "%d", i); return SOAP_OK; } |
int ns__mymethod(...) { ... if (exception) { soap_fault(soap); // allocate Fault data soap->fault->faultstring = (char*)soap_malloc(soap, 1024); strcpy(soap->fault->faultstring, ...); return SOAP_FAULT; } ... } |
class Sample { public: struct soap *soap; // reference to gSOAP's run-time ... Sample(); ~Sample(); }; |
Sample::Sample() { this->soap = NULL; } Sample::~Sample() { soap_unlink(this->soap, this); } |
struct soap *soap = soap_new(); // new gSOAP runtime Sample *obj = soap_new_Sample(soap, -1); // new Sample object with obj->soap set to runtime ... delete obj; // also calls soap_unlink to remove obj from the deallocation chain soap_destroy(soap); // deallocate all (other) class instances soap_end(soap); // clean up |
class ns__myClass { ... struct soap *soap; // set by soap_new_ns__myClass() char *name; void setName(const char *s); ... }; |
int ns__myMethod(struct soap *soap, ...) { ns__myClass *p = soap_new_ns__myClass(soap, -1); p->setName("SOAP"); return SOAP_OK; } void ns__myClass::ns__setName(const char *s) { if (soap) name = (char*)soap_malloc(soap, strlen(s)+1); else name = (char*)malloc(strlen(s)+1); strcpy(name, s); } ns__myClass::ns__myClass() { soap = NULL; name = NULL; } ns__myClass::~ns__myClass() { if (!soap && name) free(name); soap_unlink(soap, this); } |
To activate message logging for debugging, un-comment the #define DEBUG pragma in stdsoap2.h. Compile the client and/or
server applications as described above (or simply use g++ -DDEBUG ... to compile with debugging activated). When the client and server applications run, they will log their activity in three
separate files:
|
Caution: When installing a CGI application on the Web with debugging activated, the log files may sometimes not be created due to file access permission restrictions imposed on CGI applications. To get around this, create empty log files with universal write permissions. Be careful about the security implication of this.
You can test a service CGI application without deploying it on the Web.
To do this, create a client application for the service and activate message logging by this client.
Remove any old SENT.log file and run the client (which connects to the Web service or to another dummy, but valid address)
and copy the SENT.log file to another file, e.g. SENT.tst.
Then redirect the SENT.tst file to the service CGI application. For example,
myservice.cgi < SENT.tst |
The file names of the log files and the logging activity can be controlled at the application level. This allows the creation of
separate log files by separate services, clients, and threads.
For example, the following service logs all SOAP messages (but no debug messages) in separate directories:
struct soap soap; soap_init(&soap); ... soap_set_recv_logfile(&soap, "logs/recv/service12.log"); // append all messages received in /logs/recv/service12.log soap_set_sent_logfile(&soap, "logs/sent/service12.log"); // append all messages sent in /logs/sent/service12.log soap_set_test_logfile(&soap, NULL); // no file name: do not save debug messages ... soap_serve(&soap); ... |
g++ -o myclient myclient.cpp stdsoap2.cpp soapC.cpp soapClient.cpp -lsocket -lxnet -lnsl -lm |
g++ -o myclient myclient.cpp stdsoap2.cpp soapC.cpp soapClient.cpp -lsocket -lxnet -lnsl |
A SOAP remote method is specified as a C/C++ function prototype in a header file. The function is REQUIRED to return int, which is used to represent a SOAP error code, see Section 7.2. Multiple remote methods MAY be declared together in one header file.
The general form of a SOAP remote method specification is:
[int] [namespace_prefix__]method_name([inparam1, inparam2, ...,] outparam); |
The method request is encoded in SOAP as an XML element and the namespace prefix, method name, and input parameters are encoded using the format:
<[namespace-prefix:]method_name xsi:type="[namespace-prefix:]method_name> <inparam-name1 xsi:type="...">...</inparam-name1> <inparam-name2 xsi:type="...">...</inparam-name2> ... </[namespace-prefix:]method_name> |
The XML response by the Web service is of the form:
<[namespace-prefix:]method-nameResponse xsi:type="[namespace-prefix:]method-nameResponse> <outparam-name xsi:type="...">...</outparam-name> </[namespace-prefix:]method-nameResponse> |
The gSOAP stub and skeleton compiler generates a stub routine and a proxy for the remote method. This proxy is of the form:
int soap_call_[namespace_prefix__]method_name(struct soap *soap, char *URL, char *action, [inparam1, inparam2, ...,] outparam); |
The gSOAP stub and skeleton compiler generates a skeleton routine for the remote method. The skeleton function is:
int soap_serve_[namespace_prefix__]method_name(struct soap *soap); |
The input parameters of a remote method MUST be passed by value. Input parameters cannot be passed by reference with the & reference operator, but an input parameter value MAY be passed using a pointer to the data. Passing a pointer to the data is prefered when the size of the data of the parameter is large. Also, to pass instances of (derived) classes, pointers to the instance need to be used to avoid passing the instance by value which requires a temporary and prohibits passing derived class instances. When two input parameter values are identical, passing them using a pointer has the advantage that the value will be encoded only once as multi-reference (hence, the parameters are aliases). When input parameters are passed using a pointer, the data pointed to will not be modified by the remote method and returned to the caller.
The output parameter MUST be passed by reference using & or by using a pointer. Arrays are passed by reference by default and do not require the use of the reference operator &.
The input and output parameter types have certain limitations, see Section 6.5
If the output parameter is a struct or class type, it is considered a SOAP remote method response element instead of a simple output parameter value. That is, the name of the struct or class is the name of the response element and the struct or class fields are the output parameters of the remote method, see also 5.1.6. Hence, if the output parameter has to be a struct or class, a response struct or class MUST be declared as well. In addition, if a remote method returns multiple output parameters, a response struct or class MUST be declared. By convention, the response element is the remote method name ending with ``Response''.
The general form of a response element declaration is:
struct [namespace_prefix__]response_element_name { outparam1; outparam2; ... }; |
[int] [namespace_prefix__]method_name([inparam1, inparam2, ...,] struct [namespace_prefix__]response_element_name {outparam1[, outparam2, ...]} &anyparam); |
The method request is encoded in SOAP as an independent element and the namespace prefix, method name, and input parameters are encoded using the format:
<[namespace-prefix:]method-name xsi:type="[namespace-prefix:]method-name> <inparam-name1 xsi:type="...">...</inparam-name1> <inparam-name2 xsi:type="...">...</inparam-name2> ... </[namespace-prefix:]method-name> |
The method response is expected to be of the form:
<[namespace-prefix:]response-element-name xsi:type="[namespace-prefix:]response-element-name> <outparam-name1 xsi:type="...">...</outparam-name1> <outparam-name2 xsi:type="...">...</outparam-name2> ... </[namespace-prefix:]response-element-name> |
The input and/or output parameters can be made anonymous, which allows the deserialization of requests/responses with different parameter names as is endorsed by the SOAP 1.1 specification, see Section 5.1.12.
The error codes returned by the stub and skeleton routines are listed below.
|
A remote method implemented in a SOAP service MUST return an error code as the function's return value. SOAP_OK denotes success and SOAP_FAULT denotes an exception. The exception details can be assigned to the strings soap.fault->faultstring and soap.fault->detail, where soap is a variable that contains the current runtime environment, see Section 9.
One of the secrets behind the power and flexibility of gSOAP's encoding and decoding of remote method names, class names, type
identifiers, and struct or class fields is the ability to specify namespace prefixes with these names that are used to denote
their encoding style. More specifically, a C/C++ identifier name of the form
[namespace_prefix__]element_name |
<[namespace-prefix:]element-name ...> |
XML element names are NCNames (restricted strings) that MAY contain hypens, dots, and underscores. The special characters in the XML element names of remote methods, structs, classes, typedefs, and fields can be controlled using the following
conventions:
A single underscore in a namespace prefix or identifier name is replaced by a hyphen (-) in the XML element name.
For example, the identifier name SOAP_ENC__ur_type is represented in XML as SOAP-ENC:ur-type.
The sequence _DOT_ is replaced by a dot (.),
and the sequence _USCORE_ is replaced by an underscore (_)
in the corresponding XML element name.
For example:
class n_s__biz_DOT_com { char *n_s__biz_USCORE_name; }; |
<n-s:biz.com xsi:type="n-s:biz.com"> <n-s:biz_name xsi:type="string">Bizybiz</n-s:biz_name> </n-s:biz.com> |
For decoding, the underscores in identifier names act as wildcards. An XML element is parsed and matches the name of an identifier if the name is identical to the element name (case insensitive) and the underscores in the identifier name are allowed to match any character in the element name. For example, the identifier name I_want__soap_fun_the_bea___DOT_com matches the element name I-want:SOAP4fun@the-beach.com.
A namespace mapping table MUST be defined by clients and service applications. The mapping table is used by the serializers and
deserializers of the stub and skeleton routines to produce a valid SOAP payload and to validate an incoming SOAP payload.
A typical mapping table is shown below:
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/1999/XMLSchema-instance"}, // MUST be third {"xsd", "http://www.w3.org/1999/XMLSchema"}, // Required for XML schema types {"ns1", "urn:my-service-URI"}, // The namespace URI of the remote methods {NULL, NULL} // end of table }; |
An optional namespace pattern MAY be provided with each namespace mapping table entry.
The patterns provide an alternative namespace matching for the validation of decoded SOAP messages.
In this pattern, dashes (-) are single-character wildcards and asterisks (*) are multi-character wildcards.
For example, to decode different versions of XML Schema type with different authoring
dates, four dashes can be used in place of the specific dates in
the namespace mapping table pattern:
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"xsi", "http://www.w3.org/1999/XMLSchema-instance", "http://www.w3.org/----/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema", "http://www.w3.org/----/XMLSchema"}, ... |
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"xsi", "http://www.w3.org/1999/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema", "http://www.w3.org/*/XMLSchema"}, ... |
For decoding elements with namespace prefixes, the namespace URI associated with the namespace prefix (through the xmlns attribute of an XML element) is searched from the beginning to the end in a namespace mapping table, and for every row the following tests are performed as part of the validation process:
For example, let's say we have the following structs:
struct a__elt { ... }; struct b__elt { ... }; struct k__elt { ... }; |
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"a", "some uri"}, {"b", "other uri"}, {"c", "his uri", "* uri"}, ... |
<n:elt xmlns:n="some URI"> matches the struct name a__elt ... <m:elt xmlns:m="other URI"> matches the struct name b__elt ... <k:elt xmlns:k="my URI"> matches the struct name c__elt ... |
It is possible to use a number of different namespace tables and select the one that is appropriate.
For example, an application might contact many different Web services all using different namespace URIs.
If all the URIs are stored in one table, each remote method invocation will dump the whole namespace
table in the SOAP payload. There is no technical problem with that, but it can be ugly when the table is large.
To use different namespace tables, declare a pointer to a table and set the pointer to a particular table before remote method
invocation. For example:
struct Namespace namespacesTable1[] = { ... }; struct Namespace namespacesTable2[] = { ... }; struct Namespace namespacesTable3[] = { ... }; struct Namespace *namespaces; ... struct soap soap; ... soap_init(&soap); soap.namespaces = namespaceTable1; soap_call_remote_method(&soap, URL, Action, ...); ... |
This section describes the serialization and deserialization of C and C++ data types for SOAP 1.1 and 1.2 compliant encoding and decoding.
The default encoding rules for the primitive C and C++ data types are given in the table below:
|
By default, encoding of the primitive types will take place as per SOAP encoding style. The encoding can be changed to any XML schema type with an optional namespace prefix by using a typedef in the header file input to the gSOAP stub and skeleton compiler. The declaration enables the implementation of built-in XML schema types such as positiveInteger, xsd:anyURI, and xsd:date for which no built-in data structures in C and C++ exist but which can be represented using standard data structures such as strings, integers, and floats.
The typedef declaration is frequently used for convenience in C. A typedef declares a type name for a (complex) type expression. The type name can then be used in other declarations in place of the more complex type expression, which often improves the readability of the program code.
The gSOAP compiler interprets typedef declarations the same way as a regular C compiler interprets them, i.e. as types in declarations. In addition however, the gSOAP compiler will also use the type name in the encoding of the data in SOAP. The typedef name will appear as the XML element name of an independent element and as the value of the xsi:type attribute in the SOAP payload.
Many built-in primitive and derived XML schema types such as xsd:anyURI, positiveInteger, and decimal can be stored by standard primitive data structures in C++ as well such as strings, integers, floats, and doubles. To serialize strings, integers, floats, and doubles as built-in primitive and derived XML schema types. To this end, a typedef declaration can be used to declare an XML Schema type.
For example, the declaration
typedef unsigned int positiveInteger; |
<positiveInteger xsi:type="positiveInteger">3</positiveInteger> |
The built-in primitive and derived numerical XML Schema types are listed below together with their recommended typedef declarations. Note that the SOAP encoding schemas for primitive types are derived from the built-in XML schema types, so SOAP_ENC__ can be used as a namespace prefix instead of xsd__.
typedef char *xsd__anyURI; |
typedef char *xsd__base64Binary; |
typedef bool xsd__boolean; |
<xsd:boolean xsi:type="xsd:boolean">...</xsd:boolean> |
typedef char xsd__byte; |
<xsd:byte xsi:type="xsd:byte">...</xsd:byte> |
typedef time_t xsd__dateTime; |
Strings (char*) can be used to store xsd:dateTime XML schema types. The type declaration is:
typedef char *xsd__dateTime; |
typedef char *xsd__date; |
typedef double xsd__decimal; |
<xsd:double xsi:type="xsd:decimal">...</xsd:double> |
typedef double xsd__double; |
<xsd:double xsi:type="xsd:double">...</xsd:double> |
typedef char *xsd__duration; |
typedef float xsd__float; |
<xsd:float xsi:type="xsd:float">...</xsd:float> |
typedef char *xsd__hexBinary; |
typedef int xsd__int; |
typedef long xsd__int; |
<xsd:int xsi:type="xsd:int">...</xsd:int> |
typedef long long xsd__integer; |
<xsd:integer xsi:type="xsd:integer">...</xsd:integer> |
typedef long long xsd__long; |
typedef LONG64 xsd__long; |
<xsd:long xsi:type="xsd:long">...</xsd:long> |
typedef long long xsd__negativeInteger; |
<xsd:negativeInteger xsi:type="xsd:negativeInteger">...</xsd:negativeInteger> |
typedef unsigned long long xsd__nonNegativeInteger; |
<xsd:nonNegativeInteger xsi:type="xsd:nonNegativeInteger">...</xsd:nonNegativeInteger> |
typedef long long xsd__nonPositiveInteger; |
<xsd:nonPositiveInteger xsi:type="xsd:nonPositiveInteger">...</xsd:nonPositiveInteger> |
typedef char *xsd__normalizedString; |
<xsd:normalizedString xsi:type="xsd:normalizedString">...</xsd:normalizedString> |
typedef unsigned long long xsd__positiveInteger; |
<xsd:positiveInteger xsi:type="xsd:positiveInteger">...</xsd:positiveInteger> |
typedef short xsd__short; |
<xsd:short xsi:type="xsd:short">...</xsd:short> |
typedef char *xsd__string; |
<xsd:string xsi:type="xsd:string">...</xsd:string> |
typedef wchar_t *xsd__string; |
typedef wchar_t *xsd__string_; |
typedef char *xsd__time; |
typedef char *xsd__token; |
<xsd:token xsi:type="xsd:token">...</xsd:token> |
typedef unsigned char xsd__unsignedByte; |
<xsd:unsignedByte xsi:type="xsd:unsignedByte">...</xsd:unsignedByte> |
typedef unsigned int xsd__unsignedInt; |
typedef unsigned long xsd__unsignedInt; |
<xsd:unsignedInt xsi:type="xsd:unsignedInt">...</xsd:unsignedInt> |
typedef unsigned long long xsd__unsignedLong; |
typedef ULONG64 xsd__unsignedLong; |
<xsd:unsignedLong xsi:type="xsd:unsignedLong">...</xsd:unsignedLong> |
typedef unsigned short xsd__unsignedShort; |
<xsd:unsignedShort xsi:type="xsd:unsignedShort">...</xsd:unsignedShort> |
Trailing underscores (see Section 7.3) can be used in the type name in a typedef to enable the declaration of multiple storage
formats for a single XML schema type. For example, one part of a C/C++ application's data structure may use plain strings while
another part may use wide character strings. To enable this simultaneous use, declare:
typedef char *xsd__string; typedef wchar_t *xsd__string_; |
SOAP 1.1 supports polymorphic types, because XML schema types form a hierarchy. The root of the hierarchy is called xsd:anyType. So, for example, an array of xsd:anyType in SOAP may actually contain any mix of element types that are the derived types of the root type. The use of polymorphic types is indicated by the WSDL and schema descriptions of a Web service and can therefore be predicted/expected for each particular case.
On the one hand, the typedef construct provides a convenient way to associate C/C++ types with XML schema types and makes it easy to incorporate these types in a (legacy) C/C++ application. However, on the other hand the typedef declarations cannot be used to support polymorphic XML schema types. Most SOAP clients and services do not use polymorphic types. In case they do, the primitive polymorphic types can be declared as a hierarchy of C++ classes that can be used simultaneously with the typedef declarations.
The general form of a primitive type declaration that is derived from a super type is:
class xsd__type_name: [public xsd__super_type_name] { public: Type __item; [public:] [private] [protected:] method1; method2; ... }; |
For example, the XML schema type hierarchy can be copied to C++ with the following declarations:
class xsd__anyType { }; class xsd__anySimpleType: public xsd__anyType { }; typedef char *xsd__anyURI; class xsd__anyURI_: public xsd__anySimpleType { public: xsd__anyURI __item; }; typedef bool xsd__boolean; class xsd__boolean_: public xsd__anySimpleType { public: xsd__boolean __item; }; typedef char *xsd__date; class xsd__date_: public xsd__anySimpleType { public: xsd__date __item; }; typedef time_t xsd__dateTime; class xsd__dateTime_: public xsd__anySimpleType { public: xsd__dateTime __item; }; typedef double xsd__double; class xsd__double_: public xsd__anySimpleType { public: xsd__double __item; }; typedef char *xsd__duration; class xsd__duration_: public xsd__anySimpleType { public: xsd__duration __item; }; typedef float xsd__float; class xsd__float_: public xsd__anySimpleType { public: xsd__float __item; }; typedef char *xsd__time; class xsd__time_: public xsd__anySimpleType { public: xsd__time __item; }; typedef char *xsd__decimal; class xsd__decimal_: public xsd__anySimpleType { public: xsd__decimal __item; }; typedef char *xsd__integer; class xsd__integer_: public xsd__decimal_ { public: xsd__integer __item; }; typedef LONG64 xsd__long; class xsd__long_: public xsd__integer_ { public: xsd__long __item; }; typedef long xsd__int; class xsd__int_: public xsd__long_ { public: xsd__int __item; }; typedef short xsd__short; class xsd__short_: public xsd__int_ { public: xsd__short __item; }; typedef char xsd__byte; class xsd__byte_: public xsd__short_ { public: xsd__byte __item; }; typedef char *xsd__nonPositiveInteger; class xsd__nonPositiveInteger_: public xsd__integer_ { public: xsd__nonPositiveInteger __item; }; typedef char *xsd__negativeInteger; class xsd__negativeInteger_: public xsd__nonPositiveInteger_ { public: xsd__negativeInteger __item; }; typedef char *xsd__nonNegativeInteger; class xsd__nonNegativeInteger_: public xsd__integer_ { public: xsd__nonNegativeInteger __item; }; typedef char *xsd__positiveInteger; class xsd__positiveInteger_: public xsd__nonNegativeInteger_ { public: xsd__positiveInteger __item; }; typedef ULONG64 xsd__unsignedLong; class xsd__unsignedLong_: public xsd__nonNegativeInteger_ { public: xsd__unsignedLong __item; }; typedef unsigned long xsd__unsignedInt; class xsd__unsignedInt_: public xsd__unsginedLong_ { public: xsd__unsignedInt __item; }; typedef unsigned short xsd__unsignedShort; class xsd__unsignedShort_: public xsd__unsignedInt_ { public: xsd__unsignedShort __item; }; typedef unsigned char xsd__unsignedByte; class xsd__unsignedByte_: public xsd__unsignedShort_ { public: xsd__unsignedByte __item; }; typedef char *xsd__string; class xsd__string_: public xsd__anySimpleType { public: xsd__string __item; }; typedef char *xsd__normalizedString; class xsd__normalizedString_: public xsd__string_ { public: xsd__normalizedString __item; }; typedef char *xsd__token; class xsd__token_: public xsd__normalizedString_ { public: xsd__token __item; }; |
class xsd__base64Binary: public xsd__anySimpleType { public: unsigned char *__ptr; int __size; }; class xsd__hexBinary: public xsd__anySimpleType { public: unsigned char *__ptr; int __size; }; |
Methods are allowed to be added to the classes above, such as constructors and getter/setter methods.
The decoding rules for the primitive C and C++ data types is given in the table below:
|
|
All explicitly declared XML schema encoded primitive types adhere to the same decoding rules. For example, the following declaration:
typedef unsigned long long xsd__nonNegativeInteger; |
If more than one char pointer points to the same string, the string is encoded as a multi-reference value.
Consider for example
char *s = "hello", *t = s; |
<string id="123" xsi:type="string">hello</string> ... <string href="#123"/> |
Note: the use of typedef to declare a string type such as xsd__string will not affect the multi-reference string
encoding. However, strings declared with different typedefs will never be considered multi-reference even when they point
to the same string. For example
typedef char *xsd__string; typedef char *xsd__anyURI; xsd__anyURI *s = "http://www.myservice.com"; xsd__string *t = s; |
The implementation of string decoding in gSOAP allows for mixed content decoding. If the SOAP payload contains a complex data type in place of a string, the complex data type is decoded in the string as plain XML text.
For example, suppose the getInfo remote method returns some detailed information. The remote method is declared as:
// Contents of header file "getInfo.h": getInfo(char *detail); |
HTTP/1.1 200 OK Content-Type: text/xml Content-Length: nnn <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/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" <SOAP-ENV:Body> <getInfoResponse> <detail> <picture>Mona Lisa by <i>Leonardo da Vinci</i></picture> </detail> </getInfoResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
The double encoding format is by default set to ``%.18G'' (see a manual on printf text formatting in C), i.e. at most 18 digits of precision to limit a loss in accuracy. The float encoding format is by default ``%.9G'', i.e. at most 9 digits of precision.
The encoding format of a double type can be set by assigning a format string to soap.double_format, where soap is a
variable that contains the
current runtime environment. For example:
struct soap soap; soap_init(&soap); // sets double_format = "%.18G" soap.double_format = "%e"; // redefine |
struct soap soap; soap_init(&soap); // sets float_format = "%.9G" soap.float_format = "%.4f"; // redefine |
Caution: The format strings are not automatically reset before or after SOAP communications. An error in the format string may result in the incorrect encoding of floating point values.
The gSOAP runtime stdsoap2.cpp and header file stdsoap2.h support the marshalling of IEEE INF, -INF, and NaN
representations. Under certain circumstances this may break if the hardware and/or C/C++ compiler does not support these
representations.
To remove the representations, remove the inclusion of the <math.h> header file from the stdsoap2.h file.
You can control the representations as well, which are defined by the macros:
#define FLT_NAN #define FLT_PINFTY #define FLT_NINFTY #define DBL_NAN #define DBL_PINFTY #define DBL_NINFTY |
Enumerations are generally useful for the declaration of named integer-valued constants, also called enumeration constants.
The gSOAP stub and skeleton compiler encodes the constants of enumeration-typed variables in symbolic form using the names of the constants when possible to comply to SOAP's XML schema enumeration encoding style. Consider for example the following enumeration of weekdays:
enum weekday {Mon, Tue, Wed, Thu, Fri, Sat, Sun}; |
<weekday xsi:type="weekday">Mon</weekday> |
The encoding of complex types such as enumerations requires a reference to an XML schema through the use of a namespace prefix. The namespace prefix can be specified as part of the enumeration-type identifier's name, with the usual namespace prefix conventions for identifiers. This can be used to explicitly specify the encoding style. For example:
enum ns1__weekday {Mon, Tue, Wed, Thu, Fri, Sat, Sun}; |
<ns1:weekday xsi:type="ns1:weekday">Sat</ns1:weekday> |
<xsd:element name="weekday" type="tns:weekday"/> <xsd:simpleType name="weekday"> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Mon"/> <xsd:enumeration value="Tue"/> <xsd:enumeration value="Wed"/> <xsd:enumeration value="Thu"/> <xsd:enumeration value="Fri"/> <xsd:enumeration value="Sat"/> <xsd:enumeration value="Sun"/> </xsd:restriction> </xsd:simpleType> |
If the value of an enumeration-typed variable has no corresponding named constant, the value is encoded as a signed integer literal. For example, the following declaration of a workday enumeration type lacks named constants for Saturday and Sunday:
enum ns1__workday {Mon, Tue, Wed, Thu, Fri}; |
<ns1:workday xsi:type="ns1:workday">5</ns1:workday> |
Both symbolic and literal enumeration constants can be decoded.
To enforce the literal enumeration constant encoding and to get the literal constants in the WSDL file, use the following trick:
enum ns1__nums { _1 = 1, _2 = 2, _3 = 3 }; |
The gSOAP compiler supports the initialization of enumeration constants, as in:
enum ns1__relation {LESS = -1, EQUAL = 0, GREATER = 1}; |
A well-known deficiency of C and C++ enumeration types is the lack of support for the reuse of symbolic names by multiple enumerations. That is, the names of all the symbolic constants defined by an enumeration cannot be reused by another enumeration. To force encoding of the same symbolic name by different enumerations, the identifier of the symbolic name can end in an underscore (_) or any number of underscores to distinghuish it from other symbolic names in C++. This guarantees that the SOAP encoding will use the same name, while the symbolic names can be distinghuished in C++. Effectively, the underscores are removed from a symbolic name prior to encoding.
Consider for example:
enum ns1__workday {Mon, Tue, Wed, Thu, Fri}; enum ns1__weekday {Mon_, Tue_, Wed_, Thu_, Fri_, Sat_, Sun_}; |
Caution: The following declaration:
enum ns1__workday {Mon, Tue, Wed, Thu, Fri}; enum ns1__weekday {Sat = 5, Sun = 6}; |
When a pure C compiler is used to create SOAP clients and services, the bool type may not be supported by the compiler and
in that case an enumeration type should be used. The C enumeration-type encoding adopted by the gSOAP stub and skeleton compiler
can be used to encode boolean values according to the SOAP encoding style. The namespace prefix can be specified with the usual
namespace prefix convention for identifiers to explicitly specify the encoding style. For example, the built-in boolean XML
schema type supports the mathematical concept of binary-valued logic. The boolean XML schema encoding style can be specified
by using the xsd prefix. For example:
enum xsd__boolean {false_, true_}; |
<xsd:boolean xsi:type="xsd:boolean">false</xsd:boolean> |
enum SOAP_ENC__boolean {}; |
<SOAP-ENC:boolean xsi:type="SOAP-ENC:boolean">0<SOAP-ENC:boolean> |
A bitmask is an enumeration of flags such as declared with C#'s [Flags] enum annotation. gSOAP supports bitmask encoding and decoding for interoperability. However, bitmask types are not standardized with SOAP RPC.
A special syntactic convention is used in the header file input to the gSOAP compiler to indicate the use of bitmasks with an
asterisk:
enum * name { enum-constant, enum-constant, ... }; |
enum * ns__machineStatus { ON, BELT, VALVE, HATCH}; int ns__getMachineStatus(char *name, char *enum ns__machineStatus result); |
enum ns__machineStatus { ON=1, BELT=2, VALVE=4, HATCH=8}; |
int ns__getMachineStatus(struct soap *soap, char *name, enum ns__machineStatus result) { ... *result = BELT | HATCH; return SOAP_OK; } |
A struct data type is encoded as a SOAP compound data type such that the struct name forms the data type's element name and schema type and the fields of the struct are the data type's accessors. This encoding is identical to the class instance encoding without inheritance and method declarations, see Section 8.5 for further details. However, the encoding and decoding of structs is more efficient compared to class instances due to the lack of inheritance and the requirement by the marshalling routines to check inheritance properties at run time.
A class instance is encoded as a SOAP compound data type such that the class name forms the data type's element name and schema type and the data member fields are the data type's accessors. Only the data member fields are encoded in the SOAP payload. Class methods are not encoded.
The general form of a class declaration is:
class [namespace_prefix__]class_name1 [:[public:] [private:] [protected:] [namespace_prefix__]class_name2] { [public:] [private:] [protected:] field1; field2; ... [public:] [private:] [protected:] method1; method2; ... }; |
Only single inheritance is supported by the gSOAP compiler. Multiple inheritance is not supported, because of the limitations of the SOAP protocol.
If a constructor method is present, there MUST also be a constructor declaration with empty parameter list.
Templates are not supported by the gSOAP compiler.
A class instance is encoded as:
<[namespace-prefix:]class-name xsi:type="[namespace-prefix:]class-name"> <basefield-name1 xsi:type="...">...</basefield-name1> <basefield-name2 xsi:type="...">...</basefield-name2> ... <field-name1 xsi:type="...">...</field-name1> <field-name2 xsi:type="...">...</field-name2> ... </[namespace-prefix:]class-name> |
The decoding of a class instance allows any ordering of the accessors in the SOAP payload. However, if a base class field name is identical to a derived class field name because the field is overloaded, the base class field name MUST precede the derived class field name in the SOAP payload for decoding. gSOAP guarantees this, but interoperability with other SOAP implementations is cannot be guaranteed.
The following example declares a base class ns__Object and a derived class ns__Shape:
// Contents of file "shape.h": class ns__Object { public: char *name; }; class ns__Shape : public ns__Object { public: int sides; enum ns__Color {Red, Green, Blue} color; ns__Shape(); ns__Shape(int sides, enum ns__Green color); ~ns__Shape(); }; |
An instance of class ns__Shape with name Triangle, 3 sides, and color Green is encoded as:
<ns:Shape xsi:type="ns:Shape"> <name xsi:type="string">Triangle</name> <sides xsi:type="int">3</sides> <color xsi:type="ns:Color">Green</color> </ns:shape> |
A data member field of a class declared as static const is initialized with a constant value at compile time. This field is encoded in the serialization process, but is not decoded in the deserialization process. For example:
// Contents of file "triangle.h": class ns__Triangle : public ns__Object { public: int size; static const int sides = 3; }; |
<ns:Triangle xsi:type="ns:Triangle"> <name xsi:type="string">Triangle</name> <size xsi:type="int">15</size> <sides xsi:type="int">3>/sides> </ns:Triangle> |
Caution: The current gSOAP implementation does not support encoding static const fields, due to C++ compiler compatibility differences. This feature may be provided the future.
A class declaration in the header file input to the gSOAP compiler MAY include method declarations. The method implementations MUST NOT be part of the header file but are required to be defined in another C++ source that is externally linked with the application. This convention is also used for the constructors and destructors of the class.
Dynamic binding is supported, so a method MAY be declared virtual.
Interoperability between client and service applications developed with gSOAP is established even when clients and/or services use derived classes instead of the base classes used in the declaration of the remote method parameters. A client application MAY use pointers to instances of derived classes for the input parameters of a remote method. If the service was compiled with a declaration and implementation of the derived class, the remote method base class input parameters are demarshalled and a derived class instance is created instead of a base class instance. If the service did not include a declaration of the derived class, the derived class fields are ignored and a base class instance is created. Therefore, interoperability is guaranteed even when the client sends an instance of a derived classes and when a service returns an instance of a derived class.
The following example declares Base and Derived classes and a remote method that takes a pointer to a Base class instance and returns a Base class instance:
// Contents of file "derived.h" class Base { public: char *name; Base(); virtual void print(); }; class Derived : public Base { public: int num; Derived(); virtual void print(); }; int method(Base *in, struct methodResponse { Base *out; } &result); |
The Base and Derived class method implementations are:
// Method implementations of the Base and Derived classes: #include "soapH.h" ... Base::Base() { cout << "created a Base class instance" << endl; } Derived::Derived() { cout << "created a Derived class instance" << endl; } Base::print() { cout << "print(): Base class instance " << name << endl; } Derived::print() { cout << "print(): Derived class instance " << name << " " << num << endl; } |
// CLIENT #include "soapH.h" int main() { struct soap soap; soap_init(&soap); Derived obj1; Base *obj2; struct methodResponse r; obj1.name = "X"; obj1.num = 3; soap_call_method(&soap, url, action, &obj1, r); r.obj2->print(); } ... |
// SERVER1 #include "soapH.h" int main() { soap_serve(soap_new()); } int method(struct soap *soap, Base *obj1, struct methodResponse &result) { obj1->print(); result.obj2 = obj1; return SOAP_OK; } ... |
CLIENT: created a Derived class instance SERVER1: created a Derived class instance SERVER1: print(): Derived class instance X 3 CLIENT: created a Derived class instance CLIENT: print(): Derived class instance X 3 |
Now suppose a service application is developed that only accepts Base class instances. The header file is:
// Contents of file "base.h": class Base { public: char *name; Base(); virtual void print(); }; int method(Base *in, Base *out); |
The method implementation of the Base class are:
// Method implementations of the Base class: #include "soapH.h" ... Base::Base() { cout << "created a Base class instance" << endl; } Base::print() { cout << "print(): Base class instance " << name << endl; } |
// SERVER2 #include "soapH.h" int main() { soap_serve(soap_new()); } int method(struct soap *soap, Base *obj1, struct methodResponse &result) { obj1->print(); result.obj2 = obj1; return SOAP_OK; } ... |
CLIENT: created a Derived class instance SERVER2: created a Base class instance SERVER2: print(): Base class instance X CLIENT: created a Base class instance CLIENT: print(): Base class instance X |
The serialization of a pointer to a data type amounts to the serialization of the data type in SOAP and the SOAP encoded representation of a pointer to the data type is indistinghuishable from the encoded representation of the data type pointed to.
A data structure pointed to by more than one pointer is serialized as SOAP multi-reference data. This means that
the data will be serialized only once and
identified with a unique id attribute. The encoding of the pointers to the shared data is done through the use of
href attributes to refer to the multi-reference data (also see Section 6.6 on options to
control the serialization of multi-reference data).
Cyclic C/C++ data structures are encoded with multi-reference SOAP encoding.
Consider for example the following a linked list data structure:
typedef char *xsd__string; struct ns__list { xsd__string value; struct ns__list *next; }; |
<ns:list id="1" xsi:type="ns:list"> <value xsi:type="xsd:string">abc</value> <next xsi:type="ns:list"> <value xsi:type="xsd:string">def</value> <next href="#1"/> </next> </ns:list> |
typedef long xsd__int; struct ns__record { xsd__int *a; xsd__int *b; } P; struct ns__record { xsd__int a; xsd__int b; } R; ... P.a = &n; P.b = &n; ... |
<ns:record xsi:type="ns:record"> <a href="#1"/> <b href="#1"/> </ns:record> <id id="1" xsi:type="xsd:int">123</id> |
A NULL pointer is not serialized, unless the pointer itself is pointed to by another pointer (but see
Section 6.6 to control the serialization of NULLs).
For example:
struct X { int *p; int **q; } |
<... id="123" xsi:nil="true"/> |
Caution: When the deserializer encounters an XML element that has a xsi:nil="true" attribute but the corresponding C++ data is not a pointer or reference, the deserializer will terminate with a SOAP_NULL fault when the soap.enable_null flag is set. The types section of a WSDL description contains information on the ``nilability'' of data.
Fixed size arrays are encoded as per SOAP 1.1 one-dimensional array types. Multi-dimensional fixed size arrays are encoded by gSOAP as nested one-dimensional arrays in SOAP. Encoding of fixed size arrays supports partially transmitted and sparse array SOAP formats.
The decoding of (multi-dimensional) fixed-size arrays supports the SOAP multi-dimensional array format as well as partially transmitted and sparse array formats.
An example:
// Contents of header file "fixed.h": struct Example { float a[2][3]; }; |
<a xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="float[][2]"> <SOAP-ENC:Array xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="float[3]" <float xsi:type="float">...</float> <float xsi:type="float">...</float> <float xsi:type="float">...</float> </SOAP-ENC:Array> <SOAP-ENC:Array xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="float[3]" <float xsi:type="float">...</float> <float xsi:type="float">...</float> <float xsi:type="float">...</float> </SOAP-ENC:Array> </a> |
As the name suggests, dynamic arrays are much more flexible than fixed-size arrays and dynamic arrays are better adaptabe to the SOAP encoding and decoding rules for arrays. In addition, a typical C application allocates a dynamic array using malloc, assigns the location to a pointer variable, and deallocates the array later with free. A typical C++ application allocates a dynamic array using new, assigns the location to a pointer variable, and deallocates the array later with delete. Such dynamic allocations are flexible, but pose a problem for the serialization of data: how does the array serializer know the length of the array to be serialized given only a pointer to the sequence of elements? The application stores the size information somewhere. This information is crucial for the array serializer and has to be made explicitly known to the array serializer by packaging the pointer and array size information within a struct or class.
A special form of struct or class is used for one-dimensional dynamic arrays that contains a pointer variable and a field that records the number of elements the pointer points to in memory.
The general form of the struct declaration for one-dimensional dynamic arrays is:
struct some_name { Type *__ptr; int __size; [[static const] int __offset [= ...];] ... // anything that follows here will be ignored }; |
An alternative to a struct is to use a class with optional methods that MUST appear after the __ptr and
__size fields:
class some_name { public: Type *__ptr; int __size; [[static const] int __offset [= ...];] method1; method2; ... // any fields that follow will be ignored }; |
The deserializer of a dynamic array can decode partially transmitted and/or SOAP sparse arrays, and even multi-dimensional arrays which will be collapsed into a one-dimensional array with row-major ordering.
The following example header file specifies the XMethods Service Listing service getAllSOAPServices remote method and an array of SOAPService data structures:
// Contents of file "listing.h": class 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; }; class ServiceArray { public: ns3__SOAPService *__ptr; // points to array elements int __size; // number of elements pointed to ServiceArray(); ~ServiceArray(); void print(); }; int ns__getAllSOAPServices(ServiceArray &return_); |
#include "soapH.h"
... // ServiceArray class method implementations: ServiceArray::ServiceArray() { __ptr = NULL; __size = 0; } ServiceArray::~ServiceArray() { if (__ptr) free(__ptr); __size = 0; } void ServiceArray::print() { for (int i = 0; i < __size; i++) cout << __ptr[i].name << ": " << __ptr[i].homepage << endl; } ... // Request a service listing and display results: { struct soap soap; ServiceArray result; const char *endpoint = "www.xmethods.net:80/soap/servlet/rpcrouter"; const char *action = "urn:xmethodsServicesManager#getAllSOAPServices"; ... soap_init(&soap); soap_call_ns__getAllSOAPServices(&soap, endpoint, action, result); result.print(); ... } |
The declaration of a dynamic array as described in 8.8 MAY include an int __offset field. When set to an integer value, the serializer of the dynamic array will use this field as the start index of the array and the SOAP array offset attribute will be used in the SOAP payload.
For example, the following header file declares a mathematical Vector class, which is a dynamic array of floating point values with an index that starts at 1:
// Contents of file "vector.h": typedef float xsd__float; class Vector { xsd__float *__ptr; int __size; int __offset; Vector(); Vector(int n); float& operator[](int i); } |
Vector::Vector() { __ptr = NULL; __size = 0; __offset = 1; } Vector::Vector(int n) { __ptr = (float*)malloc(n*sizeof(float)); __size = n; __offset = 1; } Vector::~Vector() { if (__ptr) free(__ptr); } float& Vector::operator[](int i) { return __ptr[i-__offset]; } |
struct soap soap; soap_init(&soap); Vector v(3); v[1] = 1.0; v[2] = 2.0; v[3] = 3.0; soap_begin(&soap); v.serialize(&soap); v.put("vec"); soap_end(&soap); |
<vec xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="xsd:float[4]" SOAP-ENC:offset="[1]"> <item xsi:type="xsd:float">1.0</item> <item xsi:type="xsd:float">2.0</item> <item xsi:type="xsd:float">3.0</item> </vec> |
The decoding of a dynamic array with an __offset field is more efficient than decoding a dynamic array without an __offset field, because the __offset field will be assigned the value of the SOAP-ENC:offset attribute instead of padding the initial part of the array with default values.
One-dimensional dynamic arrays MAY be nested.
For example, using class Vector declared in the previous section, class Matrix is declared:
// Contents of file "matrix.h": class Matrix { public: Vector *__ptr; int __size; int __offset; Matrix(); Matrix(int n, int m); ~Matrix(); Vector& operator[](int i); }; |
The general form of the struct declaration for K-dimensional (K>1) dynamic arrays is:
struct some_name { Type *__ptr; int __size[K]; int __offset[K]; ... // anything that follows here will be ignored }; |
An alternative is to use a class with optional methods:
class some_name { public: Type *__ptr; int __size[K]; int __offset[K]; method1; method2; ... // any fields that follow will be ignored }; |
To encode the data type as an array, the name of the struct or class SHOULD NOT have a namespace prefix, otherwise the data type will be encoded and decoded as a SOAP list/vector, see Section 8.8.6.
The deserializer of a dynamic array can decode partially transmitted multi-dimensional arrays.
For example, the following declaration specifies a matrix class:
typedef double xsd__double; class Matrix { public: xsd__double *__ptr; int __size[2]; int __offset[2]; }; |
In case the name of the struct or class of a dynamic array has a namespace prefix, the data type is considered a list (a.k.a. vector) and will be serialized as a SOAP list and not encoded as a SOAP array.
For example:
struct ns__Map { struct ns__Binding {char *key; char *val;} *__ptr; int __size; }; |
<ns:Map xsi:type="ns:Map"> <ns:Binding xsi:type="ns:Binding"> <key>Joe</key> <val>555 77 1234</val> </ns:Binding> <ns:Binding xsi:type="ns:Binding"> <key>Susan</key> <val>555 12 6725</val> </ns:Binding> <ns:Binding xsi:type="ns:Binding"> <key>Pete</key> <val>555 99 4321</val> </ns:Binding> </ns:Map> |
A list (de)serialization is also in affect for dynamic arrays when the pointer field does not refer to a type that is associated
with a schema. For example:
struct vector { int *__ptr; int __size; }; |
<X> <item>1</item> <item>-2</item> ... </X> |
An array of pointers to class instances allows the encoding of polymorphic arrays (arrays of polymorphic element types)
and lists. For example:
class ns__Object { public: ... }; class ns__Data: public ns__Object { public: ... }; class ArrayOfObject { public: ns__Object **__ptr; int __size; int __offset; }; |
The __ptr field in a struct or class declaration of a dynamic array may have an optional suffix part that
describes the name of the tags of the SOAP array XML elements.
The suffix is part of the field name:
Type *__ptrarray_elt_name |
Consider for example:
struct ArrayOfstring { xsd__string *__ptrstring; int __size; }; |
<array xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="xsd:string[2]» <string xsi:type="xsd:string»Hello</string> <string xsi:type="xsd:string»World</string> </array> |
An array (or list) can be embedded in a struct/class without the need to declare a separate array data type. When a struct or class type declaration contains a int __size field and the next field below is a pointer type, gSOAP assumes the pointer type points to an array of values where the __size field holds the number of values at run time. Multiple arrays can be embedded in a struct/class by using __size field names that end with a unique name suffix.
The general convention for embedding arrays is:
struct ns__SomeStruct { ... int __sizename1; // number of elements pointed to Type1 *field1; // by this field ... int __sizename2; // number of elements pointed to Type2 *field2; // by this field ... }; |
For example, the following struct has two embedded arrays:
struct ns__Contact { char *firstName; char *lastName; int __sizePhones; ULONG64 *phoneNumber; // array of phone numbers int __sizeEmails; char **emailAddress; // array of email addresses char *socSecNumber; }; |
<mycontact xsi:type="ns:Contact"> <firstName>Joe</firstName> <lastName>Smith</lastName> <phoneNumber>5551112222</phoneNumber> <phoneNumber>5551234567</phoneNumber> <phoneNumber>5552348901</phoneNumber> <emailAddress>Joe.Smith@mail.com</emailAddress> <emailAddress>Joe@Smith.com</emailAddress> <socSecNumber>999999999</socSecNumber> </mycontact> |
The base64Binary XML schema type is a special form of dynamic array declared with a pointer (__ptr) to an unsigned char array.
For example using a struct:
struct xsd__base64Binary { unsigned char *__ptr; int __size; }; |
class xsd__base64Binary { public: unsigned char *__ptr; int __size; }; |
The SOAP_ENC:base64 encoding is another type for base 64 binary encoding
specified by the SOAP data type schema and some SOAP applications may use this form
(as indicated by their WSDL descriptions). It is declared by:
struct SOAP_ENC__base64 { unsigned char *__ptr; int __size; }; |
class SOAP_ENC__base64 { unsigned char *__ptr; int __size; }; |
The advantage of using a class is that methods can be used to initialize and manipulate the __ptr and __size fields. The user can add methods to this class to do this. For example:
class xsd__base64Binary { public: unsigned char *__ptr; int __size; xsd__base64Binary(); // Constructor xsd__base64Binary(int n); // Constructor ~xsd__base64Binary(); // Destructor unsigned char *location(); // returns the memory location int size(); // returns the number of bytes }; |
xsd__base64Binary::xsd__base64Binary() { __ptr = NULL; __size = 0; } xsd__base64Binary::xsd__base64Binary(int n) { __ptr = (unsigned int*)malloc(n); __size = n; } xsd__base64Binary::~xsd__base64Binary() { if (__ptr) free(__ptr); } unsigned char *xsd__base64Binary::location() { return __ptr; } int xsd__base64Binary::size() { return __size; } |
... FILE *fd = fopen("image.jpg", "r"); xsd__base64Binary image(filesize(fd)); fread(image.location(), image.size(), 1, fd); fclose(fd); soap_begin(&soap); image.soap_serialize(&soap); image.soap_put(&soap, "jpegimage", NULL); soap_end(&soap); ... |
Reading the xsd:base64Binary encoded image.
... xsd__base64Binary image; soap_begin(&soap); image.get(&soap, "jpegimage"); soap_end(&soap); ... |
The hexBinary XML schema type is a special form of dynamic array declared with the name xsd__hexBinary and a pointer (__ptr) to an unsigned char array.
For example, using a struct:
struct xsd__hexBinary { unsigned char *__ptr; int __size; }; |
class xsd__hexBinary { public: unsigned char *__ptr; int __size; }; |
gSOAP supports doc/literal SOAP encoding of request and/or response messages. However, there are some limitations on the XML format to support (de)serialization of XML documents into C/C++ data structures. XML documents may contain constructs that gSOAP cannot parse and will simply ignore because the gSOAP XML parser has been optimized for SOAP data. This occurs when the XML document uses XML attribute values that are part of the data. Arbitrary XML documents can be (de)serialized into regular C strings or wide character strings (wchar_t*) by gSOAP. Because XML documents are stored in strings, an application may need a ``plug-in'' XML parser to decode XML content stored in strings. For details on (de)serialization XML into strings, see Section 8.11.1.
gSOAP supports doc/literal SOAP encoding either manually by setting soap.encodingStyle, soap.defaultNamespace, and soap.disable_href of the current gSOAP environment soap, or automatically by using a gSOAP directive in the header file. In most doc/literal cases, the SOAP-ENV:encodingStyle attribute needs to be absent. To do this, set soap.encodingStyle=NULL. Furthermore, a default namespace needs to be defined by setting soap.defaultNamespace. Finally, doc/literal is a limited form of serialization and does not support graphs. So setting soap.disable_href=1 will not produce multi-reference data. Note that cyclic data will crash the doc/literal serializer because of this setting. Also polymorphic data may cause deserialization problems due to the absense of type information in the SOAP payload (which makes us wonder why doc/literal is the default in .NET).
The LocalTimeByZipCode remote method of the LocalTime service provides the local time given a zip code and uses doc/literal
SOAP encoding (using MS .NET).
The following header file declares the method:
int LocalTimeByZipCode(char *ZipCode, char **LocalTimeByZipCodeResult); |
To illustrate the manual doc/literal setting, the following client program sets the required properties before the call:
#include "soapH.h" int main() { struct soap soap; char *t; soap_init(&soap); soap.encodingStyle = NULL; // don't use SOAP encoding soap.defaultNamespace = "http://alethea.net/webservices/"; // use the service's namespace soap.disable_href = 1;" // don't produce multi-ref data (but can accept) if (soap_call_LocalTimeByZipCode(&soap, "http://alethea.net/webservices/LocalTime.asmx", "http://alethea.net/webservices/LocalTimeByZipCode", "32306", &t)) soap_print_fault(&soap, stderr); else printf("Time = %s\n", t); return 0; } 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"}, {NULL, NULL} }; |
POST /webservices/LocalTime.asmx HTTP/1.0 Host: alethea.net Content-Type: text/xml; charset=utf-8 Content-Length: 479 SOAPAction: "http://alethea.net/webservices/LocalTimeByZipCode" <?xml version="1.0" encoding=ÜTF-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="http://alethea.net/webservices/"> <SOAP-ENV:Body> <LocalTimeByZipCode><ZipCode>32306</ZipCode></LocalTimeByZipCode> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
//gsoap ns service name: localtime //gsoap ns service encoding: literal //gsoap ns service namespace: http://alethea.net/webservices/ int ns__LocalTimeByZipCode(char *ZipCode, char **LocalTimeByZipCodeResult); |
The example client program can be simplified into:
#include "soapH.h" #include "localtime.nsmap" // include generated map file int main() { struct soap soap; char *t; soap_init(&soap); if (soap_call_ ns__LocalTimeByZipCode(&soap, "http://alethea.net/webservices/LocalTime.asmx", "http://alethea.net/webservices/LocalTimeByZipCode", "32306", &t)) soap_print_fault(&soap, stderr); else printf("Time = %s\n", t); return 0; } |
To declare a literal XML ``type'' to hold XML documents in regular strings, use:
typedef char *XML; |
typedef wchar_t *XML; |
typedef char *XML; ns__GetDocument(XML m__XMLDoc, XML &m__XMLDoc_); |
<?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://my.org/" xmlns:m="http://my.org/mydoc.xsd" SOAP-ENV:encodingStyle=""> <SOAP-ENV:Body> <ns:GetDocument> <XMLDoc xmlns="http://my.org/mydoc.xsd"> ... </XMLDoc> </ns:Document> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
struct soap soap; soap_init(&soap); soap.encodingStyle = NULL; |
For interoperability with Apache SOAP, use
struct soap soap; soap_init(&soap); soap.encodingStyle = "http://xml.apache.org/xml-soap/literalxml"; |
typedef char *XML; ns__GetDocument(struct soap *soap, XML m__XMLDoc, struct ns__Document { XML m__XMLDoc; } &result); |
A predeclared standard SOAP Fault data structure is generated by the gSOAP stub and skeleton compiler for exchanging exception messages.
This predeclared data structure is:
struct SOAP_ENV__Fault { char *faultcode; char *faultstring; char *faultactor; char *detail; }; |
struct SOAP_ENV__Fault { char *faultcode; // MUST be string char *faultstring; // MUST be string char *faultactor; char *detail; // MUST be string Detail *t__detail; // new detail field (note namespace prefix "t") }; |
When the proxy of a remote method returns an error (see Section 7.2), thne soap.fault contains the SOAP Fault data.
When a remote method wants to raise an exception, it does so by assigning the fault field of the current reference to the
runtime environment with
appropriate data associated with the exception and by returning the error SOAP_FAULT.
For example:
soap_fault(soap); // allocate fault struct if necessary soap->fault->faultstring = "Stack dump"; soap->fault->detail = NULL; soap->fault->t__detail = sp; // point to stack (needs stack serializer) return SOAP_Fault; // return from remote method call |
Each remote method implementation in a service application can return a SOAP Fault upon an exception by returning an error code,
see Section 5.2.1 for details and an example.
In addition, a SOAP Fault can be returned by a service application through calling the soap_send_fault function.
This is useful in case the initialization of the application fails, as illustrated in the example below:
int main() { struct soap soap; soap_init(&soap); some initialization code if (initialization failed) { soap.error = SOAP_FAULT; soap_fault(&soap); soap.fault->faultcode = "Server"; soap.fault->faultstring = Ïnit failed"; soap.fault->details = "..."; soap_send_fault(&soap); // Send SOAP Fault to client return 0; // Terminate } } |
A predeclared standard SOAP Header data structure is generated by the gSOAP stub and skeleton compiler for exchanging SOAP
messages with SOAP Headers.
This predeclared data structure is:
struct SOAP_ENV__Header { void *dummy; }; |
To adapt the data structure to a specific need for SOAP Header processing, a new struct SOAP_ENV__Header can be added to the header file input to the gSOAP compiler. A class for the SOAP Header data structure can be used instead of a struct.
For example, the following header can be used for transaction control:
struct SOAP_ENV__Header { char *t__transaction; }; |
struct soap soap; soap_init(&soap); ... soap.header = NULL; // do not use a SOAP Header for the request (as set with soap_init) soap.actor = NULL; // do not use an actor (receiver is actor) soap_call_method(&soap, ...); if (soap.header) // a SOAP Header was received cout << soap.header->t__transaction; // Can reset, modify, or set soap.header here before next call soap_call_method(&soap, ...); // reuse the SOAP Header of the service response for the request ... |
... <SOAP-ENV:Envelope ...> <SOAP-ENV:Header> <t:transaction xsi:type="int">12345</t:transaction> </SOAP-ENV:Header> <SOAP-ENV:Body> ... </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
A Web service can read and set the SOAP Header as follows:
int main() { struct soap soap; soap.actor = NULL; // use this to accept all headers (default) soap.actor = "http://some/actor"; // accept headers destined for "http://some/actor" only soap_serve(&soap); } ... int method(struct soap *soap, ...) { if (soap->header) // a Header was received ... = soap->header->t__transaction; else soap->header = soap_malloc(sizeof(struct SOAP_ENV__Header)); // alloc new header ... soap->header->t__transaction = ...; return SOAP_OK; } |
The SOAP-ENV:mustUnderstand attribute indicates the requirement that the recipient of the SOAP Header (who must correspond to the SOAP-ENV:actor attribute when present or when SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next") MUST handle the Header part that carries the attribute. gSOAP handles this automatically on the background. However, an application still needs to inspect the header part's value and handle it appropriately. If a remote method in a Web service is not able to do this, it should return SOAP_MUSTUNDERSTAND to indicate this failure.
The syntax for the header file input to the gSOAP compiler is extended with a special storage qualifier mustUnderstand.
This qualifier can be used in the SOAP Header declaration to indicate which parts should carry a SOAP-ENV:mustUnderstand="1"
attrbute. For example:
struct SOAP_ENV__Header { char *t__transaction; mustUnderstand char *t__authentication; }; |
<SOAP-ENV:Envelope ...> <SOAP-ENV:Header> <t:transaction>5</t:transaction> <t:authentication SOAP-ENV:actor="http://some/actor" SOAP-ENV:mustUnderstand="1">XX</t:authentication> </SOAP-ENV:Header> <SOAP-ENV:Body> ... </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
gSOAP can transmit binary data with DIME attachments. The binary data is stored in augmented xsd:base64Binary and xsd:hexBinary structs/classes. These structs/classes have three additional fields: an id field for attachment referencing (typically a CID or UUID), a type field to specify the MIME type of the binary data, and an options field to piggy-back additional information with a DIME attachment. DIME attachment support is fully automatic, which means that gSOAP will test for the presence of attachments at run time and use SOAP in DIME accordingly.
A xsd:base64Binary type with DIME attachment support is declared by
struct xsd__base64Binary { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; |
char *soap_option(struct soap *soap, unsigned short type, const char *option) |
struct xsd__base64Binary image; image.__ptr = ...; image.__size = ...; image.id = "uuid:09233523-345b-4351-b623-5dsf35sgs5d6"; image.type = "image/jpeg"; image.options = soap_option(soap, 0, "My wedding picture"); |
If necessary, the xsd:base64Binary schema type and its attachment-based type can be separated with class inheritance. For example:
class xsd__base64Binary { unsigned char *__ptr; int __size; }; class xsd__base64Binary_ : xsd__base64Binary { char *id; char *type; char *options; }; |
struct soap soap; soap_init(&soap); soap.dime_id_format = "uuid:09233523-345b-4351-b623-5dsf35sgs5d6-%x"; |
Caution: Do not set the disable_request_count or disable_response_count attributes of the gSOAP run-time with DIME.
The use of wide-character strings (wchar_t*) in C and C++ clients and services suffices for internationalization. In contrast, when strings with wide characters are received and stored in regular strings, only the lower 8 bits of the wide characters are retained. The enable_utf_string attribute of the current gSOAP environment can be set to enable send and receive of wide-characters with regular strings. With this attrbute set, text will be stored in UTF8 format in the strings directly, which means that character codes 1 to 127 are treated as plain ASCII. Codes with the MSB set are UTF8-encoded characters. Please consult the UTF8 specification for details.
A header file can be augmented with directives for the gSOAP Stub and Skeleton compiler to automatically generate customized WSDL and namespace mapping tables contents. The WSDL and namespace mapping table files do not need to be modified by hand (Sections 5.2.5 and 7.4). These compiler directive are specified as //-comments.
Three directives are currently supported that can be used to specify details associated with namespace prefixes used by the remote
method names in the header file.
To specify the name of a Web Service in the header file, use:
//gsoap namespace-prefix service name: service-name |
To specify the location of a Web Service in the header file, use:
//gsoap namespace-prefix service location: URL |
To specify the name of the executable of a Web Service in the header file, use:
//gsoap namespace-prefix service executable: executable-name |
To specify the namespace URI of a Web Service in the header file, use:
//gsoap namespace-prefix service namespace: namespace-URI |
In addition, the schema namespace URI can be specified in the header file:
//gsoap namespace-prefix schema namespace: namespace-URI |
When header processing is required, each method declared in the WSDL should provide a binding to the parts of the header that may
appear as part of a method request message. Such a binding is given by:
//gsoap namespace-prefix service method-header-part: method-name header-part |
struct SOAP_ENV__Header { char *h__transaction; struct UserAuth *h__authentication; }; |
//gsoap ns service method-header-part: login transaction //gsoap ns service method-header-part: login authentication int ns__login(...); |
//gsoap ns service method-header-part: search transaction int ns__search(...); |
To specify the header parts for the method input (method request message), use:
//gsoap namespace-prefix service method-input-header-part: method-name header-part |
//gsoap namespace-prefix service method-output-header-part: method-name header-part |
When doc/literal encoding is required, the service encoding can be specified in the header file:
//gsoap namespace-prefix service encoding: literal |
//gsoap namespace-prefix service encoding: encoding-style |
(Note: blanks can be used anywhere in the directive, except between // and gsoap.)
The use of these directive is best illustrated with an example.
The quotex.h header file of the quotex example in the gSOAP distribution for Unix/Linux is:
//gsoap ns1 service namespace: urn:xmethods-delayed-quotes int ns1__getQuote(char *symbol, float &result); //gsoap ns2 service namespace: urn:xmethods-CurrencyExchange int ns2__getRate(char *country1, char *country2, float &result); //gsoap ns3 service name: quotex //gsoap ns3 service location: http://www.cs.fsu.edu/~engelen //gsoap ns3 service namespace: urn:quotex int ns3__getQuote(char *symbol, char *country, float &result); |
Namespace prefix ns3 is used for the new quotex Web Service with namespace URI urn:quotex,
service name quotex, and location http://www.cs.fsu.edu/~engelen.
Since the new Web Service invokes the ns1__getQuote and ns2__getRate remote methods,
the service namespaces of these methods are given.
The service names and locations of these methods are not given because they
are only required for setting up a new Web Service for these methods
(but may also be provided in the header file for documentation purposes).
After invoking the gSOAP Stub and Skeleton compiler on the quotex.h header file:
soapcpp2 quotex.h |
The namespace mapping table for the quotex.cpp Web Service implementation is saved as quotex.nsmap.
This file can be directly included in quotex.cpp instead of specified by hand in the source of quotex.cpp:
#include "quotex.nsmap" |
There are situations when certain data types have to be ignored by gSOAP for the compilation of (de)marshalling routines. For example, in certain cases the fields in a class or struct need not be (de)serialized, or the base class of a derived class should not be (de)serialized, and certain built-in classes such as ostream cannot be (de)serialized. These data types (including fields) are called ``transient'' and can be declared outside of gSOAP's compilation window. Transient data type and transient fields are declared with the extern keyword or are declared within [ and ] blocks in the header file input to the gSOAP compiler. The extern keyword has a special meaning to the gSOAP compiler and won't affect the generated codes. The special [ and ] block construct can be used with data type declarations and within struct and class declarations. The use of extern or [ ] achieve the same effect, but [ ] may be more convenient to encapsulate transient types in a larger part of the header file. The use of extern with typedef is reserved for the declaration of user-defined external (de)serializers for data types, see Section 12.4.
First example:
extern class ostream; // ostream can't be (de)serialized, but need to be declared to make it visible to gSOAP class ns__myClass { ... virtual void print(ostream &s) const; // need ostream here ... }; |
[ class myBase // base class need not be (de)serialized { ... }; ] class ns__myDerived : myBase { ... }; |
[ typedef int transientInt; ] class ns__myClass { int a; // will be (de)serialized [ int b; // transient field char s[256]; // transient field ] extern float d; // transient field char *t; // will be (de)serialized transientInt *n; // transient field [ virtual void method(char buf[1024]); // does not create a char[1024] (de)serializer ] }; |
Functions prototypes of remote methods cannot be declared transient and will result in errors when attempted.
Users can declare their own (de)serializers for specific data types instead of relying on the gSOAP-generated (de)serializers.
To declare a external (de)serializer, declare a type with extern typedef. gSOAP will not generate the (de)serialzers
for the type name that is declared. For example:
extern typedef char *MyData; struct Sample { MyData s; // use user-defined (de)serializer for this field char *t; // use gSOAP (de)serializer for this field }; |
void soap_mark_T(struct soap *soap, const T *a) void soap_default_T(struct soap *soap, T *a) void soap_out_T(struct soap *soap, const char *tag, int id, const T *a, const char *type) T *soap_in_T(struct soap *soap, const char *tag, T *a, const char *type) |
For example, the (de)serialization of MyData can be done with the following code:
void soap_mark_MyData(struct soap *soap, MyData *const*a) { } // no need to mark this node (for multi-ref and cycle detection) void soap_default_MyData(&soap, MyData **a) { *a = NULL } void soap_out_MyData(struct soap *soap, const char *tag, int id, MyData *const*a, const char *type) { soap_element_begin_out(soap, tag, id, type); // print XML beginning tag soap_send(soap, *a); // just print the string (no XML conversion) soap_element_end_out(soap, tag); // print XML ending tag } MyData **soap_in_MyData(struct soap *soap, const char *tag, MyData **a, const char *type) { if (soap_element_begin_in(soap, tag)) return NULL; if (!a) a = (MyData**)soap_malloc(soap, sizeof(MyData*)); if (soap->null) *a = NULL; // xsi:nil element if (*soap->type && soap_match_tag(soap, soap->type, type)) { soap->error = SOAP_TYPE_MISMATCH; return NULL; // type mismatch } if (*soap->href) a = (MyData**)soap_id_forward(soap, soap->href, a, SOAP_MyData, sizeof(MyData*)) else if (soap->body) { char *s = soap_value(soap); // fill buffer *a = (char*)soap_malloc(soap, strlen(s)+1); strcpy(*a, s); } if (soap->body && soap_element_end_in(soap, tag)) return NULL; return a; |
gSOAP serializes data in XML with xsi:type attributes when the types are declared with namespace prefixes to indicate the type of the data contained in the elements. SOAP 1.1 and 1.2 requires xsi:type attributes in the presence of polymorphic data or when the type of the data cannot be deduced from the SOAP payload.
To omit the generation of xsi:type attributes in the serialization, simply use type declarations that do not include namespace prefixes. The only remaining issue is the (de)serialization of lists/vectors with typed elements. To declare a list/vector with typed elements, use a leading underscores for type names of the struct or class. The leading underscores in type names makes type anonymous (invisible in XML).
gSOAP provides five callback functions for customized I/O and HTTP handling:
|
The following example uses I/O function callbacks for customized serialization of data into a buffer and deserialization back into a
datastructure:
char buf[10000]; // XML buffer int len1 = 0; // #chars written int len2 = 0; // #chars read // mysend: put XML in buf[] int mysend(struct soap *soap, const char *s, size_t n) { if (len1 + n > sizeof(buf)) return SOAP_EOF; strcpy(buf + len1, s); len1 += n; return SOAP_OK; } // myrecv: get XML from buf[] size_t myrecv(struct soap *soap, char *s, size_t n) { if (len2 + n > len1) n = len1 - len2; strncpy(s, buf + len2, n); len2 += n; return n; } main() { struct soap soap; struct ns__person p; soap_init(&soap); len1 = len2 = 0; // reset buffer pointers p.name = "John Doe"; p.age = 25; soap.fsend = mysend; // assign callback soap.frecv = myrecv; // assign callback soap_begin(&soap); soap.enable_embedding = 1; soap_serialize_ns__person(&soap, &p); soap_put_ns__person(&soap, &p, "ns:person", NULL); if (soap.error) { soap_print_fault(&soap, stdout); exit(-1); } soap_end(&soap); soap_begin(&soap); soap_get_ns__person(&soap, &p, "ns:person", NULL); if (soap.error) { soap_print_fault(&soap, stdout); exit(-1); } soap_end(&soap); soap_init(&soap); // disable callbacks } |
The following example illustrates customized I/O and (HTTP) header handling. The SOAP request is saved to a file. The client proxy then reads the file contents as the service response. To perform this trick, the service response has exactly the same structure as the request. This is declared by the struct ns__test output parameter part of the remote method declaration. This struct resembles the service request (see the generated soapStub.h file created from the header file).
The header file is:
//gsoap ns service name: callback //gsoap ns service namespace: urn:callback struct ns__person { char *name; int age; }; int ns__test(struct ns__person in, struct ns__test &out); |
#include "soapH.h" ... int myopen(struct soap *soap, const char *endpoint, const char *host, int port) { if (strncmp(endpoint, "file:", 5)) { printf("File name expected\n"); return SOAP_EOF; } if ((soap->sendfd = soap->recvfd = open(host, O_RDWR, S_IWUSR|S_IRUSR)) < 0) return SOAP_EOF; return SOAP_OK; } void myclose(struct soap *soap) { if (soap->sendfd > 2) // still open? close(soap->sendfd); // then close it soap->recvfd = 0; // set back to stdin soap->sendfd = 1; // set back to stdout } int mypost(struct soap *soap, const char *endpoint, const char *host, const char *path, const char *action, size_t count) { return soap_send(soap, "Custom-generated file\n"); // writes to soap->sendfd } int myparse(struct soap *soap) { char buf[256]; if (lseek(soap->recvfd, 0, SEEK_SET) < 0 || soap_getline(soap, buf, 256)) // go to begin and skip custom header return SOAP_EOF; return SOAP_OK; } main() { structsoap soap; struct ns__test r; struct ns__person p; soap_init(&soap); // reset p.name = "John Doe"; p.age = 99; soap.fopen = myopen; // use custom open soap.fpost = mypost; // use custom post soap.fparse = myparse; // use custom response parser soap.fclose = myclose; // use custom close soap_call_ns__test(&soap, "file://test.xml", "", p, r); if (soap.error) { soap_print_fault(&soap, stdout); exit(-1); } soap_end(&soap); soap_init(&soap); // reset to default callbacks } |
int myignore(struct soap *soap, const char *tag) { return SOAP_MUSTUNDERSTAND; // never skip elements (secure) } ... soap.fignore = myignore; soap_call_ns__method(&soap, ...); // or soap_serve(&soap); |
int myignore(struct soap *soap, const char *tag) { if (soap_match_tag(soap, tag, "ns:xyz") != SOAP_OK) return SOAP_MUSTUNDERSTAND; return SOAP_OK; } ... soap.fignore = myignore; soap_call_ns__method(&soap, ...); // or soap_serve(&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/1999/XMLSchema-instance"}, {"xsd", "http://www.w3.org/1999/XMLSchema"}, {"ns", "some-URI"}, // the namespace of element ns:xyz {NULL, NULL} |
The callback can also be used to keep track of unknown elements in an internal data structure such as a list:
struct Unknown { char *tag; struct Unknown *next; }; int myignore(struct soap *soap, const char *tag) { char *s = (char*)soap_malloc(soap, strlen(tag)+1); struct Unknown *u = (struct Unknown*)soap_malloc(soap, sizeof(struct Unknown)); if (s && u) { strcpy(s, tag); u->tag = s; u->next = ulist; ulist = u; } } ... struct soap *soap; struct Unknown *ulist = NULL; soap_init(&soap); soap.fignore = myignore; soap_call_ns__method(&soap, ...); // or soap_serve(&soap) // print the list of unknown elements soap_end(&soap); // clean up |
gSOAP uses HTTP 1.0 by default. gSOAP supports HTTP 1.1, but does not support all HTTP 1.1 transfer encodings such as gzipped
encodings. gSOAP does support HTTP 1.1 chunked-transfer encoding. Nevertheless, the the HTTP version used can be changed by
setting the attribute:
struct soap soap; soap_init(&soap); ... soap.http_version = "1.1"; |
gSOAP supports keep-alive socket connections. To activate keep-alive support, set the keep_alive attribute:
struct soap soap; soap_init(&soap); ... soap.keep_alive = 1; |
Keep-alive support can be activated for stand-alone services:
main() { ... soap.keep_alive = 1; s = soap_accept(&soap); ... while (soap_serve(&soap) == SOAP_OK && soap.keep_alive) ...; ... } |
gSOAP supports HTTP chunked transfer encoding. Un-chunking of inbound messages takes place automatically. Outbound messages are never chunked, except when the soap.chunked_transfer=1 is set, where soap is the current gSOAP run-time environment. Most Web services, however, will not accept chunked inbound messages. Note that chunking allows soap.disable_request_count=0 and soap.disable_response_count=0, but not when DIME attachments are used.
Socket connect, accept, send, and receive timeout values can be set to manage socket communication timeouts. The soap.connect_timeout, soap.accept_timeout, soap.send_timeout, and soap.recv_timeout attributes of the current gSOAP runtime environment soap can be set to the appropriate user-defined socket send, receive, and accept timeout values. A positive value measures the timeout in seconds. A negative timeout value measures the timeout in microseconds (10-6 sec).
The soap.connect_timeout specifies the timeout value for soap_call_ns__method calls.
The soap.accept_timeout specifies the timeout value for soap_accept(&soap) calls.
The soap.send_timeout and soap.recv_timeout specify the timeout values for non-blocking socket I/O operations.
Example:
struct soap soap; soap_init(&soap); soap.send_timeout = 10; soap.recv_timeout = 10; |
soap.send_timeout = 0; soap.recv_timeout = 0; |
#define uSec *-1 #define mSec *-1000 soap.accept_timeout = 10 uSec; soap.send_timeout = 20 mSec; soap.recv_timeout = 20 mSec; |
Caution: Many Linux versions do not support non-blocking connect(). Therefore, setting soap.connect_timeout for non-blocking soap_call_ns__method calls may not work under Linux.
You need to install the OpenSSL library on your platform to enable secure SOAP clients to utilize HTTPS/SSL.
After installation, compile your application with option -DWITH_OPENSSL. For example on Linux:
g++ -DWITH_OPENSSL myclient.cpp stdsoap.cpp soapC.cpp soapClient.cpp -lssl -lcrypto |
g++ -DWITH_OPENSSL myclient.cpp stdsoap.cpp soapC.cpp soapClient.cpp -lxnet -lsocket -lnsl -lssl -lcrypto |
#define WITH_OPENSSL |
soap_call_ns__mymethod(&soap, "https://domain/path/secure.cgi", "", ...); |
By default, server authentication is disabled. To enable server authentication, set the require_server_auth attribute of
the current gSOAP runtime environment (struct soap) before a call is made:
soap.require_server_auth = 1; |
Make sure you have signal handlers set in your application to catch broken connections (SIGPIPE):
signal(SIGPIPE, sigpipe_handle); |
void sigpipe_handle(int x) { } |
When a Web Service is installed as CGI, it uses standard I/O that is encryped/decrypted by the Web server that runs the CGI application. Therefore, HTTPS/SSL support must be configured for the Web server (not Web Service).
SSL support for stand-alone gSOAP Web services is accomplished by calling soap_ssl_accept after soap_accept.
In addition, a key file, CA file, DH file, and password need to be supplied. Instructions on how to do this can be found in the
OpenSSL documentation.
To enable OpenSSL, first install OpenSSL and use option -DWITH_OPENSSL with your C or C++ compiler, for example:
g++ -DWITH_OPENSSL -o myprog myprog.cpp stdsoap2.cpp soapC.cpp soapServer.cpp -lssl -lcrypto |
int main() { int m, s; pthread_t tid; struct soap soap, *tsoap; soap_init(&soap); soap.keyfile = "server.pem"; // must be resident key file soap.cafile = "cacert.pem"; // must be resident CA file soap.dhfile = "dh512.pem"; // must be resident DH file soap.password = "password"; // password m = soap_bind(&soap, "linprog2.cs.fsu.edu", 18000, 100); if (m < 0) { soap_print_fault(&soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for (;;) { s = soap_accept(&soap); fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); if (s < 0) { soap_print_fault(&soap, stderr); exit(-1); } if (soap_ssl_accept(&soap)) { soap_print_fault(&soap, stderr); exit(-1); } tsoap = soap_new(); if (!tsoap) exit(-1); tsoap->socket = soap.socket; // set by soap_accept tsoap->ssl = soap.ssl; // set by soap_ssl_accept tsoap->bio = soap.bio; // set by soap_ssl_accept pthread_create(&tid, NULL, &process_request, (void*)tsoap); } return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_end((struct soap*)soap); free(soap); return NULL; } |
... soap_init(&soap); soap.keyfile = "client.pem"; soap.password = "password"; soap.cafile = "cacert.pem"; if (soap_call_ns__method(&soap, "https://linprog2.cs.fsu.edu:18000", "", ...) ... |
signal(SIGPIPE, sigpipe_handle); |
void sigpipe_handle(int x) { } |
Client-side cookie support is optional. To enable cookie support, compile with option -DWITH_COOKIES, for example:
g++ -DWITH_COOKIES -o myclient stdsoap2.cpp soapC.cpp soapClient.cpp |
#define WITH_COOKIES |
A database of cookies is kept and returned to the appropriate servers. Cookies are not automatically saved to a file by a client. So the internal cookie database is discarded when the client program terminates.
To avoid "cookie storms" caused by malicious servers that return an
unreasonable amount of cookies, gSOAP clients/servers are restricted to
a database size that the user can limit (32 cookies by default), for example:
struct soap soap; soap_init(&soap); soap.cookie_max = 10; |
struct soap_cookie { char *name; char *value; char *domain; char *path; long expire; /* client-side: local time to expire; server-side: seconds to expire */ unsigned int version; short secure; short session; /* server-side */ short env; /* server-side: got cookie from client */ short modified; /* server-side: client cookie was modified */ struct soap_cookie *next; }; |
Server-side cookie support is optional. To enable cookie support, compile with option -DWITH_COOKIES, for example:
g++ -DWITH_COOKIES -o myserver ... |
|
|
The following example server adopts cookies for session control:
int main() { struct soap soap; int m, s; soap_init(&soap); soap.cookie_domain = "..."; soap.cookie_path = "/"; // the path which is used to filter/set cookies with this destination if (argc < 2) { soap_getenv_cookies(&soap); // CGI app: grab cookies from 'HTTP_COOKIE' env var soap_serve(&soap); } else { m = soap_bind(&soap, NULL, atoi(argv[1]), 100); if (m < 0) exit(-1); for (int i = 1; ; i++) { s = soap_accept(&soap); if (s < 0) exit(-1); soap_serve(&soap); soap_end(&soap); // clean up soap_free_cookies(&soap); // remove all old cookies from database so no interference occurs with the arrival of new cookies } } return 0; } int ck__demo(struct soap *soap, ...) { int n; const char *s; s = soap_cookie_value(soap, "demo", NULL, NULL); // cookie returned by client? if (!s) s = "init-value"; // no: set initial cookie value else ... // modify 's' to reflect session control soap_set_cookie(soap, "demo", s, NULL, NULL); soap_set_cookie_expire(soap, "demo", 5, NULL, NULL); // cookie may expire at client-side in 5 seconds return SOAP_OK; } |
When a client needs to connect to a Web Service through a proxy server, set the soap.proxy_host string and
soap.proxy_port integer attributes of the current soap runtime environment to the proxy's host name and port, respectively. For example:
struct soap soap; soap_init(&soap); soap.proxy_host = "proxyhostname"; soap.proxy_port = 8080; if (soap_call_ns__method(&soap, "http://host:port/path", "action", ...)) soap_print_fault(&soap, stderr); else ... |
To enable FastCGI support, install FastCGI and compile with option -DWITH_FASTCGI or add
#define WITH_FASTCGI |