The gSOAP compiler tools provide a SOAP/XML-to-C/C++ language binding to ease the development of SOAP/XML Web services and client application in C and/or C++. Most toolkits for C++ Web services adopt a SOAP-centric view and offer APIs for C++ that require the use of class libraries for SOAP-specific 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 XML 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, thus enabling him or her to concentrate on the application-essential logic. The compiler enables the integration 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 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 parser and importer "closes the circle" in that it enables client development without the need for users to analyze Web service details to implement a client or a server.
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.
To comply with WS-I Basic Profile 1.0a, gSOAP 2.5 and higher adopts SOAP RPC literal by default. There is no need for concern, because the WSDL parser wsdl2h automatically takes care of the differences when you provide a WSDL document, because SOAP RPC encoding, literal, and document style are supported. A new soapcpp2 compiler option was added -e for backward compatibility with gSOAP 2.4 and earlier to adopt SOAP RPC encoding by default in case you want to develop a service that uses SOAP encoding. You can also use the gSOAP compiler directives to specify SOAP encoding for individual operarations, when desired.
You should read this section only if you are upgrading from gSOAP 2.1 to 2.2 and later.
Run-time options and flags have been changed to enable separate recv/send settings for transport, content encodings, and mappings. The flags are divided into four classes: transport (IO), content encoding (ENC), XML marshalling (XML), and C/C++ data mapping (C). The old-style flags soap_disable_X and soap_enable_X, where X is a particular feature, are deprecated. See Section 9.12 for more details.
You should read this section only if you are upgrading from gSOAP 1.X to 2.X.
gSOAP versions 2.0 and later have been rewritten based on versions 1.X.
gSOAP 2.0 and later is thread-safe, while 1.X is not.
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.
Three 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_destroy(&soap); // remove deserialized class instances (C++ only) soap_end(&soap); // clean up and remove deserialized data soap_done(&soap); // detach environment (last use and no longer in scope) ... } |
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_destroy(soap); // remove deserialized class instances (C++ only) soap_end(soap); // clean up and remove deserialized data soap_done(soap); // detach runtime environment ... free(soap); // deallocate runtime environment } |
int main() { struct soap soap; soap_init(&soap); soap_serve(&soap); } |
int main() { soap_serve(soap_new()); } |
A service can use multi-threading to handle requests while running some other code that invokes remote methods:
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, NULL); // wait for thread to terminate soap_end(&soap1); // release its data } |
Section 8.2.4 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:
To start building Web services applications with gSOAP, you need:
Although gSOAP is available in binary format for several platforms, the code generated by the gSOAP stub and skeleton compiler and the gSOAP runtime codes are equivalent. This means that the generated codes can be transferred to other platforms and compiled.
The platform-independent release of gSOAP requires you to build the 'soapcpp2' compiler and 'wsdl2h' parser. The minimal requirements for your platform to build these executables is:
The gSOAP packages contain numerous examples in the 'samples' directory. Run 'make' to build the example applications. The examples are also meant to demonstrate different features of gSOAP. The simplest examples are the one-liners (samples/oneliners). Indeed, you can write a one-line Web service (requires CGI). A streaming DIME attachment server and client application demonstrate efficient file exchanges (samples/dime). An SSL-secure Web server application demonstrates the generation of dynamic content for Web browsing and Web services functionality at the same time. And much more.
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 compiler without a detailed understanding of the SOAP protocol when gSOAP client-server applications are built 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 gSOAP Web services and clients that connect to and interoperate with other SOAP implementations such as Apache Axis, SOAP::Lite, and .NET. This requires some details of the SOAP and WSDL protocols 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 parameter data, send the request with the parameters to the designated SOAP service over the wire, to wait for the response, and to demarshall the parameter data of the response 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 parameters of a remote method contain elaborate data structures such as records, arrays, and graphs. Fortunately, the gSOAP 'wsdl2h' WSDL parser and 'soapcpp2' stub and skeleton compiler automate the development of Web service client and server applications.
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 parser.
Consider the following command (entered at the command prompt):
$ wsdl2h -o quote.h http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl |
To generate a header file to develop a pure C client application, issue the command:
Consider the following command (entered at the command prompt):
$ wsdl2h -c -o quote.h http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl |
The quote.h header file is then processed by the gSOAP compiler to generate the stubs to develop client applications (and skeletons to develop a service).
The SOAP service methods are specified in the 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 service method may be simple data types or compound data types, either generated by the WSDL parser or specified by hand. 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 in XML.
The getQuote remote method of XMethods Delayed Stock Quote service (defined in the quote.h file obtained with the 'wsdl2h' WSDL parser)
provides a delayed stock quote for a given ticker name.
The WSDL description of the XMethods Delayed Stock Quote service provides the following details:
|
//gsoap ns1 service name: net_DOTxmethods_DOTservices_DOTstockquote_DOTStockQuoteBinding //gsoap ns1 service type: net_DOTxmethods_DOTservices_DOTstockquote_DOTStockQuotePortType //gsoap ns1 service port: http://66.28.98.121:9090/soap //gsoap ns1 service namespace: urn:xmethods-delayed-quotes //gsoap ns1 service documentation: Definitions generated by the gSOAP WSDL parser 1.0 // Service net.xmethods.services.stockquote.StockQuoteService : net.xmethods.services.stockquote.StockQuote web service //gsoap ns1 service method-style: getQuote rpc //gsoap ns1 service method-encoding: getQuote http://schemas.xmlsoap.org/soap/encoding/ //gsoap ns1 service method-action: getQuote urn:xmethods-delayed-quotes#getQuote 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 10.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 8.1.2. Basically, a namespace prefix is distinguished 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 into a dash in XML, because dashes are more frequently used in XML compared to underscores, see Section 10.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 function 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. Note that the XMethods Delayed Stock Quote service endpoint URL is http://66.28.98.121:9090/soap and the SOAP action required is "" (two quotes). You can change the endpoint and action dynamically. The endpoint and action are the second and third parameters of the soap_call_ns1__getQuote. When NULL, the values specified in the header file will be used.
The following example mixed C/C++ client program invokes the stub to retrieve the latest IBM stock quote from the XMethods Delayed Stock
Quote service:
#include "soapH.h" // obtain the generated stub #include "net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding.nsmap" // obtain the namespace mapping table int main() { struct soap soap; // gSOAP runtime environment float quote; soap_init(&soap); // initialize runtime environment (only once) if (soap_call_ns1__getQuote(&soap, NULL, NULL, "IBM", "e) == SOAP_OK) std::cout << "Current IBM Stock Quote = " << quote << std::endl; else // an error occurred soap_print_fault(&soap, stderr); // display the SOAP fault message on the stderr stream soap_destroy(&soap); // delete deserialized class instances (for C++ only) soap_end(&soap); // remove deserialized data and clean up soap_done(&soap); // detach the gSOAP environment return 0; } |
The gSOAP compiler also generates a proxy class for C++ client applications. This generated proxy class can be included into a client application together with the generated namespace table as shown in this example:
#include "soapnet_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBindingProxy.h" // get proxy #include "net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding.nsmap" // obtain the namespace mapping table int main() { net q; // "net" is the proxy class with a name that is the short name of the service float r; if (q.ns1__getQuote("IBM", r) == SOAP_OK) std::cout << r << std::endl; else soap_print_fault(q.soap, stderr); return 0; } |
//gsoap ns1 service name: net_DOT_xmethods_DOT_services_DOT_stockquote_DOT_StockQuoteBinding |
The following functions can be used to explicitly setup a gSOAP runtime environment (struct soap):
|
When the example client application is invoked, the SOAP request is performed by the stub routine soap_call_ns1__getQuote, which
generates the following SOAP RPC 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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:xmethods-delayed-quotes" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getQuote> <symbol>IBM</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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/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", "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], "es[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 distinguished 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 bound to the namespace name urn:xmethods-delayed-quotes through the xmlns:n="urn:xmethods-delayed-quotes binding. The use of namespace prefixes and namespace names is also required to enable SOAP applications to validate the content of SOAP messages. The namespace name in the service response is verified by the stub routine by using the information supplied in a namespace mapping table that is required to be part of gSOAP client and service application codes. The table is accessed at run time to resolve namespace bindings, both by the generated stub's data structure serializer for encoding the client request and by the generated stub's data structure deserializer to decode and validate the service response. The namespace mapping table should not be part of the header file input to the gSOAP stub and skeleton compiler. Service details including namespace bindings may be provided with gSOAP directives in a header file, see Section 19.2.
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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/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 provided by separate Web services and/or organizations. 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 distinguished by their namespaces. Consequently, two different namespace prefixes had to be 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"}, ... |
Proxy classes for C++ client applications are automatically generated by the gSOAP compiler.
To illustrate the generation of a proxy class, the getQuote.h header file example of the previous section is augmented with the appropriate directives to enable the gSOAP compiler to
generate the proxy class. Similar directives are included in the header file by the WSDL importer.
// Content of file "getQuote.h": //gsoap ns1 service name: Quote //gsoap ns1 service location: http://services.xmethods.net/soap //gsoap ns1 service namespace: urn:xmethods-delayed-quotes //gsoap ns1 service style: rpc //gsoap ns1 service encoding: encoded //gsoap ns1 service method-action: getQuote "" int ns1__getQuote(char *symbol, float &Result); |
#include "soapH.h" class Quote { public: struct soap *soap; const char *endpoint; Quote() { soap = soap_new(); endpoint = "http://services.xmethods.net/soap"; }; ~Quote() { if (soap) { soap_destroy(soap); soap_end(soap); soap_done(soap); free((void*)soap); }}; int getQuote(char *symbol, float &Result) { return soap ? soap_call_ns1__getQuote(soap, endpoint, "", symbol, Result) : SOAP_EOM; }; }; |
This generated proxy class can be included into a client application together with the generated namespace table as shown in this example:
#include "soapQuoteProxy.h" // get proxy #include "Quote.nsmap" // get namespace bindings int main() { Quote q; float r; if (q.ns1__getQuote("IBM", r) == SOAP_OK) std::cout << r << std::endl; else soap_print_fault(q.soap, stderr); return 0; } |
You can use soapcpp2 compiler option -n together with -p to create a local namespaces table to avoid link conflicts when you need multiple namespace tables or need to combine multiple clients, see also Sections 9.1 and 19.34, and you can use a C++ code namespace to create a namespace qualified proxy class, see Section 19.33.
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 compiler, assumes SOAP RPC encoding which only requires the use of types to handle polymorphic cases.
Nevertheless, the use of XSD typed messages is advised to improve interoperability.
XSD types are introduced with typedef definitions in
the header file input to the gSOAP compiler. The type name defined by a typedef definition corresponds to an XML Schema
type (XSD type). For example, the following typedef declarations
define various built-in XSD types implemented as primitive C/C++ types:
// Contents of header file: ... typedef char *xsd__string; // encode xsd__string value as the xsd:string schema type typedef char *xsd__anyURI; // encode xsd__anyURI value as the xsd:anyURI schema type typedef float xsd__float; // encode xsd__float value as the xsd:float schema type typedef long xsd__int; // encode xsd__int value as the xsd:int schema type typedef bool xsd__boolean; // encode xsd__boolean value as the xsd:boolean schema type typedef unsigned long long xsd__positiveInteger; // encode xsd__positiveInteger value as the xsd:positiveInteger schema type ... |
Reconsider the getQuote example, now rewritten with explicit XSD 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 an xsd:string:
... <SOAP-ENV:Body> <ns1:getQuote><symbol xsi:type="xsd:string">IBM</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 standardized convention for the response element name in a SOAP response message, 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">IBM</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 last parameter of the function prototype declaration of a remove method in a header file is also the only single output parameter of the method. 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 8.1.7. 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 8.1.13.
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:
// Content 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 single 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 8.1.7.
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.
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 gSOAP compiler generates the soap_call_ns1__getFlightInfo proxy. Here is an example fragment of a client application that uses this proxy to request flight information:
struct soap soap; ... soap_init(&soap); ... soap_call_ns1__getFlightInfo(&soap, "testvger.objectspace.com/soap/servlet/rpcrouter", "urn:galdemo:flighttracker", "UAL", "184", r); ... struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC","http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns1", "urn:galdemo:flighttracker"}, {"ns2", "http://galdemo.flighttracker.com"}, {NULL, NULL} }; |
POST /soap/servlet/rpcrouter HTTP/1.1 Host: testvger.objectspace.com Content-Type: text/xml Content-Length: 634 SOAPAction: "urn:galdemo:flighttracker" <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="urn:galdemo:flighttracker" xmlns:ns2="http://galdemo.flighttracker.com" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getFlightInfo xsi:type="ns1:getFlightInfo"> <param1 xsi:type="xsd:string">UAL</param1> <param2 xsi:type="xsd:string">184</param2> </ns1:getFlightInfo> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
HTTP/1.1 200 ok Date: Thu, 30 Aug 2001 00:34:17 GMT Server: IBM_HTTP_Server/1.3.12.3 Apache/1.3.12 (Win32) Set-Cookie: sesessionid=2GFVTOGC30D0LGRGU2L4HFA;Path=/ Cache-Control: no-cache="set-cookie,set-cookie2" Expires: Thu, 01 Dec 1994 16:00:00 GMT Content-Length: 861 Content-Type: text/xml; charset=utf-8 Content-Language: en <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <SOAP-ENV:Body> <ns1:getFlightInfoResponse xmlns:ns1="urn:galdemo:flighttracker" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xmlns:ns2="http://galdemo.flighttracker.com" xsi:type="ns2:FlightInfo"> <equipment xsi:type="xsd:string">A320</equipment> <airline xsi:type="xsd:string">UAL</airline> <currentLocation xsi:type="xsd:string">188 mi W of Lincoln, NE</currentLocation> <altitude xsi:type="xsd:string">37000</altitude> <speed xsi:type="xsd:string">497</speed> <flightNumber xsi:type="xsd:string">184</flightNumber> </return> </ns1:getFlightInfoResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
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 strictly 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. Parameter names are implicitly
anonymous by omitting the parameter names in the function prototype of the
remote method. For
example:
// Contents of "getQuote.h": typedef char *xsd__string; typedef float xsd__float; int ns1__getQuote(xsd__string, xsd__float&); |
For example:
// Contents of "getQuote.h": typedef char *xsd__string; typedef float xsd__float; int ns1__getQuote(xsd__string symbol, xsd__float &_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.
To specify a remote method that has no output parameters, just provide a function prototype with a response struct that is
empty. For example:
enum ns__event { off, on, stand_by }; int ns__signal(enum ns__event in, struct ns__signalResponse { } *out); |
Some SOAP resources refer to SOAP RPC with empty responses as one way SOAP messaging. However, we refer to one-way massaging by asynchronous explicit send and receive operations as described in Section 8.3. The latter view of one-way SOAP messaging is also in line with Basic Profile 1.0.
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 return soap_receiver_fault(soap, "Square root of negative number", "I can only take the square root of a non-negative number"); } // As always, a namespace mapping table is needed: struct Namespace namespaces[] = { // {"ns-prefix", "ns-name"} {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:simple-calc"}, // bind "ns" namespace prefix {NULL, NULL} }; |
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 stub routine 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. gSOAP services can also run as stand-alone services on any port by utilizing the built-in HTTP and TCP/IP stacks. The stand-alone services can be run on port 80 thereby providing Web server capabilities restricted to SOAP RPC.
To create a stand-alone service, only the main routine of the service needs to be modified as follows. Instead of just calling the
soap_serve routine, the main routine is changed into:
int main() { struct soap soap; int m, s; // master and slave sockets soap_init(&soap); m = soap_bind(&soap, "machine.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); if (soap_serve(&soap) != SOAP_OK) // process RPC request soap_print_fault(&soap, stderr); // print error fprintf(stderr, "request served\n"); soap_destroy(&soap); // clean up class instances soap_end(&soap); // clean up everything and close socket } } soap_done(&soap); // close master socket and detach environment } |
The gSOAP functions that can be used are:
|
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 19.17 for more details on timeout management.
See Section 9.13 for more details on memory management.
A client application connects to this stand-alone service with the endpoint machine.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 or when keep-alive is enabled, see Section 19.11. In case of long response times, the latencies introduced by the unrelated requests may become prohibitive for a successful deployment of a stand-alone service. When HTTP keep-alive is enabled, a client may not close the socket on time, thereby preventing other clients from connecting.
gSOAP 2.0 and higher is thread safe and supports the implementation of multi-threaded stand-alone services in which a thread is used to handle a request.
The following example illustrates the use of threads to improve the quality of service by handling new requests in separate threads:
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { soap.send_timeout = 60; // 60 seconds soap.recv_timeout = 60; // 60 seconds soap.accept_timeout = 3600; // server stops after 1 hour of inactivity soap.max_keep_alive = 100; // max keep-alive sequence void *process_request(void*); struct soap *tsoap; pthread_t tid; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); for (;;) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); exit(1); } fprintf(stderr, "server timed out\n"); break; } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); tsoap = soap_copy(&soap); // make a safe copy if (!tsoap) break; pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap); } } soap_done(&soap); // detach soap struct return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); // dealloc C++ data soap_end((struct soap*)soap); // dealloc data and clean up soap_done((struct soap*)soap); // detach soap struct free(soap); return NULL; } |
The soap_serve dispatcher handles one request or multiple requests when HTTP keep-alive is set with SOAP_IO_KEEPALIVE. The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed, which is important to avoid a client from holding a thread indefinitely. The send and receive timeouts are set to avoid (intentionally) slow clients from holding a socket connection too long. The accept timeout is used to let the server terminate automatically after a period of inactivity.
The following example uses a pool of servers to limit the machine's resource utilization:
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog #define MAX_THR (10) // Max. threads to serve requests int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { struct soap *soap_thr[MAX_THR]; // each thread needs a runtime environment pthread_t tid[MAX_THR]; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; int i; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); for (i = 0; i < MAX_THR; i++) soap_thr[i] = NULL; for (;;) { for (i = 0; i < MAX_THR; i++) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); continue; // retry } else { fprintf(stderr, "Server timed out\n"); break; } } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); if (!soap_thr[i]) // first time around { soap_thr[i] = soap_copy(&soap); if (!soap_thr[i]) exit(1); // could not allocate } else// recycle soap environment { pthread_join(tid[i], NULL); fprintf(stderr, "Thread %d completed\n", i); soap_destroy(soap_thr[i]); // deallocate C++ data of old thread soap_end(soap_thr[i]); // deallocate data of old thread } soap_thr[i]->socket = s; // new socket fd pthread_create(&tid[i], NULL, (void*(*)(void*))soap_serve, (void*)soap_thr[i]); } } for (i = 0; i < MAX_THR; i++) if (soap_thr[i]) { soap_done(soap_thr[i]); // detach context free(soap_thr[i]); // free up } } return 0; } |
|
For clean termination of the server, the master socket can be closed and callbacks removed with soap_done(struct soap *soap).
The advantage of the code shown above is that the machine cannot be overloaded with requests, since the number of active services is limited. However, threads are still started and terminated. This overhead can be eliminated using a queue of requests (open sockets) as is shown in the code below.
#include "soapH.h" #include < pthread.h > #define BACKLOG (100) // Max. request backlog #define MAX_THR (10) // Size of thread pool #define MAX_QUEUE (1000) // Max. size of request queue SOAP_SOCKET queue[MAX_QUEUE]; // The global request queue of sockets int head = 0, tail = 0; // Queue head and tail void *process_queue(void*); int enqueue(SOAP_SOCKET); SOAP_SOCKET dequeue(); pthread_mutex_t queue_cs; pthread_cond_t queue_cv; int main(int argc, char **argv) { struct soap soap; soap_init(&soap); if (argc < 2) // no args: assume this is a CGI application { soap_serve(&soap); // serve request, one thread, CGI style soap_destroy(&soap); // dealloc C++ data soap_end(&soap); // dealloc data and clean up } else { struct soap *soap_thr[MAX_THR]; // each thread needs a runtime environment pthread_t tid[MAX_THR]; int port = atoi(argv[1]); // first command-line arg is port SOAP_SOCKET m, s; int i; m = soap_bind(&soap, NULL, port, BACKLOG); if (!soap_valid_socket(m)) exit(1); fprintf(stderr, "Socket connection successful %d\n", m); pthread_mutex_init(&queue_cs, NULL); pthread_cond_init(&queue_cv, NULL); for (i = 0; i < MAX_THR; i++) { soap_thr[i] = soap_copy(&soap); fprintf(stderr, "Starting thread %d\n", i); pthread_create(&tid[i], NULL, (void*(*)(void*))process_queue, (void*)soap_thr[i]); } for (;;) { s = soap_accept(&soap); if (!soap_valid_socket(s)) { if (soap.errnum) { soap_print_fault(&soap, stderr); continue; // retry } else { fprintf(stderr, "Server timed out\n"); break; } } fprintf(stderr, "Thread %d accepts socket %d connection from IP %d.%d.%d.%d\n", i, s, (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF); while (enqueue(s) == SOAP_EOM) sleep(1); } for (i = 0; i < MAX_THR; i++) { while (enqueue(SOAP_INVALID_SOCKET) == SOAP_EOM) sleep(1); } for (i = 0; i < MAX_THR; i++) { fprintf(stderr, "Waiting for thread %d to terminate... ", i); pthread_join(tid[i], NULL); fprintf(stderr, "terminated\n"); soap_done(soap_thr[i]); free(soap_thr[i]); } pthread_mutex_destroy(&queue_cs); pthread_cond_destroy(&queue_cv); } soap_done(&soap); return 0; } void *process_queue(void *soap) { struct soap *tsoap = (struct soap*)soap; for (;;) { tsoap->socket = dequeue(); if (!soap_valid_socket(tsoap->socket)) break; soap_serve(tsoap); soap_destroy(tsoap); soap_end(tsoap); fprintf(stderr, "served\n"); } return NULL; } int enqueue(SOAP_SOCKET sock) { int status = SOAP_OK; int next; pthread_mutex_lock(&queue_cs); next = tail + 1; if (next > = MAX_QUEUE) next = 0; if (next == head) status = SOAP_EOM; else { queue[tail] = sock; tail = next; } pthread_cond_signal(&queue_cv); pthread_mutex_unlock(&queue_cs); return status; } SOAP_SOCKET dequeue() { SOAP_SOCKET sock; pthread_mutex_lock(&queue_cs); pthread_cond_wait(&queue_cv, &queue_cs); sock = queue[head++]; if (head > = MAX_QUEUE) head = 0; pthread_mutex_unlock(&queue_cs); return sock; } |
The void *soap.user field can be used to pass application data to service methods. This field should be set before the soap_serve() call. The service method can access this field to use the application-dependent data. The following example shows how a non-static database handle is initialized and passed to the service methods:
{ ... struct soap soap; database_handle_type database_handle; soap_init(&soap); soap.user = (void*)database_handle; ... soap_serve(&soap); // call the remove method dispatcher to handle request ... } int ns__myMethod(struct soap *soap, ...) { ... fetch((database_handle_type*)soap->user); // get data ... return SOAP_OK; } |
The same client header file specification issues apply to the specification and implementation of a SOAP Web service. Refer to
Server object classes for C++ server applications are automatically generated by the gSOAP compiler.
We illustrate the generation of an object class with a calculator example.
// Content of file "calc.h": //gsoap ns service name: Calculator //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://www.cs.fsu.edu/~engelen/calc.cgi //gsoap ns schema namespace: urn:calc //gsoap ns service method-action: add "" int ns__add(double a, double b, double &result); int ns__sub(double a, double b, double &result); int ns__mul(double a, double b, double &result); int ns__div(double a, double b, double &result); |
#include "soapH.h" class Calculator : public soap { public: Quote() { soap_init(this); }; ~Quote() { soap_destroy(this); soap_end(this); soap_done(this); }}; int serve() { return soap_serve(this); }; }; |
#include "soapCalculatorObject.h" // get server object #include "Calculator.nsmap" // get namespace bindings int main() { Calculator c; return c.serve(); // calls soap_serve to serve as CGI application (using stdin/out) } int ns__add(double a, double b, double &result) { result = a + b; return SOAP_OK; } ... sub(), mul(), and div() implementations ... |
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.
In addition to the generation of the ns.wsdl file, a file with a namespace mapping table is generated by the gSOAP
compiler. An example mapping table is shown below:
struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance", \"http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", \"http://www.w3.org/*/XMLSchema"}, {"ns", "http://tempuri.org"}, {NULL, NULL} }; |
To deploy a Web service, copy the compiled CGI service application to the designated CGI directory of your Web server. Make sure the proper file permissions are set (chmod 755 calc.cgi for Unix/Linux). You can then publish the WSDL file on the Web by placing it in the appropriate Web server directory.
The gSOAP compiler also generates XML Schema files for all C/C++ complex types (e.g. structs and classes) when declared with a namespace prefix. These files are named ns.xsd, where ns is the namespace prefix used in the declaration of the complex type. The XML Schema files do not have to be published as the WSDL file already contains the appropriate XML Schema definitions.
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="http://location/Service.wsdl" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:WSDL="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:tns="http://location/Service.wsdl" xmlns:ns="http://tempuri.org"> <types> <schema xmlns="http://www.w3.org/2000/10/XMLSchema" targetNamespace="http://tempuri.org" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"> <complexType name="addResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> <complexType name="subResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> <complexType name="sqrtResponse"> <all> <element name="result" type="double" minOccurs="0" maxOccurs="1"/> </all> <anyAttribute namespace="##other"/> </complexType> </schema> </types> <message name="addRequest"> <part name="a" type="xsd:double"/> <part name="b" type="xsd:double"/> </message> <message name="addResponse"> <part name="result" type="xsd:double"/> </message> <message name="subRequest"> <part name="a" type="xsd:double"/> <part name="b" type="xsd:double"/> </message> <message name="subResponse"> <part name="result" type="xsd:double"/> </message> <message name="sqrtRequest"> <part name="a" type="xsd:double"/> </message> <message name="sqrtResponse"> <part name="result" type="xsd:double"/> </message> <portType name="ServicePortType"> <operation name="add"> <input message="tns:addRequest"/> <output message="tns:addResponse"/> </operation> <operation name="sub"> <input message="tns:subRequest"/> <output message="tns:subResponse"/> </operation> <operation name="sqrt"> <input message="tns:sqrtRequest"/> <output message="tns:sqrtResponse"/> </operation> </portType> <binding name="ServiceBinding" type="tns:ServicePortType"> <SOAP:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="add"> <SOAP:operation soapAction="http://tempuri.org#add"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sub"> <SOAP:operation soapAction="http://tempuri.org#sub"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> <operation name="sqrt"> <SOAP:operation soapAction="http://tempuri.org#sqrt"/> <input> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </input> <output> <SOAP:body use="encoded" namespace="http://tempuri.org" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/> </output> </operation> </binding> <service name="Service"> <port name="ServicePort" binding="tns:ServiceBinding"> <SOAP:address location="http://location/Service.cgi"/> </port> </service> </definitions> |
Note: see README.txt in the wsdl directory for installation instructions for the WSDL parser and importer.
The creation of SOAP Web Services applications from a WSDL service description is a two-step process.
First, execute wsdl2h file.wsdl which generates the a C++ header file file.h (use wsdl2h -c file.wsdl to generate pure C code). You can provide a URL instead of a file name, when applicable. The generated header file is a Web service specification that contains the parameter types and service function definitions. The functions are represented as function prototypes. The file contains various annotations related to the Web service. The header file must be processed by the gSOAP compiler. You cannot use it with a C/C++ compiler directly.
Second, the header file file.h is processed by the gSOAP compiler by executing soapcpp2 -i file.h. This creates the C/C++ source files to build a client application, see 8.1. In addition, this generates a client proxy object declared in soapServiceProxy.h, where Service is the name of the service defined in the WSDL. To use this object, include the soapServiceProxy.h and Service.nsmap files in your C++ client application. The Service class provides the remote Web service methods as class members.
Consider the following example commands (entered at the command prompt):
$ wsdl2h -o XMethodsQuery.h http://www.xmethods.net/wsdl/query.wsdl ... $ soapcpp2 -i XMethodsQuery.h |
When parsing a WSDL, the output file name is the WSDL input file name with extension .h instead of .wsdl. When an input file is absent or a WSDL file from a Web location is accessed, the header output will be produced on the standard output. Schema files (.xsd) can also be parsed and processed.
The wsdl2h command-line options are:
|
A typemap.dat file for wsdl2h contains custom XML Schema and C/C++ type bindings. An internal table is used by default.
An example typemap file is:
# This file contains custom definitions of the XML Schema types and # C/C++ types for your project, and XML namespace prefix definitions. # The wsdl2h WSDL importer consults this file to determine bindings. [ // This comment will be included in the generated .h file // You can include any additional declarations, includes, imports, etc. // within [ ] sections. The brackets MUST appear at the start of a line ] # XML namespace prefix definitions can be provided to override the # default choice of ns1, ns2, ... prefixes. For example: i = "http://www.soapinterop.org/" s = "http://www.soapinterop.org/xsd" # Type definitions are of the form: # type = declaration | use | pointer-use # where # type is the XML Schema type (or an application type in a namespace # that has a prefix definition given as above). # declaration is an optional C/C++ type declaration # use is how the type is referred to in code # pointer-use is how the type should be referred to as a pointer (opt) # Example XML Schema and C/C++ type bindings: xsd__int = | int xsd__string = | char* | char* xsd__boolean = enum xsd__boolean false_, true_ ; | enum xsd__boolean xsd__base64Binary = class xsd__base64Binary unsigned char *__ptr; int __size; ; | xsd__base64Binary | xsd__base64Binary # You can extend structs and classes with member data and functions. # For example, adding a constructor to ns__myClass: ns__myClass = $ ns__myClass(); # The general form is # class_name = $ member; |
A gSOAP service may make client calls to other services from within its remove methods. This is best illustrated with an example. The following example is a more sophisticated example that combines the functionality of two Web services into one new SOAP Web service. The service provides a currency-converted stock quote. To serve a request, the service in turn requests the stock quote and the currency-exchange rate from two XMethods services.
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(&soap); else if (soap_call_ns3__getQuote(&soap, "http://www.cs.fsu.edu/~engelen/quotex.cgi", "", argv[1], argv[2], q)) soap_print_fault(&soap, stderr); else printf("\nCompany %s: %f (%s)\n", argv[1], q, argv[2]); return 0; } int ns3__getQuote(struct soap *soap, char *symbol, char *country, float &result) { float q, r; int socket = soap->socket; // save socket (stand-alone service only, does not support keep-alive) if (soap_call_ns1__getQuote(soap, "http://services.xmethods.net/soap", "", symbol, &q) == 0 && soap_call_ns2__getRate(soap, "http://services.xmethods.net/soap", NULL, "us", country, &r) == 0) { result = q*r; soap->socket = socket; return SOAP_OK; } soap->socket = socket; return SOAP_FAULT; // pass soap fault messages on to the client of this app } /* Since this app is a combined client-server, it is put together with * one header file that describes all 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(struct soap *soap, char *symbol, float &result) { return SOAP_NO_METHOD; } // dummy: will never be called int ns2__getRate(struct soap *soap, char *country1, char *country2, float &result) { return SOAP_NO_METHOD; } // dummy: will never be called struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema"}, {"ns1", "urn:xmethods-delayed-quotes"}, {"ns2", "urn:xmethods-CurrencyExchange"}, {"ns3", "urn:quotex"}, {NULL, NULL} }; |
soapcpp2 quotex.h g++ -o quotex.cgi quotex.cpp soapC.cpp soapClient.cpp soapServer.cpp stdsoap2.cpp -lsocket -lxnet -lnsl |
The quotex.cgi executable is installed as a CGI application on the Web by
copying it in the designated directory specific to your Web server. After
this, the executable can also serve to test the service. For example
quotex.cgi IBM uk |
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).
SOAP RPC client-server interaction is synchronous: the client blocks until the server responds to the request. gSOAP also supports asynchronous one-way message passing and the interoperable synchronous one-way message passing over HTTP. The two styles are similar, but only the latter is interoperable and is compliant to Basic Profile 1.0. The interoperable synchronous one-way message passing style over HTTP is discussed in Section 8.4 below.
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; } |
One-way SOAP message passing over HTTP as defined by the SOAP specification and Basic Profile 1.0 is synchrounous, meaning that the server must respond with an HTTP OK header and an empty body. To implement synchrounous one-way messaging, the same setup for asynchrounous one-way messaing discussed in Section 8.3 is used, but with one simple addition at the client and server side.
At the server side, we need to return an empty HTTP OK response. This is accomplished as follows. For each one-way operation implemented in C/C++, we replace the return SOAP_OK with:
int ns__event(struct soap *soap, int eventNo) { ... // handle event return soap_send_empty_response(soap); } |
if (soap_send_ns__event(soap, eventNo) != SOAP_OK || soap_recv_empty_response(soap) != SOAP_OK) soap_print_fault(soap, stderr); ... |
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 or load data stored in XML format.
The following attributes can be set to control the destination and source for serialization and deserialization:
|
|
See also Section 9.12 to control the I/O buffering and content encoding such as compression and DIME encoding.
To serialize a data type to a stream, two functions should be called to prepare for serialization of the data and to send the data, respectively. The first function, soap_serialize, analyzes pointers and determines if multi-references are required to encode the data and if cycles are present the object graph. The second function, soap_put, produces the XML output on a stream.
The soap_serialize and soap_put 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 by gSOAP:
|
struct ns__Person { char *name; } *p; |
soap_serialize_PointerTons__Person(&soap, &p); |
soap_begin_send(&soap); soap_put_PointerTons__Person(&soap, &p, "ns:element-name", "ns:type-name"); soap_end_send(&soap); |
<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> |
You can assign an
output stream to soap.os or a file descriptor to soap.sendfd.
For example
soap.sendfd = open(file, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); soap_serialize_PointerTons__Person(&soap, &p); soap_begin_send(&soap); soap_put_PointerTons__Person(&soap, &p, "ns:element-name", "ns:type-name"); soap_end_send(&soap); |
To save the data as an XML tree (with one root) without any id-ref attributes, use the SOAP_XML_TREE flag. The data structure MUST NOT contain pointer-based cycles.
To preserve the exact structure of the data object graph and create XML with one root, use the SOAP_XML_GRAPH output-mode flag (see Section 9.12). Use this flag and the soap_serialize function to prepare the serialization of data with in-line id-ref attributes. Using the SOAP_XML_GRAPH flag assures the preservation of the logical structure of the data
For example, to encode the contents of two variables var1 and var2 that may
share data throug pointer structures,
the serializers are called before the output routines:
T1 var1; T2 var2; struct soap soap; ... soap_init(&soap); // initialize [soap_omode(&soap, flags);] // set output-mode flags (e.g. SOAP_ENC_XML|SOAP_ENC_ZLIB) soap_begin(&soap); // start new (de)serialization phase soap_set_omode(&soap, SOAP_XML_GRAPH); soap_serialize_Type1(&soap, &var1); soap_serialize_Type2(&soap, &var2); ... [soap.socket = a_socket_file_descriptor;] // when using sockets [soap.os = an_output_stream;] // C++ [soap.sendfd = an_output_file_descriptor;] // C soap_begin_send(&soap); soap_put_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); soap_put_Type2(&soap, &var2, "[namespace-prefix:]element-name2", "[namespace-prefix:]type-name2"); ... soap_end_send(&soap); // flush soap_end(&soap); // remove temporary data structures after phase soap_done(&soap); // finalize last use of this environment ... |
For serializing class instances, method invocations MUST be used instead of function calls, for example obj.soap_serialize(&soap) and obj.soap_put(&soap, "elt", "type"). This ensures that the proper serializers are used for serializing instances of derived classes.
You can serialize a class instance to a stream as follows:
struct soap soap; myClass obj; soap_init(&soap); // initialize soap_begin(&soap); // start new (de)serialization phase soap_set_omode(&soap, SOAP_XML_GRAPH); obj.serialize(&soap); soap.os = cout; // send to cout soap_begin_send(&soap); obj.put(&soap, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1"); ... soap_end_send(&soap); // flush soap_end(&soap); // remove temporary data structures after phase soap_done(&soap); // finalize last use of this environment |
ostream &operator<<(ostream &o, const myClass &e) { if (!e.soap) ... error: need a soap struct to serialize (could use global struct) ... else { ostream *os = e.soap->os; e.soap->os = &o; soap_set_omode(e.soap, SOAP_XML_GRAPH); e.serialize(e.soap); soap_begin_send(e.soap); e.put(e.soap, "myClass", NULL); soap_end_send(e.soap); e.soap->os = os; soap_clr_omode(e.soap, SOAP_XML_GRAPH); } return o; } |
In principle, XML output for a data structure can be produced with soap_put without calling the soap_serialize function first. In this case, the result is similar to SOAP_XML_TREE which means that no id-refs are output. Cycles in the data structure will crash the serialization algorithm, even when the SOAP_XML_GRAPH is set.
Consider the following struct:
// Contents of file "tricky.h": struct Tricky { int *p; int n; int *q; }; |
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 |
The resulting output is:
<Tricky xsi:type="Tricky"> <p href="#2"/> <n xsi:type="int">1</n> <q href="#2"/> <r xsi:type="int">2</r> </Tricky> <id id="2" xsi:type="int">1</id> |
With the SOAP_XML_GRAPH flag the output is:
<Tricky xsi:type="Tricky"> <p href="#2"/> <n id="2" xsi:type="int">1</n> <q href="#2"/> </Tricky> |
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_imode(&soap, flags);] // set input-mode flags soap_begin(&soap); // begin new decoding phase [soap.is = an_input_stream;] // C++ [soap.recvfd = an_input_file_desriptpr;] // C soap_begin_recv(&soap); // if HTTP/MIME/DIME/GZIP headers are present, parse them if (!soap_get_Type1(&soap, &var1, "[namespace-prefix:]element-name1", "[namespace-prefix:]type-name1")) ... error ... if (!soap_get_Type2(&soap, &var2, "[namespace-prefix:]element-name2", "[namespace-prefix:]type-name1")) ... error ... ... soap_end_recv(&soap); // check consistency of id/hrefs soap_destroy(&soap); // remove deserialized class instances soap_end(&soap); // remove temporary data, including the decoded data on the heap soap_done(&soap); // finalize last use of the environment |
The soap_begin call resets the deserializers. The soap_destroy and soap_end calls remove the temporary data structures and the decoded data that was placed on the heap.
To remove temporary data while retaining the deserialized data on the heap, the function soap_free should be called instead of soap_destroy and soap_end.
One call to the soap_get_Type function of a type Type scans the entire input to process its XML content and to capture SOAP 1.1 independent elements (which contain multi-referenced objects). As a result, soap.error will set to SOAP_EOF. Also storing multiple objects into one file will fail to decode them properly with multiple soap_get calls. A well-formed XML document should only have one root anyway, so don't save multiple objects into one file. If you must save multiple objects, create a linked list or an array of objects and save the linked list or array. You could use the soap_in_Type function instead of the soap_get_Type function. The soap_in_Type function parses one XML element at a time.
You can deserialize class instances from a stream as follows:
myClass obj; struct soap soap; soap_init(&soap); // initialize soap_begin(&soap); // begin new decoding phase soap.is = cin; // read from cin soap_begin_recv(&soap); // if HTTP header is present, parse it if (!obj.get(&soap, "myClass", NULL) ... error ... soap_end_recv(&soap); // check consistency of id/hrefs ... soap_destroy(&soap); // remove deserialized class instances soap_end(&soap); // remove temporary data, including the decoded data on the heap soap_done(&soap); // finalize last use of the environment |
istream &operator>>(istream &i, myClass &e) { if (!e.soap) ... error: need soap struct to deserialize (could use global struct)... istream *is = e.soap->is; e.soap->is = &i; if (soap_begin_recv(e.soap) || e.in(e.soap, NULL, NULL) || soap_end_recv(e.soap)) ... error ... e.soap->is = is; return i; } |
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; mother.name = "Mary"; mother.gender = female; mother.address.street = "Downing st."; mother.address.number = 10; mother.address.city = "London"; mother.mother = NULL; mother.father = NULL; father.name = "Stuart"; father.gender = male; father.address.street = "Main st."; father.address.number = 5; father.address.city = "London"; father.mother = NULL; father.father = NULL; john.name = "John"; john.gender = male; john.address = mother.address; john.mother = &mother; john.father = &father; soap_init(&soap); soap_omode(&soap, SOAP_ENC_ZLIB | SOAP_XML_GRAPH); // see 9.12 soap_begin(&soap); soap_begin_send(&soap); john.soap_serialize(&soap); john.soap_put(&soap, "johnnie", NULL); soap_end_send(&soap); soap_end(&soap); soap_done(&soap); } struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"}, {"SOAP-ENC","http://schemas.xmlsoap.org/soap/encoding/"}, {"xsi", "http://www.w3.org/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the ``Person'' data type {NULL, NULL} }; |
soapcpp2 person.h g++ -DWITH_GZIP -o person person.cpp soapC.cpp stdsoap2.cpp -lsocket -lxnet -lnsl -lz |
Running the person application results in the compressed XML output:
<johnnie xsi:type="ns:Person" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:person" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <name xsi:type="xsd:Name">John</name> <gender xsi:type="ns:Gender">male</gender> <address xsi:type="ns:Address"> <street id="3" xsi:type="xsd:string">Dowling st.</street> <number xsi:type="unsignedInt">10</number> <city id="4" xsi:type="xsd:string">London</city> </address> <mother xsi:type="ns:Person"> <name xsi:type="xsd:Name">Mary</name> <gender xsi:type="ns:Gender">female</gender> <address xsi:type="ns:Address"> <street href="#3"/> <number xsi:type="unsignedInt">5</number> <city href="#4"/> </address> </mother> <father xsi:type="ns:Person"> <name xsi:type="xsd:Name">Stuart</name> <gender xsi:type="ns:Gender">male</gender> <address xsi:type="ns:Address"> <street xsi:type="xsd:string">Main st.</street> <number xsi:type="unsignedInt">13</number> <city href="#4"/> </address> </father> </johnnie> |
#include "soapH.h" int main() { struct soap soap; ns__Person *mother, *father, *john = NULL; soap_init(&soap); soap_imode(&soap, SOAP_ENC_ZLIB); // optional: gzip is detected automatically soap_begin(&soap); soap_begin_recv(&soap); if (soap_get_ns__Person(&soap, john, "johnnie", NULL) == NULL) ... error ... mother = john->mother; father = john->father; ... soap_end_recv(&soap); 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/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns", "urn:person"}, // Namespace URI of the ``Person'' data type {NULL, NULL} }; |
john = soap_get_ns__Person(&soap, NULL, "johnnie", NULL); |
Alternatively, the XML 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_imode(&soap, SOAP_ENC_ZLIB); // optional soap_begin(&soap); soap_begin_recv(&soap); soap_default_ns__Person(&soap, &john); if (soap_get_ns__Person(&soap, &john, "johnnie", NULL) == NULL) ... error ... ... } struct Namespace namespaces[] = ... |
C++ applications can define appropriate stream operations on objects for (de)serialization of objects on streams.
This is best illustrated with an example.
Consider the class
class ns__person { public: char *name; struct soap *soap; // we need this, see below ns__person(); ~ns__person(); }; |
soapcpp2 person.h |
#include "soapH.h" #include "ns.nsmap" ... struct soap *soap = soap_new(); ns__person *p = soap_new_ns__person(soap, -1); ... cout << p; // serialize p in XML ... in >> p; // parse XML and deserialize p ... soap_destroy(soap); // deletes p too soap_end(soap); soap_done(soap); |
ostream &operator<<(ostream &o, const ns__person &p) { if (!p->soap) return o; // need a gSOAP environment to serialize p.soap->os = &o; soap_omode(p.soap, SOAP_XML_GRAPH); // XML tree or graph p->soap_serialize(p.soap); soap_begin_send(p.soap); if (p->soap_put(p.soap, "person", NULL) | | soap_end_send(p.soap)) ; // handle I/O error return o; } istream &operator>>(istream &i, ns__person &p) { if (!p->soap) return o; // need a gSOAP environment to parse XML and deserialize p.soap->is = &i; if (soap_begin_recv(p.soap) | | p->soap_in(p.soap, NULL, NULL) | | soap_end_recv(p.soap)) ; // handle I/O error return i; } |
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 = "Unknown"; int value = 9999; enum Status { active, passive } status = passive; } |
Because method requests and responses are essentially structs, default values can also be assigned to method parameters. The
default parameter values do not control the parameterization of C/C++ function calls, i.e. all actual parameters must be present
when calling a function. The default parameter values are used in case an inbound request or response message lacks the XML
elements with parameter values. For example, a Web service can use default values to fill-in absent parameters in a
SOAP/XML request:
int ns__login(char *uid = "anonymous", char *pwd = "guest", bool granted); |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="http://tempuri.org"> <SOAP-ENV:Body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:login> </ns:login> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
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 soapClientLib.cpp and soapServerLib.cpp can be used to build (dynamic) client and server libraries. The serialization routines are local (static) to avoid link symbol conflicts. You must create a separate library for SOAP Header and Fault handling, as described in Section 19.34.
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 |
// Generate pure C and do not produce WSDL output: //gsoapopt cw int ns__myMethod(char*,char**); // takes a string and returns a string |
gSOAP supports SOAP 1.1 by default. SOAP 1.2 support is automatically turned on when the appropriate SOAP 1.2 namespace is used
in the namespace mapping table:
struct Namespace namespaces[] = { {"SOAP-ENV", "http://www.w3.org/2002/06/soap-envelope"}, {"SOAP-ENC", "http://www.w3.org/2002/06/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"}, ... } |
struct Namespace namespaces[] = { {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2002/06/soap-encoding"}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/2002/06/soap-envelope"}, {"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"}, ... } |
The gSOAP soapcpp2 compiler generates a .nsmap file with SOAP-ENV and SOAP-ENC namespace patterns similar to the above. Since clients issue a send first, they will always use SOAP 1.1 for requests when the namespace table is similar as shown above. Clients can accept SOAP 1.2 responses by inspecting the response message. To restrict gSOAP services and clients to SOAP 1.2 and to generate SOAP 1.2 service WSDLs, use soapcpp2 compiler option -2 to generate SOAP 1.2 conformant .nsmap and .wsdl files.
Caution: SOAP 1.2 does not support partially transmitted arrays. So the __offset field of a dynamic array is meaningless.
Caution: SOAP 1.2 requires the use of SOAP_ENV__Code, SOAP_ENV__Reason, and SOAP_ENV__Detail fields in a SOAP_ENV__Fault fault struct, while SOAP 1.1 uses faultcode, faultstring, and detail fields. Use soap_receiver_fault_subcode(struct soap *soap, const char *subcode, const char *faultstring, const char *detail) to set a SOAP 1.1/1.2 fault at the server-side with a fault subcode (SOAP 1.2). Use soap_sender_fault_subcode(struct soap *soap, const char *subcode, const char *faultstring, const char *detail) to set a SOAP 1.1/1.2 unrecoverable Bad Request fault at the server-side with a fault subcode (SOAP 1.2).
The soapdefs.h header file is included in stdsoap2.h when compiling with option -DWITH_SOAPDEFS_H:
g++ -DWITH_SOAPDEFS_H -c stdsoap2.cpp |
// Contents of soapdefs.h #include < ostream > #define SOAP_BUFLEN 20480 // use large send/recv buffer |
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 ... }; |
The #module directive is used to build modules. A library can be build from a module and linked with multiple Web services applications. The directive should appear at the top of the header file and has the following format:
#module "name" |
For example:
/* Contents of module.h */ #module "test" long; char*; struct ns__S { ... } |
A module MUST be imported into another header file to use it and you cannot use a module alone to build a SOAP or XML application. That is, the top most header file in the import tree SHOULD NOT be a module.
When multiple modules are linked, the types that they declare MUST be declared in one module only to avoid name clashes and link errors. You cannot create two modules that share the same type declaration and link the modules. When necessary, you should consider creating a module hierarchy such that types are declared only once and by only one module when these modules must be linked.
The #import directive is used to include gSOAP header files into other gSOAP header files for processing with the gSOAP compiler soapcpp2. The C #include directive cannot be used to include gSOAP header files. The #include directive is reserved to control the post-gSOAP compilation process, see 9.6.
The #import directive is used for two purposes: you can use it to include the contents of a header file into another header file and you can use it to import a module, see 9.4.
An example of the #import directive:
#import "mydefs.gsoap" int ns__mymethod(xsd__string in, xsd__int *out); |
typedef char *xsd__string; typedef int xsd__int; |
The #include and #define directives are normally ignored by the gSOAP compiler. The use of the directives is enabled with the -i option of the gSOAP compiler, see Section 9.1. However, the gSOAP compiler will not actually parse the contents of the header files provided by the #include directives in a header file. Instead, the #include and #define directives will be added to the generated soapH.h header file before any other header file is included. Therefore, #include and #define directives can be used to control the C/C++ compilation process of the sources of an application.
The following example header file refers to ostream by including < ostream > :
#include < ostream > #define WITH_COOKIES // use HTTP cookie support (you must compile stdsoap2.cpp with -DWITH_COOKIES) #define WITH_OPENSSL // enable HTTPS (SSL) support (you must compile stdsoap2.cpp with -DWITH_OPENSSL) #define SOAP_DEFAULT_float FLT_NAN // use NaN instead of 0.0 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 ... }; |
Caution: Note that the use of #define in the header file does not automatically result in compiling stdsoap2.cpp with these directives. You MUST use the -DWITH_COOKIES and -DWITH_OPENSSL options when compiling stdsoap2.cpp before linking the object file with your codes. As an alternative, you can use #define WITH_SOAPDEFS_H and put the #define directives in the soapdefs.h file.
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 -lnsl |
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/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/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 -lnsl |
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/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema"}, {"ns1", "urn:my-remote-method"}, {NULL, NULL} }; ... |
The gSOAP compiler can be used to create pure C Web services and clients. The gSOAP stub and skeleton compiler
soapcpp2 generates .cpp files by default. The compiler generates .c files with the -c option.
However, these files only use C syntax and data types if the header
file input to soapcpp2 uses C syntax and data types. For example:
soapcpp2 -c quote.h gcc -o quote quote.c stdsoap2.c soapC.c soapClient.c |
gSOAP is SOAP 1.1 and SOAP 1.2 compliant and supports SOAP RPC and document/literal operations.
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.
There are certain limitations for the following C++ language constructs:
The following macros (#defines) can be used to enable certain optional features:
|
gSOAP provides flags to control the input and output mode settings at runtime. These flags are divided into four categories: transport (IO), content encoding (ENC), XML marshalling (XML), and C/C++ data mapping (C).
Although gSOAP is fully SOAP 1.1 compliant, some SOAP implementations may have
trouble accepting multi-reference data and/or require explicit nil data so
these flags can be used to put gSOAP in ``safe mode''. In addition, the
embedding (or inlining) of multi-reference data is adopted in the SOAP 1.2
specification, which gSOAP automatically supports when handling with SOAP 1.2
messages. The flags are:
|
All flags are orthogonal, except SOAP_IO_FLUSH, SOAP_IO_BUFFER, SOAP_IO_STORE, and SOAP_IO_CHUNK which are enumerations and only one of these I/O flags can be used. Also the XML serialization flags SOAP_XML_TREE and SOAP_XML_GRAPH should not be mixed.
The flags control the inbound and outbound message transport, encoding, and
(de)serialization. The following functions are used to set and reset the flags
for input and output modes:
|
For example
struct soap soap; soap_init2(&soap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE | SOAP_ENC_ZLIB | SOAP_XML_TREE | SOAP_XML_CANONICAL); if (soap_call_ns__myMethod(&soap, ...)) ... |
In many cases, setting the input mode will have no effect, especially with HTTP transport because gSOAP will determine the optimal input buffering and the encoding used for an inbound message. The flags that have an effect on handling inbound messages are SOAP_IO_KEEPALIVE, SOAP_ENC_SSL (but automatic when "https:" endpoints are used or soap_ssl_accept), SOAP_C_NOIOB, SOAP_C_UTFSTRING, and SOAP_C_MBSTRING.
Caution: The SOAP_XML_TREE serialization flag can be used to improve interoperability with SOAP implementations that are not fully SOAP 1.1 compliant. However, a tree serialization will duplicate data when necessary and 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 requires several calls to the malloc library function to allocate space for the data 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 functions to deallocate data collectively or individually.
The deallocation functions are:
|
Individual data objects can be unlinked from the deallocation chain if necessary, to prevent deallocation by the collective soap_end or soap_destroy functions.
There are three situations to consider for memory deallocation policies for class instances:
To summarize, it is advised to pass class data types by pointer to a remote method. For example:
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.
Objects instantiated with soap_new_X(struct soap*) are removed altogether with soap_destroy(struct soap*).
Individual objects instantiated with soap_new_X are removed with soap_delete_X(struct soap*, X*).
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) { char *msg = (char*)soap_malloc(soap, 1024); // allocate temporary space for detailed message sprintf(msg, "...", ...); // produce the detailed message return soap_receiver_fault(soap, Än exception occurred", msg); // return the server-side fault } ... } |
The above functions do not include a SOAP 1.2 Subcode element. To include Subcode element, use soap_receiver_fault_subcode(struct soap *soap, const char *subcode, const char *faultstring, const char *detail) to set a SOAP 1.1/1.2 fault with Subcode at the server-side. Use soap_sender_fault_subcode(struct soap *soap, const char *subcode, const char *faultstring, const char *detail) to set a SOAP 1.1/1.2 unrecoverable Bad Request fault with Subcode at the server-side.
gSOAP provides a function to duplicate a string into gSOAP's memory space:
char *soap_strdup(struct soap *soap, const char *s) |
When a class declaration has a struct soap * field, this field will be set to point to the current gSOAP run-time environment by
gSOAP's deserializers and by the soap_new_Class functions.
This simplifies memory management for class instances.
The struct soap* pointer is implicitly set by the gSOAP deserializer for
the class or explicitly by calling the soap_new_X function for class X.
For example:
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 directive 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 |
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 10.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 for the remote
method. This stub 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 by a pointer to the data. Of course, passing a pointer to the data is preferred 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 9.10
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 8.1.7. 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 8.1.13.
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 with the soap_receiver_fault(struct soap *soap, const char *faultstring, const char *detail) which sets the strings soap.fault->faultstring and soap.fault->detail for SOAP 1.1, and soap.fault->SOAP_ENV__Reason and soap.fault->SOAP_ENV__Detail for SOAP 1.2, where soap is a variable that contains the current runtime environment, see Section 12. A receiver error indicates that the service can't handle the request, but can possibly recover from the error. To return an unrecoverable error, use soap_receiver_fault(struct soap *soap, const char *faultstring, const char *detail).
To return a HTTP error code a service method can simply return the HTTP error code number.
For example, return 404; returns a "404 Not Found" HTTP error back to the client. The soap.error
is set to the HTTP error code at the client side.
The HTTP 1.1 error codes are:
|
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
hyphens, 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_DOTcom { char *n_s__biz_USCOREname; }; |
<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___DOTcom 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/2001/XMLSchema-instance"}, // MUST be third {"xsd", "http://www.w3.org/2001/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/2001/XMLSchema-instance", "http://www.w3.org/----/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/----/XMLSchema"}, ... |
struct Namespace namespaces[] = { // {"ns-prefix", "ns-name", "ns-name validation pattern"} ... {"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"}, ... |
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_set_namespaces(&soap, 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 (XSD 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 (also known as XSD 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 XSD 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 XSD types. To this end, a typedef declaration can be used to declare an XSD type.
For example, the declaration
typedef unsigned int xsd__positiveInteger; |
<positiveInteger xsi:type="xsd: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 XSD 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 10.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 XSD Schema types form a hierarchy. The root of the hierarchy is called xsd:anyType (xsd:ur-type in the older 1999 schema). 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, see Section 11.5.4.
Wrapper structs are supported as well, similar to wrapper classes. But they cannot be used to implement polymorphism. Rather, the wrapper structs facilitate the use of XML attributes with a primitive typed object, see 11.5.7.
The decoding rules for the primitive C and C++ data types is given in the table below:
|
|
All explicitly declared XSD 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/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" <SOAP-ENV:Body> <getInfoResponse> <detail> <picture>Mona Lisa by <i>Leonardo da Vinci</i></picture> </detail> </getInfoResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
gSOAP supports STL strings std::string and std::wstring.
For example:
typedef std::string xsd__string; class ns__myClass { public: xsd__string s; // serialized with xsi:type="xsd:string" std::string t; // serialized without xsi:type ... }; |
Caution: Please avoid mixing std::string and C strings (char*) in the header file when using SOAP 1.1 encoding. The problem is that multi-referenced strings in SOAP encoded messages cannot be assigned simultaneously to a std::string and a char* string.
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 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 distinguish it from other symbolic names in C++. This guarantees that the SOAP encoding will use the same name, while the symbolic names can be distinguished 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 developing a C Web service application, the C++ bool type should not be used since it is not usually supported by the C compiler.
Instead, an enumeration type should be used to serialize true/false values as xsd:boolean Schema type enumeration values.
The xsd:boolean XML Schema type is defined as:
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 an XML Schema complexType (SOAP-encoded 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 11.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 serialization routines to check inheritance properties at run time.
Certain type of fields of a struct can be (de)serialized as XML attributes. See 11.5.7 for more details.
A class instance is serialized as an XML Schema complexType (SOAP-encoded 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.
Classes should be declared ``volatile'' if you don't want gSOAP to add serialization methods to these classes, see Section 19.4 for more details.
Class templates are not supported by the gSOAP compiler, but you can use STL containers, see Section 11.10.8. You can also define your own containers similar to STL containers.
Certain fields of a class can be (de)serialized as XML attributes. See 11.5.7 for more details.
Arrays may be embedded within a class (and struct) using a pointer field and size information, see Section 11.10.7. This defines what is sometimes referred to in SOAP as ``generics''.
Void pointers may be used in a class (or struct), but you have to add a type field so the gSOAP runtime can determine the type of object pointed to, see Section 11.8.
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.
Setter and getter methods are invoked at run time upon serialization and deserialization of class instances, respectively. The use of setter and getter methods adds more flexibility to the serialization and deserialization process.
A setter method is called in the serialization phase from the virtual soap_serialization method generated by the gSOAP compiler. You can use setter methods to process a class instance just before it is serialized. A setter method can be used to convert application data, such as translating transient application data into serializable data, for example. You can also use setter methods to retrieve dynamic content and use it to update a class instance right before serialization. Remember setters as ßet to serialize" operations.
Getter methods are invoked after deserialization of the instance. You can use them to adjust the contents of class instances after all their members have been deserialized. Getters can be used to convert deserialized members into transient members and even invoke methods to process the deserialized data on the fly.
Getter and setter methods have the following signature:
[virtual] int get(struct soap *soap) [const]; [virtual] int set(struct soap *soap); |
Here is an example of a base64 binary class:
class xsd__base64Binary { public: unsignedchar *__ptr; int__size; int get(struct soap *soap); int set(struct soap *soap); }; |
The get method is invoked after the base64 data has been processed. You can use it for post-processing purposes.
Here is another example. It defines a primitive update type. The class is a wrapper for the time_t type, see Section 11.2.2. Therefore, elements of this type contain xsd:dateType data.
class update { public: time_t __item; int set(struct soap *soap); }; |
int update::set(struct soap *soap) { this->__item = time(NULL); return SOAP_OK; } |
Caution: a get method is invoked only when the XML element with its data was completely parsed. The method is not invoked when the element is an xsi:nil element or has an href attribute.
Caution: The soap_serialize method of a class calls the setter (when provided). However, the soap_serialize method is declared const while the setter should be allowed to modify the contents of the class instance. Therefore, the gSOAP-generated code recasts the instance and the const is removed when invoking the setter.
Getter methods enable streaming XML operations. A getter method is invoked
when the object is deserialized and the rest of the SOAP/XML message has not
been processed yet. For example, you can add a getter method to the SOAP Header
class to implement header processing logic that is activated as soon as the
SOAP Header is received. An example code is shown below:
class h__Authentication { public: char *id; int get(struct soap *soap); }; class SOAP_ENV__Header { public: h__Authentication *h__authentication; }; |
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 SOAP RPC/LIT and SOAP DOC/LIT encoding styles support XML attributes in SOAP messages while SOAP RPC with ``Section 5'' encoding does not support XML attributes other than the SOAP and XSD specific attributes. SOAP RPC ``Section 5'' encoding has advantages for cross-language interoperability and data encodings such as graph serialization. However, RPC/LIT and DOC/LIT enables direct exchange of XML documents, which may include encoded application data structures. Language interoperability is compromised, because no mapping between XML and the typical language data types is defined. The meaning of the RPC/LIT and DOC/LIT XML content is Schema driven rather than application/language driven.
gSOAP supports XML attribute (de)serialization of members in structs and classes.
Attributes are primitive XSD types, such as strings, enumerations, boolean, and
numeric types. To declare an XML attribute in a struct/class, the qualifier
@ is used with the type of the attribute. The type must be
primitive type (including enumerations and strings), which can be declared with or without
a typedef to associate a XSD type with the C/C+ type. For example
typedef char *xsd__string; typedef bool *xsd__boolean; enum ns__state { _0, _1, _2 }; struct ns__myStruct { @xsd__string ns__type; // encode as XML attribute 'ns:type' of type 'xsd:string' @xsd__boolean ns__flag = false; // encode as XML attribute 'ns:flag' of type 'xsd:boolean' @enum ns__state ns__state = _2; // encode as XML attribute 'ns:state' of type 'ns:state' struct ns__myStruct *next; }; |
Default values can be associated with any field that has a primitive type in a struct/class, as is illustrated in this example. The default values are used when the receiving message does not contain the corresponding values.
String attributes are optional. Other type of attributes should be declared as pointers to make them optional:
struct ns__myStruct { @int *a; // omitted when NULL }; |
int ns__myMethod(@char *ns__name, ...); |
struct xsd__string { char *__item; @xsd__boolean flag; }; |
struct xsd__base64Binary { unsigned char *__ptr; int __size; @xsd__boolean flag; }; |
Caution: Do not use XML attributes with SOAP RPC encoding. You can only use attributes with RPC literal encoding.
gSOAP ensures the proper decoding of XSD QNames.
An element or attribute with type QName (Qualified Name) contains a namespace prefix and a local name.
You can declare a QName type as a typedef char *xsd__QName. Values of type QName
are internally handled as regular strings.
gSOAP takes care of the proper namespace prefix mappings when deserializing QName values.
For example
typedef char *xsd__QName; struct ns__myStruct { xsd__QName elt = "ns:xyz"; // QName element with default value "ns:xyz" @xsd__QName att = "ns:abc"; // QName attribute with default value "ns:abc" }; |
Note: QName is a pre-defined typedef type and used by gSOAP to (de)serialize SOAP Fault codes which are QName elements.
A union is only serialized if the union is used within a struct or class declaration that includes a int __union field that acts as a discriminant or selector for the union fields. The selector stores run-time usage information about the union fields. That is, the selector is used to enumerate the union fields such that the gSOAP engine is able to select the correct union field to serialize.
A union within a struct or class with a selector field
represents xs:choice within a Schema complexType component. For example:
struct ns__PO { ... }; struct ns__Invoice { ... }; union ns__PO_or_Invoice { struct ns__PO po; struct ns__Invoice invoice; }; struct ns__composite { char *name; int __union; union ns__PO_or_Invoice value; }; |
<complexType name="composite» <sequence> <element name="name" type="xsd:string"/> <choice> <element name="po" type="ns:PO"/> <element name="invoice" type="ns:Invoice"/> </choice> </sequence> </complexType> |
The int __union field selector's values are determined by the
soapcpp2 compiler. Each union field name has a selector value
formed by:
SOAP_UNION_union-name_field-name |
struct ns__composite { char *name; int __union 0; // <choice minOccurs="0» union ns__PO_or_Invoice value; }; |
The following example shows how the struct ns__composite instance is
initialized for serialization:
struct ns__composite data; data.name = "..."; data.__union = SOAP_UNION_ns__PO_or_Invoice_po; // select PO data.value.po.number = ...; // populate the PO |
For deserialization of union types, the __union selector will be ser to 0 (when permitted) by the gSOAP deserializer or set to one of the union field selector values as determined by the XML payload.
When more than one union is used in a struct or class, the
__union selectors must be renamed to avoid name clashes by using
suffixes as in:
struct ns__composite { char *name; int __union_value; // added suffix "_value" union ns__PO_or_Invoice value; int __union_data; // added suffix "_data" union ns__Email_or_Fax data; }; |
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 indistinguishable 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 9.12 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 9.12 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_XML_STRICT flag is set. The types section of a WSDL description contains information on the ``nilability'' of data.
In general, void pointers (void*) cannot be (de)serialized because the type of data referred to is untyped. To enable the (de)serialization of the void pointers that are members of structs or classes, you can insert a int __type field right before the void pointer field. The int __ type field contains run time information on the type of the data pointed to by void* member in a struct/class to enable the (de)serialization of this data. The int __type field is set to a SOAP_TYPE_X value, where X is the name of a type. gSOAP generates the SOAP_TYPE_X definitions in soapH.h and uses them internally to uniquely identify the type of each object. The type naming conventions outlined in Section 8.5.1 are used to determine the type name for X.
Here is an example to illustrate the (de)serialization of a void* field in a struct:
struct myStruct { int __type; // the SOAP_TYPE pointed to by p void *p; }; |
The following example illustrates the initialization of myStruct with a
void pointer to an int:
struct myStruct S; int n; S.p = &n; S.__type = SOAP_TYPE_int; |
The deserializer for myStruct will automatically set the __type field and void pointer to the deserialized data, provided that the XML content for p carries the xsi:type attribute from which gSOAP can determine the type.
Important: when (de)serializing strings via a void* field, the void* pointer MUST directly point to the string value rather than indirectly as with all other types. For example:
struct myStruct S; S.p = (void*)"Hello"; S.__type = SOAP_TYPE_string; |
You may use an arbitrary suffix with the __type fields to handle
multiple void pointers in structs/classes. For example
struct myStruct { int __typeOfp; // the SOAP_TYPE pointed to by p void *p; int __typeOfq; // the SOAP_TYPE pointed to by q void *q; }; |
typedef char *xsd__string; typedef int xsd__int; typedef float xsd__float; enum ns__status { on, off }; struct ns__widget { xsd__string name; xsd__int part; }; int ns__myMethod(int __type, void *data, struct ns__myMethodResponse { int __type; void *return_; } *out); |
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 adaptable 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.
SOAP encoded arrays use the SOAP-ENC:Array type and the SOAP-ENC:arrayType attribute to define the array dimensionality and size. As a security measure to avoid denial of service attacks based on sending a huge array size value requiring the allocation of large chunks of memory, the total number of array elements set by the SOAP-ENC:arrayType attribute cannot exceed SOAP_MAXARRAYSIZE, which is set to 100,000 by default. This constant is defined in stdsoap2.h. This constant only affects multi-dimensional arrays and the dimensionality of the receiving array will be lost when the number of elements exceeds 100,000. One-dimensional arrays will be populated in sequential order as expected.
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 SOAP arrays is:
struct some_name { Type *__ptr; // pointer to array int __size; // number of elements pointed to [[static const] int __offset [= ...];] // optional SOAP 1.1 array 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.
Caution: SOAP 1.2 does not support partially transmitted arrays. So the __offset field of a dynamic array is ignored.
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() { // destruction handled by gSOAP } 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(); ... soap_destroy(&soap); // dealloc class instances soap_end(&soap); // dealloc deserialized data soap_done(&soap); // cleanup and detach soap struct } |
The declaration of a dynamic array as described in 11.10 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. Note that array offsets is a SOAP 1.1 specific feature which is not supported in SOAP 1.2.
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 generic vector, see Section 11.10.7.
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]; }; |
XML ``generics'' extend the concept of a struct by allowing repetitions of elements within the struct. A simple generic is an array-like data structure with a repetition of one element. To achieve this, declare a dynamic array as a struct or class with a name that is qualified with a namespace prefix. SOAP arrays are declared without prefix.
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> |
Multiple arrays can be used in a struct/class to support the concept of generics. Each array results in a repetition of elements in the struct/class. This is achieved with a int __size field in the struct/class where the next field (i.e. below the __size field) is a pointer type. The pointer type is assumed to point to an array of values at run time. The __size field holds the number of values at run time. Multiple arrays can be embedded in a struct/class with __size fields that have a distinct names. To make the __size fields distinct, you can end them with a unique name suffix such as __sizeOfstrings, for example.
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> |
gSOAP supports the STL containers std::deque, std::list, std::set, and std::vector.
STL containers can only be used within classes to declare members that contain multiple values. This is somewhat similar to the embedding of arrays in structs in C as explained in Section 11.10.7, but the STL container approach is more flexible.
You need to import stldeque.h, stllist.h, stlset.h, or
stlvector.h to enable std::deque, std::list, std::set,
and std::vector (de)serialization.
Here is an example:
#import "stlvector.h" class ns__myClass { public: std::vector < int > *number; std::vector < xsd__string > *name; ... }; |
The XML Schema that corresponds to the ns__myClass type is
<complexType name="myClass» <sequence> <element name="number" type="xsd:int" minOccurs="1" maxOccurs="unbounded"/> <element name="name" type="xsd:string" minOccurs="1" maxOccurs="unbounded"/> ... </sequence> </complexType> |
You can also implement your own containers similar to STL containers. The containers must be class templates and should define an iterator type, and void clear(), iterator begin(), iterator end(), and iterator insert(iterator pos, const_reference val). The iterator should have a dereference operator to access the container's elements. The dereference operator is used by gSOAP to send a sequence of XML element values. The insert method can be used as a setter method. gSOAP reads a sequence of XML element values and inserts them in the container via this method.
Here is in example user-defined container template class:
// simpleVector.h template < class T > class simpleVector { public: typedef T value_type; typedef value_type * pointer; typedef const value_type * const_pointer; typedef value_type & reference; typedef const value_type & const_reference; typedef pointer iterator; typedef const_pointer const_iterator; protected: iterator start; iterator finish; size_t size; public: simpleVector() { clear(); } ~simpleVector() { delete[] start; } void\ clear() { start = finish = NULL; } iterator begin() { return start; } const_iterator begin() const\ { return start; } iterator end() { return finish; } const_iterator end() const\ { return finish; } iterator insert(iterator pos, const_reference val) { if (!start) start = finish = new value_type[size = 4]; else if (finish > = start + size) { iterator i = start; iterator j = new value_type[2 * size]; start = j; finish = start + size; size *= 2; if (pos) pos = j + (pos - i); while (i != finish) *j++ = *i++; } if (pos && pos != finish) { iterator i = finish; iterator j = i - 1; while (j != pos) *i- = *j-; } *finish++ = val; return pos; } }; |
#include "simpleVector.h" template < class T > class simpleVector; |
Caution: when parsing XML content the container elements may not be stored in the same order given in the XML content. When gSOAP parses XML it uses the insert container methods to store elements one by one. However, element content that is ``forwarded'' with href attributes will be appended to the container. Forwarding can take place with multi-referenced data that is referred to from the main part of the SOAP 1.1 XML message to the independent elements that carry ids. Therefore, your application should not rely on the preservation of the order of elements in a container.
Polymorphic arrays (arrays of polymorphic element types) can be encoded when
declared as an array of pointers to class instances. and lists. For example:
class ns__Object { public: ... }; class ns__Data: public ns__Object { public: ... }; class ArrayOfObject { public: ns__Object **__ptr; // pointer to array of pointers to Objects int __size; // number of Objects pointed to int __offset; // optional SOAP 1.1 array 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> |
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(struct soap *soap, 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(struct soap *soap, int n) { __ptr = (unsigned char*)soap_malloc(soap, n); __size = n; } xsd__base64Binary::~xsd__base64Binary() { } unsigned char *xsd__base64Binary::location() { return __ptr; } int xsd__base64Binary::size() { return __size; } |
... FILE *fd = fopen("image.jpg", "rb"); xsd__base64Binary image(&soap, 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 SOAP RPC encoding by default. Literal encoding is optional. Just as with SOAP RPC encoding, literal encoding requires the XML Schema of the message data to be provided e.g. in WSDL in order for the gSOAP compiler to generate the (de)serialization routines. Alternatively, the optional DOM parser (dom.c and dom++.cpp) can be used to handle generic XML or arbitrary XML documents can be (de)serialized into regular C strings or wide character strings (wchar_t*) by gSOAP (see Section 11.13.1).
gSOAP supports literal encoding by default.
The //gsoap service encoding, //gsoap service method-encoding, and //gsoap service method-response-encoding directives explicitly enable SOAP encoded or literal encoded messages. For example, to enable RPC encoding style for the entire service, use:
//gsoap ns service encoding: encoded |
//gsoap ns service method-encoding: myMethod encoded int ns__myMethod(...) |
//gsoap ns service method-response-encoding: myMethod encoded int ns__myMethod(...) |
Consider the following example that uses the directive to make the literal encoding explicit.
The LocalTimeByZipCode remote service method of the LocalTime service provides
the local time given a zip code and uses literal encoding (with MS
.NET). The following header file declares the method:
int LocalTimeByZipCode(char *ZipCode, char **LocalTimeByZipCodeResult); |
//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 is:
#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 illustrate the manual doc/literal setting, the following client program sets
the required properties before the call:
#include "soapH.h" #include "localtime.nsmap" // include generated map file int main() { struct soap soap; char *t; soap_init(&soap); soap.encodingStyle = NULL; // don't use SOAP encoding soap_set_omode(&soap, SOAP_XML_TREE);" // don't produce multi-ref data (but can accept) 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; } |
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" <SOAP-ENV:Body> <LocalTimeByZipCode xmlns="http://alethea.net/webservices/"> <ZipCode>32306</ZipCode></LocalTimeByZipCode> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
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"; |
The XML type can be used as part of any data structure to enable the rendering and parsing of custom XML documents. For example:
typedef char *XML; struct ns__Data /* data in namespace 'ns' */ { int number; char *name; XML m__document; /* XML document in default namespace 'm' */ }; ns__Example(struct ns__Data data, struct ns__ExampleResponse { struct ns__Data data; } *out); |
A predeclared standard SOAP Fault data structure is generated by the gSOAP stub and skeleton compiler for exchanging exception messages.
The built-in struct SOAP_ENV__Fault data structure is defined as:
struct SOAP_ENV__Fault { _QName faultcode; // _QName is builtin char *faultstring; char *faultactor; struct SOAP_ENC__Detail *detail; struct SOAP_ENV__Code *SOAP_ENV__Code; // MUST be a SOAP_ENV__Code struct defined below char *SOAP_ENV__Reason; char *SOAP_ENV__Node; char *SOAP_ENV__Role; struct SOAP_ENV__Detail SOAP_ENV__Detail; // SOAP 1.2 detail field }; struct SOAP_ENV__Code { _QName SOAP_ENV__Value; struct SOAP_ENV__Code *SOAP_ENV__Subcode; }; struct SOAP_ENV__Detail { int __type; // The SOAP_TYPE_ of the object serialized as Fault detail void *fault; // pointer to the fault object, or NULL char *__any; // any other detail element content (stored in XML format) }; |
The data structure content can be changed to the need of an application, but this is generally not necessary because the application-specific SOAP Fault details can be serialized via the __type and fault fields in the SOAP_ENV__Detail field, see Section 11.8 on the serialization of data refered to by __type and fault.
The __type field allows application data to be serialized as part of the SOAP Fault. The application data SHOULD be defined as XML elements, which requires you to declare the type names with a leading underscore to ensure that the types are compatible with XML elements and not just simpleTypes and complexTypes.
When the skeleton of a remote method returns an error (see Section 10.2), then soap.fault contains the SOAP Fault data at the receiving side (client).
Server-side faults are raised with soap_sender_fault or soap_receiver_fault. The soap_sender_fault call should be used to inform that the sender is at fault and the sender (client) should not resend the request. The soap_receiver_fault call should be used to indicate a temporary server-side problem, so a sender (client) can resend the request later. For example:
int ns1__myMethod(struct soap *soap, ...) { ... return soap_receiver_fault(soap, "Resource temporarily unavailable", NULL); // return fault to sender } |
return soap_receiver_fault(soap, "Resource temporarily unavailable", " < errorcode xmlns='http://tempuri.org' > 123 < /errorcode > < errorinfo xmlns='http://tempuri.org' > abc < /errorinfo > "); |
When a remote method must raise an exception with application SOAP Fault details, it does so by assigning the soap.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_receiver_fault(soap, "Stack dump", NULL); if (soap->version == 2) // SOAP 1.2 is used { soap->fault->SOAP_ENV__Detail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail); soap->fault->SOAP_ENV__Detail->__type = SOAP_TYPE_ns1__myStackDataType; // stack type soap->fault->SOAP_ENV__Detail->fault = sp; // point to stack soap->fault->SOAP_ENV__Detail->__any = NULL; // no other XML data } else { soap->fault->detail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail); soap->fault->detail->__type = SOAP_TYPE_ns1__myStackDataType; // stack type soap->fault->detail->fault = sp; // point to stack soap->fault->detail->__any = NULL; // no other XML data } 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 8.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_receiver_fault(&soap, Ïnit failed", NULL); // set the error condition (SOAP_FAULT) 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> <transaction xmlns="..." xsi:type="int">12345</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 the attribute has the value 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"
attribute. For example:
struct SOAP_ENV__Header { char *t__transaction; mustUnderstand char *t__authentication; }; |
<SOAP-ENV:Envelope ...> <SOAP-ENV:Header> <transaction xmlns="...»5</transaction> <authentication xmlns="..." SOAP-ENV:actor="http://some/actor" SOAP-ENV:mustUnderstand="1">XX </authentication> </SOAP-ENV:Header> <SOAP-ENV:Body> ... </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
The gSOAP toolkit supports MIME attachments as per SOAP with Attachments (SwA) specification (http://www.w3.org/TR/SOAP-attachments). MIME attachment data must be memory resident for sending operations and MIME attachments received will be stored in memory. DIME attachments on the other hand can be streamed and therefore DIME attachment data does not need to be stored in memory, see Section 15.
Transmitting multipart/related MIME attachments with a SOAP/XML message is accomplished with two functions, soap_set_mime and soap_set_mime_attachment. The first function is for initialization purposes and the latter function is used to specify meta data and content data for each attachment.
The following functions should be used to set up a collection of
multipart/related MIME attachments for transmission with a SOAP/XML message.
|
The internal list of attachments is destroyed with soap_end, you should call this function sometime after the message exchange was completed (the content of the block of memory referred to by the ptr parameter is unaffected).
The following example shows how a multipart/related HTTP message with three
MIME attachments is set up and transmitted to a server. The first attachment
contains the SOAP message. The second and third attachments contain image data.
In this example we let the SOAP message body refer to the attachments using
href attributes. The struct claim__form data type includes a
definition of a href attribute for this purpose.
struct claim__form form1, form2; form1.href = "cid:claim061400a.tiff@claiming-it.com"; form2.href = "cid:claim061400a.jpeg@claiming-it.com"; /* initialize and enable MIME */ soap_set_mime(soap, "MIME_boundary", "<claim061400a.xml@claiming-it.com>"); /* add a base64 encoded tiff image (tiffImage points to base64 data) */ soap_set_mime_attachment(soap, tiffImage, tiffLen, SOAP_MIME_BASE64, "image/tiff", "<claim061400a.tiff@claiming-it.com>", NULL, NULL); /* add a raw binary jpeg image (jpegImage points to raw data) */ soap_set_mime_attachment(soap, jpegImage, jpegLen, SOAP_MIME_BINARY, "image/jpeg", "<claim061400a.jpeg@claiming-it.com>", NULL, NULL); /* send the forms as MIME attachments with this invocation */ if (soap_call_claim__insurance_claim_auto(soap, form1, form2, ...)) // an error occurred else // process response |
struct claim__form { @char *href; }; |
Note: the above example assumes that the boundary MIME_boundary does not occur in the SOAP/XML message.
The server-side code to transmit MIME attachments back to a client is similar:
int claim__insurance_claim_auto(struct soap *soap, ...) { soap_set_mime(soap, NULL, NULL); // enable MIME // add a HTML document (htmlDoc points to data, where the HTML doc is stored in compliance with 7bit encoding RFC2045) if (soap_set_mime_attachment(soap, htmlDoc, strlen(htmlDoc), SOAP_MIME_7BIT, "text/html", "<claim061400a.html@claiming-it.com>", NULL, NULL)) { soap_clr_mime(soap); // don't want fault with attachments return soap->error; } return SOAP_OK; } |
Caution: DIME in MIME is supported. However, gSOAP will not verify whether the MIME boundary is present in the DIME attachments and therefore will not select a boundary that is guaranteed to be unique. Therefore, you must provide a MIME boundary with soap_set_mime that is unique when using DIME in MIME.
MIME attachments are automatically parsed and stored in memory. After receiving a set of MIME attachments, either at the client-side or the server-side, the list of MIME attachments can be traversed to extract meta data and the attachment content. The first attachment in the collection of MIME attachments always contains meta data about the SOAP message itself (because the SOAP message was processed the attachment does not contain any useful data).
To traverse the list of MIME attachments in C, you use a loop similar to:
struct soap_multipart *attachment; for (attachment = soap.mime.list; attachment; attachment = attachment->next) { printf("MIME attachment:\n"); printf("Memory=%p\n", (*attachment).ptr); printf("Size=%ul\n", (*attachment).size); printf("Encoding=%d\n", (int)(*attachment).encoding); printf("Type=%s\n", (*attachment).type?(*attachment).type:"null"); printf("ID=%s\n", (*attachment).id?(*attachment).id:"null"); printf("Location=%s\n", (*attachment).location?(*attachment).location:"null"); printf("Description=%s\n", (*attachment).description?(*attachment).description:"null"); } |
for (soap_multipart::iterator attachment = soap.mime.begin(); attachment != soap.mime.end(); ++attachment) { cout << "MIME attachment:" << endl; cout << "Memory=" << (void*)(*attachment).ptr << endl; cout << "Size=" << (*attachment).size << endl; cout << Ëncoding=" << (*attachment).encoding << endl; cout << "Type=" << ((*attachment).type?(*attachment).type:"null") << endl; cout << ÏD=" << ((*attachment).id?(*attachment).id:"null") << endl; cout << "Location=" << ((*attachment).location?(*attachment).location:"null") << endl; cout << "Description=" << ((*attachment).description?(*attachment).description:"null") << endl; } |
A call to soap_end removes all of the received MIME data. To preserve an attachment in memory, use soap_unlink on the ptr field of the soap_multipart struct. Recall that the soap_unlink function is commonly used to prevent deallocation of deserialized data.
The wsdl2h WSDL parser recognizes MIME attachments and produces an annotated header file. However, the ordering of MIME parts in the multipartRelated elements is not reflected in the header file. Application developers should adhere the standards and ensure that multipart/related attachments are transmitted in compliance with the WSDL operation declarations.
As of this writing, the gSOAP compiler soapcpp2 does not yet produce a WSDL with MIME extensions.
The gSOAP toolkit supports DIME attachments as per DIME specification, see for example (http://msdn.microsoft.com/library/en-us/dnglobspec/html/draft-nielsen-dime-02.txt)
Applications developed with gSOAP can transmit binary DIME attachments with or without streaming. Without streaming, all data is stored and retrieved in memory, which can be prohibitive when transmitting large files on small devices. In contrast with DIME streaming, data handlers are used to pass the data to and from a resource, such as a file or device. With DIME output streaming, raw binary data is send from a data source in chunks on the fly without buffering the entire content to save memory. With DIME input streaming, raw binary data will be passed to data handlers (callbacks).
The following functions should be used to set up a collection of
multipart/related MIME attachments for transmission with a SOAP/XML message.
The attachments can be streamed, as described in Section 15.4.
Without streaming, each attachment must refer to a block of data in memory.
|
DIME attachments are automatically parsed and stored in memory (or passed to the streaming handlers). After receiving a set of DIME attachments, either at the client-side or the server-side, the list of DIME attachments can be traversed to extract meta data and the attachment content.
To traverse the list of DIME attachments in C, you use a loop similar to:
struct soap_multipart *attachment; for (attachment = soap.dime.list; attachment; attachment = attachment->next) { printf("DIME attachment:\n"); printf("Memory=%p\n", (*attachment).ptr); printf("Size=%ul\n", (*attachment).size); printf("Type=%s\n", (*attachment).type?(*attachment).type:"null"); printf("ID=%s\n", (*attachment).id?(*attachment).id:"null"); } |
for (soap_multipart::iterator attachment = soap.dime.begin(); attachment != soap.dime.end(); ++attachment) { cout << "DIME attachment:" << endl; cout << "Memory=" << (void*)(*attachment).ptr << endl; cout << "Size=" << (*attachment).size << endl; cout << "Type=" << ((*attachment).type?(*attachment).type:"null") << endl; cout << ÏD=" << ((*attachment).id?(*attachment).id:"null") << endl; } |
A call to soap_end removes all of the received DIME data. To preserve an attachment in memory, use soap_unlink on the ptr field of the soap_multipart struct. Recall that the soap_unlink function is commonly used to prevent deallocation of deserialized data.
Binary data stored in extended xsd:base64Binary and xsd:hexBinary types can be serialized and deserialized as DIME attachments. These attachments will be transmitted prior to the sequence of secondary DIME attachments defined by the user with soap_set_dime_attachment as explained in the previous section. The serialization process is automated and DIME attachments will be send even when soap_set_dime or soap_set_dime_attachment are not used.
The xsd:base64Binary XSD type is defined in gSOAP as a struct or class by
struct xsd__base64Binary { unsigned char *__ptr; // pointer to raw binary data int __size; // size of the block of data }; |
struct xsd__base64Binary { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; |
The id and type fields contain text. The set the DIME-specific
options field, you can use the soap_dime_option function:
char *soap_dime_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_dime_option(soap, 0, "My wedding picture"); |
struct ns__myBinaryDataType { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; |
class xsd__base64Binary { unsigned char *__ptr; int __size; }; class ns__myBinaryDataType : xsd__base64Binary { char *id; char *type; char *options; }; |
//gsoap WSref schema import: http://schemas.xmlsoap.org/ws/2002/04/reference/ struct ns__myBinaryDataType { unsigned char *__ptr; int __size; char *id; char *type; char *options; @char *WSref__location; }; |
When receiving DIME attachments, the DIME meta data and binary data content is stored in binary data types only when the XML parts of the message uses href attributes to refer to these attachments. The gSOAP toolkit may support automatic (de)serialization with other user-defined (or WSDL-defined) attributes in future releases.
Messages may contain binary data that references external resources not provided as attachments. In that case, the __ptr field is NULL and the id field refers to the external data source.
The dime_id_format attribute of the current gSOAP run-time environment
can be set to the default format of DIME id fields. The format string MUST
contain a %d format specifier (or any other int-based format
specifier). The value of this specifier is a non-negative integer, with zero
being the value of the DIME attachment id for the SOAP message. For example,
struct soap soap; soap_init(&soap); soap.dime_id_format = "uuid:09233523-345b-4351-b623-5dsf35sgs5d6-%x"; |
Caution: Care must be taken not to introduce duplicate content id values, when assigning content id values to the id fields of DIME extended binary data types. Content ids must be unique.
Streaming DIME is achieved with callback functions to fetch and store data during transmission. Three function callbacks for streaming DIME output and three callbacks for streaming DIME input are available.
|
The following example illustrates the client-side initialization of an image
attachment struct to stream a file into a DIME attachment:
int main() { struct soap soap; struct xsd__base64Binary image; FILE *fd; struct stat sb; soap_init(&soap); if (!fstat(fileno(fd), &sb) && sb.st_size > 0) { // because we can get the length of the file, we can stream it soap.fdimereadopen = dime_read_open; soap.fdimereadclose = dime_read_close; soap.fdimeread = dime_read; image.__ptr = (unsigned char*)fd; // must set to non-NULL (this is our fd handle which we need in the callbacks) image.__size = sb.st_size; // must set size } else { // don't know the size, so buffer it size_t i; int c; image.__ptr = (unsigned char*)soap_malloc(&soap, MAX_FILE_SIZE); for (i = 0; i < MAX_FILE_SIZE; i++) { if ((c = fgetc(fd)) == EOF) break; image.__ptr[i] = c; } fclose(fd); image.__size = i; } image.type = "image/jpeg"; image.options = soap_dime_option(&soap, 0, "My picture"); soap_call_ns__method(&soap, ...); ... } void *dime_read_open(struct soap *soap, void *handle, const char *id, const char *type, const char *options) { return handle; } void dime_read_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } size_t dime_read(struct soap *soap, void *handle, char *buf, size_t len) { return fread(buf, 1, len, (FILE*)handle); } |
int main() { struct soap soap; soap_init(&soap); soap.fdimewriteopen = dime_write_open; soap.fdimewriteclose = dime_write_close; soap.fdimewrite = dime_write; soap_call_ns__method(&soap, ...); ... } void *dime_write_open(struct soap *soap, const char *id, const char *type, const char *options) { FILE *handle = fopen("somefile", "wb"); if (!handle) { soap->error = SOAP_EOF; soap->errnum = errno; // get reason } return (void*)handle; } void dime_write_close(struct soap *soap, void *handle) { fclose((FILE*)handle); } int dime_write(struct soap *soap, void *handle, const char *buf, size_t len) { size_t nwritten; while (len) { nwritten = fwrite(buf, 1, len, (FILE*)handle); if (!nwritten) { soap->errnum = errno; // get reason return SOAP_EOF; } len -= nwritten; buf += nwritten; } return SOAP_OK; } |
Caution: The options field is a DIME-specific data structure, consisting of a 4 byte header containing the option type info (hi byte, lo byte), option string length (hi byte, lo byte), followed by a non-'\0' terminated string. The gSOAP DIME handler recognizes one option at most.
gSOAP automatically handles inbound chunked DIME attachments (streaming or non-streaming). To transmit outbound DIME attachments, the attachment sizes MUST be determined in advance to calculate HTTP message length required to stream DIME over HTTP. However, gSOAP also supports the transmission of outbound chunked DIME attachments without prior determination of DIME attachment sizes when certain conditions are met. These conditions require either non-HTTP transport (use the output-mode SOAP_ENC_XML flag), or chunked HTTP transport (use the output-mode SOAP_IO_CHUNK flag). You can also use the SOAP_IO_STORE flag (which is also used automatically with compression to determine the HTTP content length header) but that cancels the benefits of streaming DIME.
To stream chunked DIME, set the __size field of an attachment to zero and enable HTTP chunking. The DIME fdimeread callback then fetches data in chunks and it is important to fill the entire buffer unless the end of the data has been reached and the last chunk is to be send. That is, fdimeread should return the value of the last len parameter and fill the entire buffer buf for all chunks except the last.
The wsdl2h WSDL parser recognizes DIME attachments and produces an annotated header file. Both open and closed layouts are supported for transmitting DIME attachments. For closed formats, all DIME attachments must be referenced from the SOAP message, e.g. using hrefs with SOAP encoding and using the application-specific reference attribute included in the base64Binary struct/class for doc/lit.
As of this writing, the gSOAP compiler soapcpp2 does not yet produce a WSDL with DIME extensions.
MTOM (Message Transmission Optimization Mechanism) is a relatively new format for transmitting attachments with SOAP messages (see http://www.w3.org/TR/soap12-mtom). MTOM is a W3C working draft as of this writing. MTOM attachments are essentially MIME attachments with standardized mechanisms for cross referencing attachments from the SOAP body, which is absent in (plain) MIME attachments and optional with DIME attachments.
Unlike the name suggests, the speed by which attached data is transmitted is not increased compared to MIME, DIME, or even XML encoded base64 data (at least the performance differences in gSOAP will be small). The advantage of the format is the standardized attachment reference mechanism, which should improve interoperability.
The MTOM specification mandates SOAP 1.2 and the use of the XOP namespace. The XOP Include element xop:Include is used to reference attachment(s) from the SOAP message body.
Because references from within the SOAP message body to attachments are
mandatory with MTOM, the implementation of the serialization and deserialization of MTOM
MIME attachments in gSOAP uses the extended binary type comparable to DIME support in gSOAP. This binary type is predefined in the import/xop.h file:
//gsoap xop schema import: http://www.w3.org/2004/08/xop/include struct _xop__Include { unsigned char *__ptr; int __size; char *id; char *type; char *options; }; typedef struct _xop__Include _xop__Include; |
You can explicitly import the xop.h in your header file to use the MTOM attachments in your service, for example:
#import ïmport/soap12.h" /* alternatively, without the import above, use: //gsoap SOAP-ENV schema namespace: http://www.w3.org/2003/05/soap-envelope //gsoap SOAP-ENC schema namespace: http://www.w3.org/2003/05/soap-encoding */ #import ïmport/xop.h" #import ïmport/xmlmime.h" //gsoap x schema namespace: http://my.first.mtom.net struct x__myData { _xop__Include xop__Include; // attachment @char *xmlmime__contentType; // and its contentType }; int x__myMTOMtest(struct x__myData *in, struct x__myData *out); |
When an instance of x__myDataType is serialized and either or both the
id and type fields are non-NULL, the data is transmitted as MTOM
MIME attachment if the SOAP_ENC_MTOM flag is set in the gSOAP's soap
struct context:
struct soap *soap = soap_new1(SOAP_ENC_MTOM); |
To generate multipartRelated bindings in the WSDL file, use the //gsoap ... service method-mime-type directive (see also Section 19.2. The directive can be repeated for each attachment you want to associate with a method's request and response messages.
For example:
#import ïmport/soap12.h" #import ïmport/xop.h" #import ïmport/xmlmime.h" //gsoap x schema namespace: http://my.first.mtom.net struct x__myData { _xop__Include xop__Include; // attachment @char *xmlmime__contentType; // and its contentType }; //gsoap x service method-mime-type: myMTOMtest text/xml int x__myMTOMtest(struct x__myData *in, struct x__myData *out); |
To bind attachments only to the request message of an operation, use //gsoap x service method-input-mime-type. Similarly, to bind attachments only to the response message of an operation, use //gsoap x service method-ouput-mime-type.
A receiver must be told to recognize MTOM attachments by setting the SOAP_ENC_MTOM flag of the gSOAP context. Otherwise, the regular MIME attachment mechanism will be used to store attachments and in this process the references from the SOAP message body to the attachments are lost.
When using wsdl2h to build clients and/or services, you should use the typemap.dat file included in the distribution package. The typemap.dat file defines the XOP namespace and XML MIME namespaces as imported namespaces:
xop = < http://www.w3.org/2004/08/xop/include > xmime = < http://www.w3.org/2004/06/xmlmime > xmlmime = < http://www.w3.org/2004/11/xmlmime > |
#import "xop.h" #import "xmlmime.h" |
Let's take a look at an example.
The wsdl2h importer generates a header file with #import "xop.h" from a WSDL that references XOP, for example:
#import "xop.h" #import "xmlmime.h" struct ns__Data { _xop__Include xop__Include; @char *xmlmime__contentType; }; |
int ns__echoData(struct ns__Data *in, struct ns__Data *out); |
struct soap *soap = soap_new1(SOAP_ENC_MTOM); struct ns__Data data; data.xop__Include.__ptr = (unsigned char*)"<b>Hello world!</b>"; data.xop__Include.__size = 20; data.xop__Include.id = NULL; // CID automatically generated by gSOAP engine data.xop__Include.type = "text/html"; // MIME type data.xop__Include.options = NULL; // no descriptive info added data.xmlmime__contentType = "text/html"; // MIME type if (soap_call_ns__echoData(soap, endpoint, action, &data, &data)) soap_print_fault(soap, stderr); else printf("Got data\n"); soap_destroy(soap); // remove deserialized class instances (C++ only) soap_end(soap); // remove temporary and deserialized data soap_done(soap); // detach free(soap); |
At the server side, we can implement an operation handler that just copies the input data to output to implement the echo operation as follows:
int ns__echoData(struct soap *soap, struct ns__Data *in, struct ns__data *out) { *out = *in; return SOAP_OK; } |
The gSOAP XML parser applies basic rules to validate content. However, occurrence constraints are not automatically verified unless explicitly indicated. This helps to avoid interoperability problems with toolkits that do not strictly enforce validation rules. In addition, we cannot always use strict validation for SOAP RPC encoded messages, since SOAP RPC encoding adopts a very loose serialization format.
Validation constraints are checked by gSOAP with the SOAP_XML_STRICT input mode flag set, e.g. with soap_set_imode(soap, SOAP_XML_STRICT) or soap_new(SOAP_XML_STRICT), see Section 9.12 for a complete list of flags.
To force the validation of minOccurs and maxOccurs contraints the SOAP_XML_STRICT input mode flag must be set.
The minOccurs and maxOccurs constraints are specified for fields of a struct and members of a class in a header file using the following syntax:
Type fieldname [minOccurs[:maxOccurs]] [= value] |
For example
struct ns__MyRecord { int n = 5; // element with default value 5, minOccurs=0, maxOccurs=1 int m 1; // element with minOccurs=1 int __size 0:10; // sequence <item> with minOccurs=0, maxOccurs=10 int *item; std::vector<double> nums 2; // sequence <nums> with minOccurs=2, maxOccurs=unbounded }; struct arrayOfint { int *__ptr 1:100; // minOccurs=1, maxOccurs=100 int size; }; |
Similar to the minOccurs and maxOccurs annotations defined in the previous section, attributes in a struct or class can be annotated with occurrence constraints to make them optional (0), required (1), or prohibited (0:0). Default values can be assigned to optional attributes.
For example
struct ns__MyRecord { @int m 1; // required attribute (occurs at least once) @int n = 5; // optional attribute with default value 5 @int o 0; // optional attribute (may or may not occur) @int p 0:0; // prohibited attribute }; |
A schema simpleType is defined with a typedef by taking a base primitive to defined a derived simpleType. For example:
typedef int time__seconds; |
<simpleType name="seconds» <restriction base="xsd:int"/> </simpleType> |
struct time__date { char *__item; // some custom format date (restriction of string) @enum time__zone { EST, GMT, ... } zone; } |
<complexType name="date» <simpleContent> <extension base="xsd:string"/> </simpleContent> <attribute name="zone" type="time:zone" use="optional"/> </complexType> <simpleType name="zone» <restriction base="xsd:string» <enumeration value="EST"/> <enumeration value="GMT"/> ... </restriction> </simpleType> |
typedef char *ns__string256 0:256; // simpleType restriction of string with max length 256 characters typedef char *ns__string10 10:10; // simpleType restriction of string with length of 10 characters typedef std::string *ns__string8 8; // simpleType restriction of string with at least 8 characters struct ns__data { char *__item :256; // simpleContent with at most 256 characters @char *name 1; // required name attribute }; struct time__date { char *__item :100; @enum time__zone { EST, GMT, ... } zone = GMT; } |
To associate a pattern with a simpleType, you can define a simpleType with a typedef and a pattern string:
typedef int time__second "[1-5]?[0-9]|60"; |
<simpleType name="second» <restriction base="xsd:int» <pattern value="[1-5]?[0-9]|60"/> </restriction base="xsd:int"/> </simpleType> |
Patterns are currently not checked in the validation process.
UDP is a simple, unreliable datagram protocol: UDP sockets are connectionless. UDP address formats are identical to those used by TCP. In particular UDP provides a port identifier in addition to the normal Internet address format. The UDP port space is separate from the TCP port space (i.e. a UDP port may not be ``connected'' to a TCP port). In addition broadcast packets may be sent (assuming the underlying network supports this) by using a reserved ``broadcast address''; this address is network interface dependent.
To enable SOAP-over-UDP with gSOAP, stdsoap2.c and stdsoap2.cpp MUST be compiled with -DWITH_UDP prior to linking with your application.
Client-side messages with SOAP-over-UDP endpoint URLs (soap.udp://...) will be automatically transmitted as datagrams. Server-side applications should set the SOAP_IO_UDP mode flag to accept UDP requests, e.g. using soap_init1 or soap_set_mode.
The maximum message length for datagram packets is restricted by the buffer size SOAP_BUFLEN, which is 65536 by default, unless compiled with WITH_LEAN to support small-scale embedded systems. For UDP transport SOAP_BUFLEN must not exceed the maximum UDP packet size 65536 (the size of datagram messages is constrained by the UDP packet size 216=65536 as per UDP standard). You can use gzip compression to reduce the message size, but note that compressed SOAP-over-UDP is a gSOAP-specific feature because it is not part of the SOAP-over-UDP specification.
The SOAP-over-UDP specification relies on WS-Addressing. The wsa.h file in the import directory defines the WS-Addressing elements for client and server applications.
The gSOAP implementation conforms to the SOAP-over-UDP requirements:
A SOAP-over-UDP application MUST use WS-Addressing to control message delivery as per SOAP-over-UDP specification.
The wsa.h file in the import directory defines the
WS-Addressing elements. To include the WS-Addressing elements in the SOAP
Header for messaging, a struct SOAP_ENV__Header structure must be
defined in your header file with the appropriate WS-Addressing elements.
For example:
#import "wsa.h" struct SOAP_ENV__Header { mustUnderstand _wsa__MessageID wsa__MessageID 0; mustUnderstand _wsa__RelatesTo *wsa__RelatesTo 0; mustUnderstand _wsa__From *wsa__From 0; mustUnderstand _wsa__ReplyTo *wsa__ReplyTo 0; mustUnderstand _wsa__FaultTo *wsa__FaultTo 0; mustUnderstand _wsa__To wsa__To 0; mustUnderstand _wsa__Action wsa__Action 0; }; |
One-way SOAP-over-UDP messages (see Section 8.3) should be
declared to include the wsa:MessageID, wsa:To, and wsa:Action
elements in the SOAP Header of the request message as follows:
//gsoap ns service method-header-part: sendString wsa__MessageID //gsoap ns service method-header-part: sendString wsa__To //gsoap ns service method-header-part: sendString wsa__Action int ns__sendString(char *str, void); |
//gsoap ns service method-header-part: echoString wsa__MessageID //gsoap ns service method-header-part: echoString wsa__To //gsoap ns service method-header-part: echoString wsa__Action //gsoap ns service method-input-header-part: sendString wsa__ReplyTo //gsoap ns service method-output-header-part: echoString wsa__RelatesTo int ns__echoString(char *str, char **res); |
This example assumes that the gSOAP header file includes the SOAP Header with
WS-Addressing elements and the ns__sendString function discussed in
Section 18.1
struct soap soap; struct SOAP_ENV__Header header; // the SOAP Header soap_init(&soap); soap.send_timeout = 1; // 1s timeout soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header header.wsa__MessageID = "message ID"; header.wsa__To = "server URL"; header.wsa__Action = "server action"; soap.header = &header; // bind the SOAP Header for transport // Send the message over UDP: if (soap_send_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!")) soap_print_fault(&soap, stderr); // report error soap_end(&soap); // cleanup soap_destroy(&soap); // cleanup soap_done(&soap); // close connection (should not use soap struct after this) |
This example is similar to the one-way unicast example discussed above, but
uses a broadcast address and the SO_BROADCAST socket option:
struct soap soap; struct SOAP_ENV__Header header; // the SOAP Header soap_init(&soap); soap.send_timeout = 1; // 1s timeout soap.connect_flags = SO_BROADCAST; // required for broadcast soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header header.wsa__MessageID = "message ID"; header.wsa__To = "server URL"; header.wsa__Action = "server action"; soap.header = &header; // bind the SOAP Header for transport // Send the message over UDP to a broadcast address: if (soap_send_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!")) soap_print_fault(&soap, stderr); // report error soap_end(&soap); // cleanup soap_destroy(&soap); // cleanup soap_done(&soap); // close connection (should not use soap struct after this) |
This example assumes that the gSOAP header file includes the SOAP Header with
WS-Addressing elements and the ns__echoString function discussed in
Section 18.1
struct soap soap; struct SOAP_ENV__Header header; // the SOAP Header struct wsa__EndpointReferenceType replyTo; // (anonymous) reply address char *res; // server response soap_init(&soap); soap.send_timeout = 1; // 1s timeout soap.recv_timeout = 1; // 1s timeout soap_default_SOAP_ENV__Header(&soap, &header); // init SOAP Header soap_default_wsa__EndpointReferenceType(&soap, &replyTo); // init reply address replyTo.Address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; header.wsa__MessageID = "message ID"; header.wsa__To = "server URL"; header.wsa__Action = "server action"; header.wsa__ReplyTo = &replyTo; soap.header = &header; // bind the SOAP Header for transport // Send and receive messages over UDP: if (soap_call_ns__echoString(&soap, "soap.udp://...", NULL, "hello world!", &res)) { if (soap.error == SOAP_EOF && soap.errnum == 0) // Timeout: no response from server (message already delivered?) else soap_print_fault(&soap, stderr); } else // UDP server response is stored in 'res' // check SOAP header received, if applicable check_header(&soap.header); soap_end(&soap); // cleanup soap_destroy(&soap); // cleanup soap_done(&soap); // close connection (should not use soap struct after this) |
This example is similar to the request-response unicast example discussed
above, but uses a broadcast address and the SO_BROADCAST socket option.
Because we expect to receive multiple responses, we also need to use separate
request-response messages to send one request and consume multiple responses.
In this example we defined a bcastString request and a
bcastStringResponse response message, which are essentially declared as
one-way messages in the header file:
//gsoap ns service method-header-part: bcastString wsa__MessageID //gsoap ns service method-header-part: bcastString wsa__To //gsoap ns service method-header-part: bcastString wsa__Action //gsoap ns service method-header-part: bcastString wsa__ReplyTo int ns__bcastString(char *str, void); //gsoap ns service method-header-part: bcastStringResponse wsa__MessageID //gsoap ns service method-header-part: bcastStringResponse wsa__To //gsoap ns service method-header-part: bcastStringResponse wsa__Action //gsoap ns service method-header-part: bcastStringResponse wsa__RelatesTo int ns__bcastStringResponse(char *res, void); |
struct soap soap; struct SOAP_ENV__Header header; struct wsa__EndpointReferenceType replyTo; char *res; soap_init(&soap); soap.connect_flags = SO_BROADCAST; soap.send_timeout = 1; // 1s timeout soap.recv_timeout = 1; // 1s timeout soap_default_SOAP_ENV__Header(&soap, &header); soap.header = &header; soap_default_wsa__EndpointReferenceType(&soap, &replyTo); replyTo.Address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; header.wsa__MessageID = "message ID"; header.wsa__To = "server URL"; header.wsa__Action = "server action"; header.wsa__ReplyTo = &replyTo; if (soap_send_ns__bcastString(&soap, "soap.udp://...", NULL, "hello world!")) soap_print_fault(&soap, stderr); else { for (;;) { if (soap_recv_ns__bcastStringResponse(&soap, &res)) break; // Got response 'res' from a server } if (soap.error == SOAP_EOF && soap.errnum == 0) // Timeout: no more messages received else soap_print_fault(&soap, stderr); } soap_end(&soap); // cleanup soap_destroy(&soap); // cleanup soap_done(&soap); // close connection (should not use soap struct after this) |
The following example code illustrates a SOAP-over-UDP server for one-way sendString and request-response echoString messages.
This example assumes that the gSOAP header file includes the SOAP Header with
WS-Addressing elements and the ns__echoString function discussed in
Section 18.1.
main() { struct soap soap; soap_init1(&soap, SOAP_IO_UDP); // must set UDP flag // bind to host (NULL=current host) and port: if (!soap_valid_socket(soap_bind(&soap, host, port, 100))) { soap_print_fault(&soap, stderr); exit(1); } for (;;) { if (soap_serve(&soap)) soap_print_fault(&soap, stderr); // report the problem soap_destroy(&soap); soap_end(&soap); } soap_done(&soap); // close connection } int ns__echoString(struct soap *soap, char *str, char **res) { if (!soap->header) return soap_sender_fault(soap, "No SOAP header", NULL); if (!soap->header->wsa__MessageID) return soap_sender_fault(soap, "No WS-Addressing MessageID", NULL); soap->header->wsa__RelatesTo = (struct wsa__Relationship*)soap_malloc(soap, sizeof(struct wsa__Relationship)); soap_default_wsa__Relationship(soap, soap->header->wsa__RelatesTo); soap->header->wsa__RelatesTo->__item = soap->header->wsa__MessageID; // must check for duplicate messages if (check_received(soap->header->wsa__MessageID)) { // Request message already received return SOAP_STOP; // don't return response } if (!soap->header->wsa__ReplyTo || !soap->header->wsa__ReplyTo->Address) return soap_sender_fault(soap, "No WS-Addressing ReplyTo address", NULL); soap->header->wsa__To = soap->header->wsa__ReplyTo->Address; soap->header->wsa__MessageID = soap_strdup(soap, soap_int2s(soap, id_count++)) ; soap->header->wsa__Action = "http://genivia.com/udp/echoStringResponse"; *res = str; return SOAP_OK; } int ns__sendString(struct soap *soap, char *str) { if (!soap->header) return SOAP_STOP; if (!soap->header->wsa__MessageID) return SOAP_STOP; // must check for duplicate messages if (check_received(soap->header->wsa__MessageID)) return SOAP_STOP; return SOAP_OK; } int ns__sendStringResponse(struct soap *soap, char *res) { return SOAP_NO_METHOD; } // we don't expect to serve this message |
gSOAP uses regular strings by default. Regular strings cannot be used to hold
UCS characters outside of the character range [1,255]. gSOAP can handle
wide-character content in two ways. First, applications can utilize
wide-character strings (wchar_t*) instead of regular strings to store
wide-character content. For example, the xsd:string string schema type
can be declared as a wide-character string and used subsequently:
typedef wchar_t *xsd__string; ... int ns__myMethod(xsd__string input, xsd__string *output); |
Both regular strings and wide-character strings can be used together within an application.
For example, the following header file declaration introduces two string schema types:
typedef wchar_t *xsd__string; typedef char *xsd__string_; // trailing '_' avoids name clash ... int ns__myMethod(xsd__string input, xsd__string_ *output); |
Please consult the UTF-8 specification for details on the UTF-8 format. Note that the ASCII character set [1-127] is a subset of UTF-8. Therefore, with the SOAP_C_UTFSTRING flag set, strings may hold ASCII character data and UTF-8 extensions.
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 8.2.8 and 10.4). In addition, the sample SOAP/XML request and response files generated by the compiler are valid provided that XML Schema namespace information is added to the header file with directives so that the gSOAP compiler can produce example SOAP/XML messages that are correctly namespace qualified. These compiler directive are specified as //-comments. (Note: blanks can be used anywhere in the directive, except between // and gsoap.)
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 name of the WSDL definitions in the header file, use:
//gsoap namespace-prefix service definitions: definitions-name |
To specify the documentation of a Web Service in the header file, use:
//gsoap namespace-prefix service documentation: text |
To specify the portType of a Web Service in the header file, use:
//gsoap namespace-prefix service portType: portType-name |
//gsoap namespace-prefix service type: portType-name |
To specify the binding name of a Web Service in the header file, use:
//gsoap namespace-prefix service binding: binding-name |
To specify the binding's transport protocol of a Web Service in the header file, use:
//gsoap namespace-prefix service transport: transport-URL |
To specify the location (or port endpoint) of a Web Service in the header file, use:
//gsoap namespace-prefix service location: URL |
//gsoap namespace-prefix service port: URL |
To specify the name of the executable of a Web Service in the header file, use:
//gsoap namespace-prefix service executable: executable-name |
When doc/literal encoding is required for the entire service, the service encoding can be specified in the header file as follows:
//gsoap namespace-prefix service encoding: literal |
//gsoap namespace-prefix service encoding: encoding-style |
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 |
The directive above specifies a new schema and the gSOAP compiler generates a schema files (.xsd) file for the schema.
An existing schema namespace URI can be imported with:
//gsoap namespace-prefix schema import: namespace-URI |
A schema namespace URI can be imported from a location with:
//gsoap namespace-prefix schema namespace: namespace-URI //gsoap namespace-prefix schema import: schema-location |
The elementFormDefault and attributeFormDefault qualification of a schema can be defined with:
//gsoap namespace-prefix schema elementForm: qualified //gsoap namespace-prefix schema attributeForm: qualified |
//gsoap namespace-prefix schema elementForm: unqualified //gsoap namespace-prefix schema attributeForm: unqualified |
//gsoap namespace-prefix schema form: qualified |
//gsoap namespace-prefix schema form: unqualified |
To document a data type, use:
//gsoap namespace-prefix schema type-documentation: type-name //text |
//gsoap ns schema type-documentation: tdata stores <a href="transaction.html">transaction</a> data class ns__tdata { ... } |
To document a method, use:
//gsoap namespace-prefix service method-documentation: method-name //text |
//gsoap ns service method-documentation: getQuote returns a <i>stock quote</i> int ns__getQuote(char *symbol, float &_result); |
To specify the SOAPAction for a method, use:
//gsoap namespace-prefix service method-action: method-name action |
//gsoap ns service method-action: getQuote "" int ns__getQuote(char *symbol, float &_result); |
When document style is preferred for a particular service method, use:
//gsoap namespace-prefix service method-style: method-name document |
When SOAP RPC encoding is required for a particular service method, use:
//gsoap namespace-prefix service method-encoding: method-name encoded |
//gsoap namespace-prefix service method-encoding: method-name literal |
//gsoap namespace-prefix service method-encoding: method-name encoding-style |
When SOAP RPC encoding is required for a particular service method response when the request message is literal, use:
//gsoap namespace-prefix service method-response-encoding: method-name encoded |
//gsoap namespace-prefix service method-response-encoding: method-name literal |
//gsoap namespace-prefix service method-response-encoding: method-name encoding-style |
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 h__transaction //gsoap ns service method-header-part: login h__authentication int ns__login(...); |
//gsoap ns service method-header-part: search h__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 |
struct SOAP_ENV__Header { char *h__transaction; struct UserAuth *h__authentication; }; //gsoap ns service method-input-header-part: login h__authentication //gsoap ns service method-input-header-part: login h__transaction //gsoap ns service method-output-header-part: login h__transaction int ns__login(...); |
To specify MIME attachments for the method input and output (method request and response messages), use:
//gsoap namespace-prefix service method-mime-type: method-name mime-type |
To specify MIME attachments for the method input (method request message), use:
//gsoap namespace-prefix service method-input-mime-type: method-name mime-type |
//gsoap namespace-prefix service method-output-mime-type: method-name mime-type |
The use of directives 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 style: rpc //gsoap ns3 service encoding: encoded //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 only a few members of a class or struct need not be (de)serialized, or the base class of a derived class should not be (de)serialized. Certain built-in classes such as ostream cannot be (de)serialized. Data parts that should be kept invisible to gSOAP are called ``transient''. Transient data types and transient struct/class members are declared with the extern keyword or are declared within [ and ] blocks in the header file. 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 19.5.
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 ] }; |
While transient data types are supposed to be hidden from gSOAP, volatile data types are visible to gSOAP but their declaration and implementation is assumed to be hidden. That is, volatile data types are assumed to be part of an existing non-modifiable software package, such as a built-in library. It would not make sense to redefine the data types in a gSOAP header file. When you need to (de)serialize such data types, you must declare them in a gSOAP header file and use the volatile qualifier.
Consider for example struct tm, declared in time.h. The structure may actually vary between platforms, but the tm structure includes at least the following fields:
volatile struct tm { int tm_sec; /* seconds (0 - 60) */ int tm_min; /* minutes (0 - 59) */ int tm_hour; /* hours (0 - 23) */ int tm_mday; /* day of month (1 - 31) */ int tm_mon; /* month of year (0 - 11) */ int tm_year; /* year - 1900 */ int tm_wday; /* day of week (Sunday = 0) */ int tm_yday; /* day of year (0 - 365) */ int tm_isdst; /* is summer time in effect? */ char *tm_zone; /* abbreviation of timezone name */ long tm_gmtoff; /* offset from UTC in seconds */ }; |
time_t T = time(NULL); struct tm *t = localtime(&T); struct soap *soap = soap_new(); soap_set_omode(soap, SOAP_XML_GRAPH); // good habit to use this soap_begin_send(soap); soap_put_tm(soap, t, "myLocalTime", NULL); soap_end_send(soap); soap_end(soap); soap_done(soap); free(soap); |
If you must produce a schema file, say time.xsd, that defines an XML
schema and namespace for the tm struct, you can add a typedef
declaration to the header file:
typedef struct tm time__struct_tm; |
Classes should be declared volatile to prevent modification of these classes by gSOAP. gSOAP adds serialization methods to classes to support polymorphism. However, this is a problem when you can't modify class declarations because they are part of a non-modifiable software package. The solution is to declare these classes volatile, similar to the tm structure example illustrated above. You can also use a typedef to associate a schema with a class.
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)serializers
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_serialize_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_serialize_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; 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 schema 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. The namespace prefixes are associated with the type names of typedefs (Section 11.2) for primitive data types, struct/class names, and enum names.
To prevent the output of these xsi:type attributes in the XML serialization, you can simply use type declarations that do not include these namespace prefixes. That is, don't use the typedefs for primitive types and use unqualified type names with structs, classes, and enums.
However, there are two issues. Firstly, if you want to use a primitive schema
type that has no C/C++ counterpart, you must declare it as a typedef
name with a leading underscore, as in:
typedef char *_xsd__date; |
struct _ns__myStruct { ... }; |
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_set_omode(&soap, SOAP_XML_GRAPH); 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 | O_CREAT, 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() { struct soap 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/2001/XMLSchema-instance"}, {"xsd", "http://www.w3.org/2001/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.1 by default. You can revert to HTTP 1.0 as follows:
struct soap soap; soap_init(&soap); ... soap.http_version = "1.0"; |
The client-side handling of HTTP 307 code "Temporary Redirect" and any of the redirect codes 301, 302, and 303 are not automated in gSOAP. Client application developers may want to consider adding a few lines of code to support redirects. It was decided not to automatically support redirects for the following reasons:
char *endpoint = NULL; // use default endpoint given in WSDL (or add another one here) int n = 10; // max redirect count while (n-) { if (soap_call_ns1__myMethod(soap, endpoint, ...)) { if ((soap->error >= 301 && soap->error <= 303) || soap->error == 307) endpoint = soap->endpoint; // endpoint from HTTP 301, 302, 303, 307 Location header else { ... report and handle error break; } } else break; } |
A gSOAP server normally only grants HTTP (and HTTPS) POST requests. To support HTTP (HTTPS) GET, you need to set the soap.fget callback. The callback is required to produce a response to the request in textual form, such as a Web page or a SOAP/XML response.
The following example produces a Web page upon a HTTP GET request (e.g. from a browser):
struct soap *soap = soap_new(); soap->fget = http_get(); ... soap_serve(soap); ... int http_get(struct soap *soap) { soap_response(soap, SOAP_HTML); // HTTP response header with text/html soap_send(soap, "<HTML>My Web server is operational.</HTML>"); soap_end_send(soap); return SOAP_OK; } |
int http_get(struct soap *soap) { char *s = strchr(soap->path, '?'); if (!s || strcmp(s, "?wsdl")) return SOAP_GET_METHOD; fd = fopen("myservice.wsdl", "rb"); // open WSDL file to copy if (!fd) return 404; // return HTTP not found error soap->http_content = "text/xml"; // HTTP header with text/xml content soap_response(soap, SOAP_FILE); for (;;) { r = fread(soap->tmpbuf, 1, sizeof(soap->tmpbuf), fd); if (!r) break; if (soap_send_raw(soap, soap->tmpbuf, r)) break; // can't send, but little we can do about that } fclose(fd); soap_end_send(soap); return SOAP_OK; } |
int http_get(struct soap *soap) { if ((soap->omode & SOAP_IO) != SOAP_IO_CHUNK) soap_set_omode(soap, SOAP_IO_STORE); // if not chunking we MUST buffer entire content to determine content length soap_response(soap, SOAP_OK); return soap_send_ns1__mySendMethodResponse(soap, "", NULL, ... params ...); } |
int ns1__mySendMethodResponse(... params ..., void); |
gSOAP supports keep-alive socket connections. To activate keep-alive support,
set the SOAP_IO_KEEPALIVE flag for both input and output modes, see Section 9.12.
For example
struct soap soap; soap_init2(&soap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE); |
signal(SIGPIPE, sigpipe_handle); |
void sigpipe_handle(int x) { } |
soap.socket_flags = MSG_NOSIGNAL; |
A connection will be kept open only if the request contains an HTTP 1.0 header with "Connection: Keep-Alive" or an HTTP 1.1 header that does not contain "Connection: close". This means that a gSOAP client method call should use "http://" in the endpoint URL of the request to the stand-alone service to ensure HTTP headers are used.
If the client does not close the connection, the server will wait forever when
no recv_timeout is specified. In addition, other clients will be denied
service as long as a client keeps the connection to the server open. To
prevent this from happening, the service should be multi-threaded such that
each thread handles the client connection:
int main(int argc, char **argv) { struct soap soap, *tsoap; pthread_t tid; int m, s; soap_init2(&soap, SOAP_IO_KEEPALIVE, SOAP_IO_KEEPALIVE); soap.max_keep_alive = 100; // optional: at most 100 calls per keep-alive session soap.accept_timeout = 600; // optional: let server time out after ten minutes of inactivity m = soap_bind(&soap, NULL, 18000, BACKLOG); // use port 18000 on the current machine if (m < 0) { soap_print_fault(&soap, stderr); exit(1); } fprintf(stderr, "Socket connection successful %d\n", m); for (count = 0; count > = 0; count++) { soap.socket_flags = MSG_NOSIGNAL; // use this soap.accept_flags = SO_NOSIGPIPE; // or this to prevent sigpipe s = soap_accept(&soap); if (s < 0) { if (soap.errnum) soap_print_fault(&soap, stderr); else fprintf(stderr, "Server timed out\n"); // Assume timeout is long enough for threads to complete serving requests break; } fprintf(stderr, "Accepts socket %d connection from IP %d.%d.%d.%d\n", s, (int)(soap.ip >> 24)&0xFF, (int)(soap.ip >> 16)&0xFF, (int)(soap.ip >> 8)&0xFF, (int)soap.ip&0xFF); tsoap = soap_copy(&soap); pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap); } return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); ((struct soap*)soap)->recv_timeout = 300; // Timeout after 5 minutes stall on recv ((struct soap*)soap)->send_timeout = 60; // Timeout after 1 minute stall on send soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); soap_end((struct soap*)soap); soap_done((struct soap*)soap); free(soap); return NULL; } |
A gSOAP client call will automatically attempt to re-establish a connection to a server when the server has terminated the connection for any reason. This way, a sequence of calls can be made to the server while keeping the connection open. Client stubs will poll the server to check if the connection is still open. When the connection was terminated by the server, the client will automatically reconnect.
A client should reset SOAP_IO_KEEPALIVE just before the last call to a server to close the connection after this last call. This will close the socket after the call and also informs the server to gracefully close the connection.
gSOAP supports HTTP chunked transfer encoding. Un-chunking of inbound messages takes place automatically. Outbound messages are never chunked, except when the SOAP_IO_CHUNK flag is set for the output mode. Most Web services, however, will not accept chunked inbound messages.
The entire outbound message can be stored to determine the HTTP content length rather than the two-phase encoding used by gSOAP which requires a separate pass over the data to determine the length of the outbound message. Setting the flag SOAP_IO_STORE for the output mode will buffer the entire message. This can speed up the transmission of messages, depending on the content, but may require significant storage space to hold the verbose XML message.
Zlib compressed transfers require buffering. The SOAP_IO_STORE flag is set when the SOAP_ENC_ZLIB flag is set to send compressed messages. The use of chunking significantly reduces memory usage and may speed up the transmission of compressed SOAP/XML messages. This is accomplished by setting the SOAP_IO_CHUNK flag with SOAP_ENC_ZLIB for the output mode.
HTTP authentication (basic) is enabled at the client-side by setting the soap.userid and soap.passwd strings to a username and password, respectively. A server may request user authentication and denies access (HTTP 401 error) when the client tries to connect without HTTP authentication (or with the wrong authentication information).
Here is an example client code fragment to set the HTTP authentication username and password:
struct soap soap; soap_init(&soap); soap.userid = "guest"; soap.passwd = "visit"; ... |
POST /XXX HTTP/1.0 Host: YYY User-Agent: gSOAP/2.2 Content-Type: text/xml; charset=utf-8 Content-Length: nnn Authorization: Basic Z3Vlc3Q6Z3Vlc3Q= ... |
When present, the value of the WWW-Authenticate HTTP header with the authentication realm can be obtained from the soap.authrealm string. This is useful for clients to respond intelligently to authentication requests.
A stand-alone gSOAP Web Service can enforce HTTP authentication upon clients, by checking the soap.userid and soap.passwd strings. These strings are set when a client request contains HTTP authentication headers. The strings SHOULD be checked in each service method (that requires authentication to execute).
Here is an example service method implementation that enforced client authentication:
int ns__method(struct soap *soap, ...) { if (!soap->.userid || !soap->.passwd || strcmp(soap->.userid, "guest") || strcmp(soap->.passwd, "visit")) return 401; ... } |
HTTP proxy authentication (basic) is enabled at the client-side by setting the
soap.proxy_userid and soap.proxy_passwd strings to a username and
password, respectively. For example, a proxy server may request user
authentication. Otherwise, access is denied by the proxy (HTTP 407 error).
Example client code fragment to set proxy server, username, and password:
struct soap soap; soap_init(&soap); soap.proxy_host = "xx.xx.xx.xx"; // IP soap.proxy_port = 8080; soap.proxy_userid = "guest"; soap.proxy_passwd = "guest"; ... |
POST /XXX HTTP/1.0 Host: YYY User-Agent: gSOAP/2.2 Content-Type: text/xml; charset=utf-8 Content-Length: nnn Proxy-Authorization: Basic Z3Vlc3Q6Z3Vlc3Q= ... |
Here are some tips you can use to speed up gSOAP. gSOAP's default settings are choosen to maximize portability and compatibility. The settings can be tweaked to optimize the performance as follows:
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.
gSOAP's socket communications can be controlled with socket options and flags. The gSOAP run-time environment struct soap flags are: int soap.socket_flags to control socket send() and recv() calls, int soap.connect_flags to set client connection socket options, int soap.bind_flags to set server-side port bind socket options, int soap.accept_flags to set server-side request message accept socket options. See the manual pages of send and recv for soap.socket_flags values and see the manual pages of setsockopt for soap.connect_flags, soap.bind_flags, and soap.accept_flags (SOL_SOCKET) values. These SO_ socket option flags (see setsockopt manual pages) can be bit-wise or-ed to set multiple socket options at once. The client-side flag soap.connect_flags=SO_LINGER is supported with values l_onoff=1 and l_linger=0.
For example, to disable sigpipe signals on Unix/Linux platforms use: soap.socket_flags=MSG_NOSIGNAL and/or soap.connect_flags=SO_NOSIGPIPE (i.e. client-side connect) depending on your platform.
Use soap.bind_flags=SO_REUSEADDR to enable server-side port reuse and local port sharing (but be aware of the security issues when the port is not blocked by a firewall and open to the Internet).
When a Web Service is installed as CGI, it uses standard I/O that is encrypted/decrypted by the Web server that runs the CGI application. Therefore, HTTPS/SSL support must be configured for the Web server (not CGI-based Web Service application itself).
To enable OpenSSL, first install OpenSSL and use option -DWITH_OPENSSL to compile the sources with your C or C++ compiler, for example:
g++ -DWITH_OPENSSL -o myprog myprog.cpp stdsoap2.cpp soapC.cpp soapServer.cpp -lssl -lcrypto |
Let's take a look at an example SSL secure
multi-threaded stand-alone SOAP Web Service:
int main() { int m, s; pthread_t tid; struct soap soap, *tsoap; if (CRYPTO_thread_setup()) { fprintf(stderr, "Cannot setup thread mutex\n"); exit(1); } soap_init(&soap); if (soap_ssl_server_context(&soap, SOAP_SSL_DEFAULT, "server.pem", /* keyfile: required when server must authenticate to clients (see SSL docs on how to obtain this file) */ "password", /* password to read the key file */ "cacert.pem", /* optional cacert file to store trusted certificates */ NULL, /* optional capath to directory with trusted certificates */ "dh512.pem", /* DH file, if NULL use RSA */ NULL, /* if randfile!=NULL: use a file with random data to seed randomness */ NULL /* optional server identification to enable SSL session cache (must be a unique name) */ )) { soap_print_fault(&soap, stderr); exit(1); } m = soap_bind(&soap, NULL, 18000, 100); // use port 18000 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); break; } tsoap = soap_copy(&soap); /* should call soap_ssl_accept on a copy */ if (!tsoap) break; if (soap_ssl_accept(tsoap)) { soap_print_fault(tsoap, stderr); soap_done(tsoap); free(tsoap); continue; /* when soap_ssl_accept fails, we should just go on */ } pthread_create(&tid, NULL, &process_request, (void*)tsoap); } soap_done(&soap); /* deallocates SSL context */ CRYPTO_thread_cleanup(); return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); soap_end((struct soap*)soap); soap_done((struct soap*)soap); free(soap); return NULL; } |
The CRYPTO_thread_setup() and CRYPTO_thread_cleanup() routines can be found in openssl/crypto/threads/th-lock.c. These routines are required to setup locks for multi-threaded applications that use SSL.
We give a Windows and POSIX threads implementation of these here:
#include < unistd.h > /* defines _POSIX_THREADS if pthreads are available */ #ifdef _POSIX_THREADS # include < pthread.h > #endif #if defined(WIN32) # define MUTEX_TYPE HANDLE # define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL) # define MUTEX_CLEANUP(x) CloseHandle(x) # define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE) # define MUTEX_UNLOCK(x) ReleaseMutex(x) # define THREAD_ID GetCurrentThreadID() #elif defined(_POSIX_THREADS) # define MUTEX_TYPE pthread_mutex_t # define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) # define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) # define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) # define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) # define THREAD_ID pthread_self() #else # error "You must define mutex operations appropriate for your platform" # error "See OpenSSL /threads/th-lock.c on how to implement mutex on your platform" #endif struct CRYPTO_dynlock_value { MUTEX_TYPE mutex; }; static MUTEX_TYPE *mutex_buf; static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line) { struct CRYPTO_dynlock_value *value; value = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value)); if (value) MUTEX_SETUP(value->mutex); return value; } static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(l->mutex); else MUTEX_UNLOCK(l->mutex); } static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line) { MUTEX_CLEANUP(l->mutex); free(l); } void locking_function(int mode, int n, const char *file, int line) { if (mode & CRYPTO_LOCK) MUTEX_LOCK(mutex_buf[n]); else MUTEX_UNLOCK(mutex_buf[n]); } unsigned long id_function() { return (unsigned long)THREAD_ID; } int CRYPTO_thread_setup() { int i; mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); if (!mutex_buf) return SOAP_EOM; for (i = 0; i < CRYPTO_num_locks(); i++) MUTEX_SETUP(mutex_buf[i]); CRYPTO_set_id_callback(id_function); CRYPTO_set_locking_callback(locking_function); CRYPTO_set_dynlock_create_callback(dyn_create_function); CRYPTO_set_dynlock_lock_callback(dyn_lock_function); CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function); return SOAP_OK; } void CRYPTO_thread_cleanup() { int i; if (!mutex_buf) return; CRYPTO_set_id_callback(NULL); CRYPTO_set_locking_callback(NULL); CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); i++) MUTEX_CLEANUP(mutex_buf[i]); free(mutex_buf); mutex_buf = NULL; } |
signal(SIGPIPE, sigpipe_handle); |
void sigpipe_handle(int x) { } |
if (soap_ssl_server_context(&soap, SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION, "server.pem", "password", "cacert.pem", NULL, "dh512.pem", NULL, NULL )) { soap_print_fault(&soap, stderr); exit(1); } |
The cacert file and capath are optional. Either one can be specified when clients must run on non-trusted systems. We want to avoid storing trusted certificates in the default location on the file system when that is not secure. Therefore, a flat cacert.pem file or directory can be specified to store trusted certificates.
The gSOAP distribution includes a cacerts.pem file with the certificates of all certificate authorities such as Verisign. You can use this file to verify the authentication of servers that provide certificates issued by these CAs.
The cacert.pem, client.pem, and server.pem files in the gSOAP distribution are examples of self-signed certificates.
Caution: it is important that the WITH_OPENSSL macro MUST be consistently defined to compile the sources, such as stdsoap2.cpp, soapC.cpp, soapClient.cpp, soapServer.cpp, and all application sources that include stdsoap2.h or soapH.h. If the macros are not consistently used, the application will crash due to a mismatches in the declaration and access of the gSOAP environment.
You need to install the OpenSSL library on your platform to enable secure SOAP clients to utilize HTTPS/SSL.
After installation, compile all the sources of 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 |
if (soap_ssl_client_context(&soap, SOAP_SSL_DEFAULT, "client.pem", /* keyfile: required only when client must authenticate to server (see SSL docs on how to obtain this file) */ "password", /* password to read the key file */ "cacert.pem", /* optional cacert file to store trusted certificates (needed to verify server) */ NULL, /* optional capath to direcoty with trusted certificates */ NULL /* if randfile!=NULL: use a file with random data to seed randomness */ )) { soap_print_fault(&soap, stderr); exit(1); } soap_call_ns__mymethod(&soap, "https://domain/path/secure.cgi", "", ...); |
By default, server authentication is enabled. To disable server authentication for testing purposes, use the following:
if (soap_ssl_client_context(&soap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL )) { soap_print_fault(&soap, stderr); exit(1); } |
The cacert file and capath are optional. Either one can be specified when clients must run on non-trusted systems. We want to avoid storing trusted certificates in the default location on the file system when that is not secure. Therefore, a flat cacert.pem file or directory can be specified to store trusted certificates.
Make sure you have signal handlers set in your application to catch broken connections (SIGPIPE):
signal(SIGPIPE, sigpipe_handle); |
void sigpipe_handle(int x) { } |
gSOAP provides a callback function for authentication initialization:
|
The gSOAP distribution includes a cacerts.pem file with the certificates of all certificate authorities (such as Verisign). You can use this file to verify the authentication of servers that provide certificates issued by these CAs. Just set the cafile parameter to the location of this file on your file system. Therefore, when you obtain a certifice signed by a trusted CA such as Verisign, you can simply use the cacerts.pem file to develop client applications that can verify the authenticity of your server.
The other .pem files in the gSOAP distribution are examples of self-signed certificates for testing purposes (cacert.pem, client.pem, server.pem).
You can also create your own self-signed certificates. There is more than one way to generate the necessary files for clients and servers. See http://www.openssl.org for information on OpenSSL and http://sial.org/howto/openssl/ca/ on how to setup and manage a local CA and http://sial.org/howto/openssl/self-signed/ on how to setup self-signed test certificates.
It is also possible to convert IIS-generated certificates to PEM format, see http://www.icewarp.com/Knowledgebase/617.htm for a discussion and examples.
Here is the simplest way to setup self-signed certificates. First you need to create a private Certificate Authority (CA). The CA is used in SSL to verify the authenticity of a given certificate. The CA acts as a trusted third party who has authenticated the user of the signed certificate as being who they say. The certificate is signed by the CA, and if the client trusts the CA, it will trust your certificate. For use within your organization, a private CA will probably serve your needs. However, if you intend use your certificates for a public service, you should probably obtain a certificate from a known CA (e.g. VeriSign). In addition to identification, your certificate is also used for encryption.
Creating certificates should be done through a CA to obtain signed certificates. But you can create your own certificates for testing purposes as follows.
Finally you need to generate Diffie-Helmann parameters for the server if you don't want to use RSA. Do the following at the prompt:
openssl dhparam -outform PEM -out dh.pem 512
File dh512.pem is the output file and 512 is the number of bits used.
You can specify a hardware engine to enable hardware support for cryptographic acceleration. This can be done once in a server or client with the following statements:
static const char *engine = "cswift"; /* engine name */ int main() { ... ENGINE *e; if (!(e = ENGINE_by_id(engine))) fprintf(stderr, "Error finding engine %s\n", engine); else if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) fprintf(stderr, "Error using engine %s\n", engine); ... |
|
Set the full path to libssl.lib and libcrypto.lib under the MSVC++ "Projects" menu, then choose "Link": Öbject/Modules". The path to libssl32.dll and libeay32.dll need to be specified in the PATH environment variable when running gSOAP applications.
Alternatively, you can use the WinInet interface available in the mod_gsoap directory of the gSOAP package. API instructions are included in the source.
To enable deflate and gzip compression with Zlib, install Zlib from http://www.zlib.org if not already installed on your system. Compile stdsoap2.cpp (or stdsoap2.c) and all your sources that include stdsoap2.h or soapH.h with compiler option -DWITH_GZIP and link your code with the Zlib library, e.g. -lz on Unix/Linux platforms.
The gzip compression is orthogonal to all transport encodings such as HTTP, SSL, DIME, and can be used with other transport layers. You can even save and load compressed XML data to/from files.
gSOAP supports two compression formats: deflate and gzip. The gzip format is used by default. The gzip format has several benefits over deflate. Firstly, gSOAP can automatically detect gzip compressed inbound messages, even without HTTP headers, by checking for the presence of a gzip header in the message content. Secondly, gzip includes a CRC32 checksum to ensure messages have been correctly received. Thirdly, gzip compressed content can be decompressed with other compression software, so you can decompress XML data saved by gSOAP in gzip format.
Gzip compression is enabled by compiling the sources with -DWITH_GZIP.
To transmit gzip compressed SOAP/XML data, set the output mode flags to
SOAP_ENC_ZLIB. For example:
soap_init(&soap); ... soap_set_omode(&soap, SOAP_ENC_ZLIB); // enable Zlib's gzip if (soap_call_ns__myMethod(&soap, ...)) ... soap_clr_omode(&soap, SOAP_ENC_ZLIB); // disable Zlib's gzip ... |
To control the level of compression for outbound messages, you can set the soap.z_level to a value between 1 and 9, where 1 is the best speed and 9 is the best compression (default is 6). For example
soap_init(&soap); ... soap_set_omode(&soap, SOAP_ENC_ZLIB); soap.z_level = 9; // best compression ... |
soap_call_ns__myMethod(&soap, ...); ... printf("Compression ratio: %f%% (in) %f%% (out)\n", 100*soap.z_ratio_out, 100*soap.z_ratio_in); ... |
Compressed transfers require buffering the entire output message to determine HTTP message length. This means that the SOAP_IO_STORE flag is automatically set when the SOAP_ENC_ZLIB flag is set to send compressed messages. The use of HTTP chunking significantly reduces memory usage and may speed up the transmission of compressed SOAP/XML messages. This is accomplished by setting the SOAP_IO_CHUNK flag with SOAP_ENC_ZLIB for the output mode. However, some Web servers do not accept HTTP chunked request messages (even when they return HTTP chunked messages!). Stand-alone gSOAP services always accept chunked request messages.
To restrict the compression to the deflate format only, compile the sources with -DWITH_ZLIB. This limits compression and decompression to the deflate format. Only plain and deflated messages can be exchanged, gzip is not supported with this option. Receiving gzip compressed content is automatic, even in the absence of HTTP headers. Receiving deflate compressed content is not automatic in the absence of HTTP headers and requires the flag SOAP_ENC_ZLIB to be set for the input mode to decompress deflated data.
Caution: it is important that the WITH_GZIP and WITH_ZLIB macros MUST be consistently defined to compile the sources, such as stdsoap2.cpp, soapC.cpp, soapClient.cpp, soapServer.cpp, and all application sources that include stdsoap2.h or soapH.h. If the macros are not consistently used, the application will crash due to a mismatches in the declaration and access of the gSOAP environment.
Client-side cookie support is optional. To enable cookie support, compile all sources 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. An example cookie file manager is included as an extras in the distribution. You should explicitly remove all cookies before terminating a gSOAP environment by calling soap_free_cookies(soap) or by calling soap_done(soap).
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: 1 = got cookie from client */ short modified; /* server-side: 1 = client cookie was modified */ struct soap_cookie *next; }; |
Server-side cookie support is optional. To enable cookie support, compile all sources 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 all sources (including your application sources that use stdsoap2.h) with option -DWITH_FASTCGI or add
#define WITH_FASTCGI |
To compile gSOAP applications intended for small memory devices, you may want to remove all non-essential features that consume precious code and data space. To do this, compile the gSOAP sources with -DWITH_LEAN (i.e. #define WITH_LEAN) to remove many non-essential features. The features that will be disabled are:
It is safe to try to compile your application with -DWITH_LEAN, provided that your application does not rely on I/O timeouts. When no linkage error occurs in the compilation process, it is safe to assume that your application will run just fine.
The stdsoap2.c and stdsoap2.cpp gSOAP runtime libraries should be linked with a BSD socket library in the project build, e.g. winsock for Win32. To eliminate the need to link a socket library, you can compile stdsoap2.c (for C) and stdsoap2.cpp (for C++) with the -DWITH_NOIO macro set (i.e. #define WITH_NOIO). This eliminates the dependency on the BSD socket API, IO streams, FILE type, and errno.
As a consequence, you MUST define callbacks to replace the missing socket stack. To do so, add to your code the following definitions:
struct soap soap; soap_init(&soap); /* fsend is used to transmit data in blocks */ soap.fsend = my_send; /* frecv is used to receive data in blocks */ soap.frecv = my_recv; /* fopen is used to connect */ soap.fopen = my_tcp_connect; /* fclose is used to disconnect */ soap.fclose = my_tcp_disconnect; /* fclosesocket is used only to close the master socket in a server upon soap_done() */ soap.fclosesocket = my_tcp_closesocket; /* fshutdownsocket is used after completing a send operation to send TCP FIN */ soap.fshutdownsocket = my_tcp_shutdownsocket; /* setting fpoll is optional, leave it NULL to omit polling the server */ soap.fpoll = my_poll; /* faccept is used only by a server application */ soap.faccept = my_accept; |
You cannot use soap_print_fault and soap_print_fault_location to print error diagnostics. Instead, the value of soap.error, which contains the gSOAP error code, can be used to determine the cause of a fault.
The wsdl2h tool can be used to import multiple WSDLs and schemas at once. The service definitions are combined in one header file to be parsed by soapcpp2. It is important to assign namespace prefixes to namespace URIs using the typemap.dat file. Otherwise, wsdl2h will assign namespace prefixes ns1, ns2, and so on to the service operations and schema types. Thus, any change to a WSDL or schema may result in a new prefix assignment. For more details, please see Section 8.2.11.
For example, consider the XMethods delayed stock quote and exchange rate services. We can import both WSDLs at once with:
wsdl2h -s -o qx.h http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl |
soapcpp2 -C qx.h |
You will notice that the qx.h file contains a very lengthy service name "net_x002exmethods_x002eservices_x002estockquote_x002eStockQuoteBinding". Since this is undesirable for naming files, operations, and proxies, we manually change its name into "StockQuote" and re-run soapcpp2.
This gives us a couple of files we need to build the application in C++:
soapStub.h soapH.h soapC.cpp soapClient.cpp soapStockQuoteProxy.h soapCurrencyExchangeBindingProxy.h StockQuote.nsmap |
#include "soapStockQuoteProxy.h" #include "soapCurrencyExchangeBindingProxy.h" #include "StockQuote.nsmap" main() { StockQuote stock; CurrencyExchangeBinding exchange; float quote, rate; stock.ns1__getQuote(ÏBM", quote); // get quote for IBM if (stock.soap->error) exit(1); exchange.ns2__getRate(üs", ük", rate); // get US to UK rate if (exchange.soap->error) exit(1); cout << ÏBM in UK pounds = " << rate*quote << endl; } |
Note that the prefixes ns1 and ns2 can be changed using the typemap.dat file for wsdl2h, see Section 8.2.11.
Another approach to combine multiple client and service applications into one executable is by using C++ namespaces to logically separate the definitions or by creating C libraries for the client/server objects as explained in subsequent sections.
You can use a C++ code namespace of your choice in your header file to build a client or server in that code namespace. In this way, you can create multiple clients and servers that can be combined and linked together without conflicts, which is explained in more detail in the next section (which also shows an example combining two client libraries defined in two C++ code namespaces).
At most one namespace can be defined for the entire gSOAP header file. The code
namespace MUST completely encapsulate the entire contents of the header file:
namespace myNamespaceName { ... gSOAP header file contents ... } |
Because the SOAP Header and Fault serialization codes will also be placed in
the namespace, they cannot be called from the stdsoap2.cpp run time
library code and are therefore rendered unusable. Therefore, these serializers
are not compiled at all (enforced with #define WITH_NOGLOBAL). To add SOAP
Header and Fault serializers, you MUST compile them separately as follows.
First, create a new header file env.h with the SOAP Header and Fault
definitions. You can leave this header file empty if you want to use the
default SOAP Header and Fault. Then compile this header file with:
soapcpp2 -penv env.h |
The gSOAP compiler produces soapClientLib.cpp and soapServerLib.cpp codes that are specifically intended for building static or dynamic client/server libraries. These codes export the stubs and skeletons, but keep all marshaling code (i.e. parameter serializers and deserializers) local (i.e. as static functions) to avoid link symbol conflicts when combining multiple clients and/or servers into one executable. Note that it is far simpler to use the wsdl2h tool on multiple WSDL files to generate a header file that combines all service definitions. However, the approach presented in this section is useful when creating (dynamic) libraries for client and server objects, such as DLLs as described in Section 19.35.
To build multiple libraries in the same project directory, you can define a C++ code namespace in your header file (see Section 19.33) or you can use soapcpp2 with option -p to rename the generated soapClientLib.cpp and soapServerLib.cpp (and associated) files. The -p option specifies the file name prefix to replace the soap prefix. The libraries don't have to be C++ codes. You can use option -c to generate C code. A clean separation of libraries can also be achieved with C++ code namespaces, see Section 19.33.
The library codes do not define SOAP Header and Fault serializers. You MUST
add SOAP Header and Fault serializers to your application, which are compiled
separately as follows. First, create a new header file env.h with the
SOAP Header and Fault definitions. You can leave this header file empty if you
want to use the default SOAP Header and Fault. Then compile this header file
with:
soapcpp2 -c -penv env.h |
You MUST compile the stdsoap2.cpp library using -DWITH_NONAMESPACES:
g++ -DWITH_NONAMESPACES -c stdsoap2.cpp |
For example, suppose we have two clients defined in header files client1.h and client2.h. We first generate the envH.h file for the SOAP Header and Fault definitions:
soapcpp2 -c -penv env.h |
soapcpp2 -c -n -pmyClient1 client1.h soapcpp2 -c -n -pmyClient2 client2.h |
#include "envH.h" // include this file first! #include "myClient1H.h" // include client 1 stubs #include "myClient2H.h" // include client 2 stubs ... #include "myClient1H.nsmap" // include client 1 nsmap #include "myClient2H.nsmap" // include client 2 nsmap ... soap_init(&soap); soap_set_namespaces(&soap, myClient1_namespaces); ... make Client 1 invocations ... ... soap_set_namespaces(&soap, myClient2_namespaces); ... make Client 2 invocations ... |
Note: Link conflicts may still occur in the unlikely situation that identical remote method names are defined in two or more client stubs or server skeletons when these methods share the same XML namespace prefix. You may have to use C++ code namespaces to avoid these link conflicts or rename the namespace prefixes used by the remote method defined in the header files.
As an example we will build a Delayed Stock Quote client library and a Currency Exchange Rate client library.
First, we create an empty header file env.h (which may contain optional SOAP Header and Fault definitions), and compile it as follows:
soapcpp2 -penv env.h g++ -c envC.cpp |
g++ -c -DWITH_NONAMESPACES stdsoap2.cpp |
Second, we create the Delayed Stock Quote header file specification, which may be obtained using the WSDL importer. If you want to use C++ namespaces then you need to manually add the namespace declaration to the generated header file:
namespace quote { //gsoap ns service name: Service //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://services.xmethods.net/soap //gsoap ns schema namespace: urn:xmethods-delayed-quotes //gsoap ns service method-action: getQuote "" int ns__getQuote(char *symbol, float &Result); } |
soapcpp2 -n quote.h g++ -c quoteClientLib.cpp |
soapcpp2 -n -pquote quote.h g++ -c quoteClientLib.cpp |
namespace rate { //gsoap ns service name: Service //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://services.xmethods.net/soap //gsoap ns schema namespace: urn:xmethods-CurrencyExchange //gsoap ns service method-action: getRate "" int ns__getRate(char *country1, char *country2, float &Result); } |
soapcpp2 -n rate.h g++ -c rateServerProxy.cpp |
#include "quoteServiceProxy.h" // get quote Service proxy #include "rateServiceProxy.h" // get rate Service proxy #include "quote.nsmap" // get quote namespace bindings #include "rate.nsmap" // get rate namespace bindings int main(int argc, char *argv[]) { if (argc < = 1) { std::cerr << "Usage: main ticker [currency]" << std::endl exit(0); } quote::Service quote; float q; if (quote.getQuote(argv[1], q)) // get quote soap_print_fault(quote.soap, stderr); else { if (argc > 2) { rate::Service rate; float r; if (rate.getRate("us", argv[2], r)) // get rate in US dollars soap_print_fault(rate.soap, stderr); else q *= r; // convert the quote } std::cout << argv[1] << ": " << q << std::endl; } return 0; } |
To compile and link a server object is very similar. For example, assume that we need to implement a calculator service and we want to create a library for it.
namespace calc { //gsoap ns service name: Service //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://www.cs.fsu.edu/ engelen/calc.cgi //gsoap ns schema namespace: urn:calc int ns__add(double a, double b, double &result); int ns__sub(double a, double b, double &result); int ns__mul(double a, double b, double &result); int ns__div(double a, double b, double &result); } |
soapcpp2 -n calc.h g++ -c calcServiceObject.cpp |
#include "calcServiceObject.h" // get Service object #include "calc.nsmap" // get calc namespace bindings ... calc::Service calc; calc.serve(); // calls request dispatcher to invoke one of the functions below ... int calc::Service::add(double a, double b, double &result); { result = a + b; returnSOAP_OK; } int calc::Service::sub(double a, double b, double &result); { result = a - b; returnSOAP_OK; } int calc::Service::mul(double a, double b, double &result); { result = a * b; returnSOAP_OK; } int calc::Service::div(double a, double b, double &result); { result = a / b; returnSOAP_OK; } |
This is the same example as above, but the clients are build with pure C.
First, we create an empty header file (which may contain optional SOAP Header and Fault definitions), and compile it as follows:
soapcpp2 -c -penv env.h gcc -c envC.c |
gcc -c -DWITH_NONAMESPACES stdsoap2.c |
//gsoap ns service name: Service //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://services.xmethods.net/soap //gsoap ns schema namespace: urn:xmethods-delayed-quotes //gsoap ns service method-action: getQuote "" int ns__getQuote(char *symbol, float *Result); |
soapcpp2 -c -n -pquote quote.h gcc -c quoteClientLib.c |
//gsoap ns service name: Service //gsoap ns service style: rpc //gsoap ns service encoding: encoded //gsoap ns service location: http://services.xmethods.net/soap //gsoap ns schema namespace: urn:xmethods-CurrencyExchange //gsoap ns service method-action: getRate "" int ns__getRate(char *country1, char *country2, float *Result); |
soapcpp2 -c -n -prate rate.h gcc -c rateClientLib.c |
#include "quoteStub.h" // get quote Service stub #include "rateStub.h" // get rate Service stub #include "quote.nsmap" // get quote namespace bindings #include "rate.nsmap" // get rate namespace bindings int main(int argc, char *argv[]) { if (argc < = 1) { fprintf(stderr, "Usage: main ticker [currency]\n"); exit(0); } struct soap soap; float q; soap_init(&soap); soap_set_namespaces(&soap, quote_namespaces); if (soap_call_ns__getQuote(&soap, "http://services.xmethods.net/soap", "", argv[1], &q)) // get quote soap_print_fault(&soap, stderr); else { if (argc > 2) { soap_set_namespaces(&soap, rate_namespaces); float r; if (soap_call_ns__getRate(&soap, "http://services.xmethods.net/soap", "", "us", argv[2], &r)) // get rate in US dollars soap_print_fault(&soap, stderr); else q *= r; // convert the quote } printf("%s: %f \n", argv[1], q); } return 0; } |
To compile and link a server library is very similar. Assuming that the server is named ``calc'' (as specified with options -n and -p), the application needs to include the calcStub.h file, link the calcServerLib.o file, and call calc_serve(&soap) function at run time.
First, create a new header file env.h with the SOAP Header and Fault
definitions. You can leave this header file empty if you want to use the
default SOAP Header and Fault. Then compile this header file with:
soapcpp2 -penv env.h |
The next step is to create stdsoap2.dll which consists of the file stdsoap2.cpp and envC.cpp. This DLL contains all common functions needed for all other clients and servers based on gSOAP. Compile envC.cpp and stdsoap2.cpp into stdsoap2.dll using the C++ compiler option -DWITH_NONAMESPACES and the MSVC Pre-Processor definitions SOAP_FMAC1=__declspec(dllexport) and SOAP_FMAC3=__declspec(dllexport) (or you can compile with -DWITH_SOAPDEFS_H and put the macro definitions in soapdefs.h). This exports all functions which are preceded by the macro SOAP_FMAC1 in the soapcpp2.cpp source file and macro SOAP_FMAC3 in the envC.cpp source file.
Compile the soapClientLib.cpp and soapServerLib.cpp sources as DLLs by using the MSVC Pre-Processor definitions SOAP_FMAC5=__declspec(dllexport) and SOAP_CMAC=__(dllexport), and by using the C++ compiler option -DWITH_NONAMESPACES. This DLL links to stdsoap2.dll.
To create multiple DLLs in the same project directory, you SHOULD use option -p to rename the generated soapClientLib.cpp and soapServerLib.cpp (and associated) files. The -p option specifies the file name prefix to replace the soap prefix. A clean separation of libraries can also be achieved with C++ namespaces, see Section 19.33.
Unless you use the client proxy and server object classes (soapXProxy.h and soapXObject.h where X is the name of the service), all client and server applications MUST explicitly set the namespaces value of the gSOAP environment:
soap_init(&soap); soap_set_namespaces(&soap, namespaces); |
The gSOAP plug-in feature enables a convenient extension mechanism of gSOAP
capabilities. When the plug-in registers with gSOAP, it has full access
to the run-time settings and the gSOAP function callbacks.
Upon registry, the plug-in's local data is associated with the gSOAP run-time.
By overriding gSOAP's function callbacks with the plug-in's function callbacks,
the plug-in can extend gSOAP's capabilities. The local plug-in data can be
accessed through a lookup function, usually invoked within a callback function
to access the plug-in data.
The registry and lookup functions are:
int soap_register_plugin_arg(struct soap *soap, int (*fcreate)(struct soap *soap, struct soap_plugin *p, void *arg), void *arg) void* soap_lookup_plugin(struct soap*, const char*); |
int soap_copy(struct soap *soap); void soap_done(struct soap *soap); |
An example will be used to illustrate these functions. This example overrides the send and receive callbacks to copy all messages that are sent and received to the terminal (stderr).
First, we write a header file plugin.h to define the local plug-in data
structure(s) and we define a global name to identify the plug-in:
#include "stdsoap2.h" #define PLUGIN_ID "PLUGIN-1.0" // some name to identify plugin struct plugin_data // local plugin data { int (*fsend)(struct soap*, const char*, size_t); // to save and use send callback size_t (*frecv)(struct soap*, char*, size_t); // to save and use recv callback }; int plugin(struct soap *soap, struct soap_plugin *plugin, void *arg); |
#include "plugin.h" static const char plugin_id[] = PLUGIN_ID; // the plugin id static int plugin_init(struct soap *soap, struct plugin_data *data); static int plugin_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src); static void plugin_delete(struct soap *soap, struct soap_plugin *p); static int plugin_send(struct soap *soap, const char *buf, size_t len); static size_t plugin_recv(struct soap *soap, char *buf, size_t len); // the registry function: int plugin(struct soap *soap, struct soap_plugin *p, void *arg) { p->id = plugin_id; p->data = (void*)malloc(sizeof(struct plugin_data)); p->fcopy = plugin_copy; /* optional: when set the plugin must copy its local data */ p->fdelete = plugin_delete; if (p->data) if (plugin_init(soap, (struct plugin_data*)p->data)) { free(p->data); // error: could not init return SOAP_EOM; // return error } return SOAP_OK; } static int plugin_init(struct soap *soap, struct plugin_data *data) { data->fsend = soap->fsend; // save old recv callback data->frecv = soap->frecv; // save old send callback soap->fsend = plugin_send; // replace send callback with new soap->frecv = plugin_recv; // replace recv callback with new return SOAP_OK; } // copy plugin data, called by soap_copy() // This is important: we need a deep copy to avoid data sharing by two run-time environments static int plugin_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src) { if (!(dst->data = (struct plugin_data*)malloc(sizeof(struct plugin_data)))) return SOAP_EOM; *dst->data = *src->data; return SOAP_OK; } // plugin deletion, called by soap_done() static void plugin_delete(struct soap *soap, struct soap_plugin *p) { free(p->data); // free allocated plugin data } // the new send callback static int plugin_send(struct soap *soap, const char *buf, size_t len) { struct plugin_data *data = (struct plugin_data*)soap_lookup_plugin(soap, plugin_id); // fetch plugin's local data fwrite(buf, len, 1, stderr); // write message to stderr return data->fsend(soap, buf, len); // pass data on to old send callback } // the new receive callback static size_t plugin_recv(struct soap *soap, char *buf, size_t len) { struct plugin_data *data = (struct plugin_data*)soap_lookup_plugin(soap, plugin_id); // fetch plugin's local data size_t res = data->frecv(soap, buf, len); // get data from old recv callback fwrite(buf, res, 1, stderr); return res; } |
A plugin is copied with the soap_copy() call. This function copies a soap struct and the chain of plugins. It is up to the plugin implementation to share the plugin data or not:
struct soap soap; soap_init(&soap); soap_register_plugin(&soap, plugin); ... soap_done(&soap); |
A number of example plug-ins are included in the gSOAP package's plugin directory. Some of these plug-ins are discussed.
The message logging and access statistics plug-in can be used to selectively log inbound and outbound messages to a file or stream. It also keeps access statistics to log the total number of bytes sent and received.
To use the plug-in, compile and link your application with logging.c located in the plugin directory of the package.
To enable the plug-in in your code, register the plug-in and set the streams as follows:
#include "logging.h" ... if (soap_register_plugin(&soap, logging)) soap_print_fault(&soap, stderr); // failed to register ... struct logging_data *logdata; logdata = (struct logging_data*)soap_lookup_plugin(&soap, logging_id); if (!logdata) ... // if the plug-in registered OK, there is certainly data but can't hurt to check logdata->inbound = stdout; // log to stdout logdata->outbound = stdout; // log to stdout ... process messages ... logdata->inbound = NULL; // don't log logdata->outbound = NULL; // don't log ... process messages ... size_t bytes_in = logdata->stat_recv; size_t bytes_out = logdata->stat_sent; |
The HTTP GET plug-in allows your server to handle HTTP GET requests as well as SOAP-based POST request. HTTP GET requests can also be handled with the fget callback, see Section 19.7. However, the HTTP GET plug-in also keeps statistics on the number of successful POST and GET exchanges and failed operations (HTTP faults, SOAP Faults, etc.). It also keeps hit histograms accumulated for up to a year.
To use the plug-in, compile and link your application with httpget.c located in the plugin directory of the package.
To enable the plug-in in your code, register the plug-in with your HTTP GET handler function as follows:
#include "httpget.h" ... if (soap_register_plugin_arg(&soap, httpget, (void*)my_http_get_handler)) soap_print_fault(&soap, stderr); // failed to register ... struct http_get_data *httpgetdata; httpgetdata = (struct http_get_data*)soap_lookup_plugin(&soap, http_get_id); if (!httpgetdata) ... // if the plug-in registered OK, there is certainly data but can't hurt to check ... process messages ... size_t get_ok = httpgetdata->stat_get; size_t post_ok = httpgetdata->stat_post; size_t errors = httpgetdata->stat_fail; ... time_t now = time(NULL); struct tm *T; T = localtime(&now); size_t hitsthisminute = httpgetdata->min[T->tm_min]; size_t hitsthishour = httpgetdata->hour[T->tm_hour]; size_t hitstoday = httpgetdata->day[T->tm_yday]; |
int my_http_get_handler(struct soap) { soap->http_content = "text/html"; soap_response(soap, SOAP_FILE); soap_send(soap, «html>Hello</html>"); soap_end_send(soap); return SOAP_OK; // return SOAP_OK or HTTP error code, e.g. 404 } |
The HTTP MD5 plug-in works in the background to automatically verify the content of messages using MD5 checksums. With the plug-in, messages can be transferred over (trusted but) unreliable connections. The plug-in can be used on the client side and server side.
To use the plug-in, compile and link your application with httpmd5.c and md5evp.c located in the plugin directory of the package. The md5evp.c implementation uses the EVP interface to compute MD5 checksums with OpenSSL (compiled with -DWITH_OPENSSL).
To enable the plug-in in your code, register the plug-in as follows:
#include "httpmd5.h" ... if (soap_register_plugin(&soap, http_md5)) soap_print_fault(&soap, stderr); // failed to register |
The plug-in requires you to set the SOAP_IO_STORE flag when sending SOAP with attachments:
#include "httpmd5.h" ... struct soap soap; soap_init1(&soap, SOAP_IO_STORE); if (soap_register_plugin(&soap, http_md5) soap_print_fault(&soap, stderr); // failed to register ... now safe to send SOAP with attachments ... |
The HTTP digest authentication plug-in enables a more secure authentication scheme compared to basic authentication. HTTP basic authentication sends unencrypted userids and passwords over the net, while digest authentication does not exchange passwords but exchanges checksums of passwords (and other data such as nonces to avoid replay attacks). For more details, please see RFC 2617.
The HTTP digest authentication can be used next to the built-in basic authentication, or basic authentication can be rejected to tighten security. The server must have a database with userid's and passwords (in plain text form). The client, when challenged by the server, checks the authentication realm provided by the server and sets the userid and passwords for digest authentication. The client application can temporarily store the userid and password for a sequence of message exchanges with the server, which is faster than repeated authorization challenges and authentication responses.
At the client side, the plug-in is registered and service invocations are checked for authorization challenges (HTTP error code 401). When the server challenges the client, the client should set the userid and password and retry the invocation. The client can determine the userid and password based on the authentication realm part of the server's challenge. The authentication information can be temporarily saved for multiple invocations.
Client-side example:
#include "httpda.h" ... if soap_register_plugin(&soap, http_da)) soap_print_fault(&soap, stderr); // failed to register ... if (soap_call_ns__method(&soap, ...) != SOAP_OK) { if (soap.error == 401) // challenge: HTTP authentication required { if (!strcmp(soap.authrealm, authrealm)) // determine authentication realm { struct http_da_info info; // to store userid and passwd http_da_save(&soap, &info, authrealm, userid, passwd); // set userid and passwd for this realm if (soap_call_ns__method(&soap, ...) == SOAP_OK) // retry { ... soap_end(&soap); // userid and passwd were deallocated http_da_restore(&soap, &info); // restore userid and passwd if (!soap_call_ns__method(&soap, ...) == SOAP_OK) // another call ... http_da_release(&soap, &info); // remove userid and passwd |
The server can challenge a client using HTTP code 401. With the plug-in, HTTP digest authentication challenges are send. Without the plug-in, basic authentication challenges are send.
Each server method can implement authentication as desired and may enforce
digest authentication or may also accept basic authentication responses. To
verify digest authentication responses, the server should compute and compare
the checksums using the plug-in's http_da_verify_post function for
HTTP POST requests (and http_da_verify_get for HTTP GET requests with
the HTTP GET plugin) as follows:
#include "httpda.h" ... if (soap_register_plugin(&soap, http_da)) soap_print_fault(&soap, stderr); // failed to register ... soap_serve(&soap); ... int ns__method(struct soap *soap, ...) { if (soap->userid && soap->passwd) // client used basic authentication { // may decide not to handle, but if ok then go ahead and compare info: if (!strcmp(soap->userid, userid) && !strcmp(soap->passwd, passwd)) { ... handle request ... return SOAP_OK; } } else if (soap->authrealm && soap->userid) // Digest authentication { passwd = ... // database lookup on userid and authrealm to find passwd if (!strcmp(soap->authrealm, authrealm) && !strcmp(soap->userid, userid)) { if (!http_da_verify_post(soap, passwd)) { ... handle request ... return SOAP_OK; } } } soap->authrealm = authrealm; // set realm for challenge return 401; // Not authorized, challenge digest authentication } |