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); |
Second, regular strings can be used to hold wide-character content in UTF-8 format. This is accomplished with the SOAP_C_UTFSTRING flag (for both input/output mode), see Section 9.12. With this flag set, gSOAP will deserialize XML into regular strings in UTF-8 format. An application is responsible for filling regular strings with UTF-8 content to ensure that strings can be correctly serialized XML. Third, the SOAP_C_MBSTRING flag (for both input/output mode) can be used to activate multibyte character support. Multibyte support depends on the locale settings for dealing with extended natural language encodings. 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); |
The input string parameter is a wide-character string and the output string parameter is a regular string. The regular string has UCS character content in the range [1,255] unless the SOAP_C_UTFSTRING flag is set. With this flag, the string has UTF-8 encoded content. 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 soapcpp2 tool 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 7.2.9 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 soapcpp2 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 service operation 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 |
where namespace-prefix is a namespace prefix used by identifiers in the header file and service-name is the name of a Web Service (only required to create new Web Services). The name may be followed by text up to the end of the line which is incorporated into the WSDL service documentation. Alternatively, the service documentation can be provided with the directive below. To specify the name of the WSDL definitions in the header file, use:
//gsoap namespace-prefix service definitions: definitions-name |
where namespace-prefix is a namespace prefix used by identifiers in the header file and definitions-name is the name of the WSDL definitions. By default, the WSDL definitions name is the same as the service name. To specify the documentation of a Web Service in the header file, use:
//gsoap namespace-prefix service documentation: text |
where namespace-prefix is a namespace prefix used by identifiers in the header file and text is the documentation text up to the end of the line. The text is incorporated into the WSDL service documentation. To specify the portType of a Web Service in the header file, use:
//gsoap namespace-prefix service portType: portType-name |
or just
//gsoap namespace-prefix service type: portType-name |
or using WSDL 2.0 terms
//gsoap namespace-prefix service interface: portType-name |
where namespace-prefix is a namespace prefix used by identifiers in the header file and portType-name is the portType name of the WSDL service portType. To specify the port name of a Web Service in the header file, use:
//gsoap namespace-prefix service portName: port-name |
where namespace-prefix is a namespace prefix used by identifiers in the header file and port-name is the name of the WSDL service port element. By default, the port name is the same as the service name. To specify the binding name of a Web Service in the header file, use:
//gsoap namespace-prefix service binding: binding-name |
where namespace-prefix is a namespace prefix used by identifiers in the header file and binding-name is the binding name of the WSDL service binding element. By default, the binding name is the same as the service name. To specify the binding's transport protocol of a Web Service in the header file, use:
//gsoap namespace-prefix service transport: transport-URL |
where namespace-prefix is a namespace prefix used by identifiers in the header file and transport-URL is the URL of the transport protocol such as http://schemas.xmlsoap.org/soap/http for HTTP. HTTP transport is assumed by default. To specify the location (or port endpoint) of a Web Service in the header file, use:
//gsoap namespace-prefix service location: URL |
or alternatively
//gsoap namespace-prefix service endpoint: URL |
or
//gsoap namespace-prefix service port: URL |
where URL is the location of the Web Service (only required to create new Web Services). The URL specifies the path to the service executable (so URL/service-executable is the actual location of the executable when declared). To specify the name of the executable of a Web Service in the header file, use:
//gsoap namespace-prefix service executable: executable-name |
where executable-name is the name of the executable of the Web Service. 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 |
or when the SOAP-ENV:encodingStyle attribute is different from the SOAP 1.1/1.2 encoding style:
//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 |
where namespace-URI is the URI associated with the namespace prefix. In addition, the schema namespace URI can be specified in the header file:
//gsoap namespace-prefix schema namespace: namespace-URI |
where namespace-URI is the schema URI associated with the namespace prefix. If present, it defines the schema-part of the generated WSDL file and the URI in the namespace mapping table. This declaration is useful when the service declares its own data types that need to be associated with a namespace. Furthermore, the header file for client applications do not need the full service details and the specification of the schema namespaces for namespace prefixes suffices. In addition, a second namespace can be defined that is only used to match the namespaces of inbound XML:
//gsoap namespace-prefix schema namespace2: namespace-URI-pattern |
If the first namespace does not match the inbound parsed XML, then the second will be tried. This pattern may contain '*' multichar wildcards and '-' single chard wildcards. This allows two or more namespace versions to be handled by the same namespace prefix. The directive above specifies a new schema and the gSOAP soapcpp2 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 |
where namespace-URI is the schema URI associated with the namespace prefix. gSOAP does not produce XML Schema files for imported schemas and imports the schema namespaces in the generated WSDL file. 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 |
or:
//gsoap namespace-prefix schema elementForm: unqualified //gsoap namespace-prefix schema attributeForm: unqualified |
A shortcut to define the default qualification of elements and attributes of a schema:
//gsoap namespace-prefix schema form: qualified |
or:
//gsoap namespace-prefix schema form: unqualified |
To include xsi:type attributes in the runtime XML element output for specific schemas, use:
//gsoap namespace-prefix schema typed: yes |
Note that soapcpp2 -t enables xsi:type for all elements in the runtime XML output. To document a data type, use:
//gsoap namespace-prefix schema type-documentation: type-name //text |
where type-name is the unqualified name of the data type and text is a line of text terminated by a newline. Do not use any XML reserved characters in text such as < and > . Use well-formed XML and XHTML markup instead. For example:
//gsoap ns schema type-documentation: tdata stores <a href="transaction.html">transaction</a> data class ns__tdata { ... } |
To document a data type's fields and members, use:
//gsoap namespace-prefix schema type-documentation: type-name::field //text |
where type-name is the unqualified name of the data type, field is a field, member, or enum name, and text is a line of text terminated by a newline. Do not use any XML reserved characters in text such as < and > . Use well-formed XML and XHTML markup instead. For example:
//gsoap ns schema type-documentation: tdata::id the transaction number //gsoap ns schema type-documentation: tdata::state transaction state //gsoap ns schema type-documentation: tstate::INIT initial state //gsoap ns schema type-documentation: tstate::DONE final state class ns__tdata { @int id; enum ns__tstate { INIT, DONE } state; ... } |
The documentation form above can also be used to document SOAP/XML message parts in the generated WSDL. For the type-name use the function name. For the field names, you can use the function name and/or the function argument names. To document a method, use:
//gsoap namespace-prefix service method-documentation: method-name //text |
where method-name is the unqualified name of the method and text is a line of text terminated by a newline. Do not use any XML reserved characters in text such as < and > . Use well-formed XML and XHTML markup instead. For example:
//gsoap ns service method-documentation: getQuote returns a <i>stock quote</i> int ns__getQuote(char *symbol, float &_result); |
To specify the SOAP Action for a SOAP method, use:
//gsoap namespace-prefix service method-action: method-name action |
where method-name is the unqualified name of the method and action is a string without spaces and blanks (the string can be quoted when preferred). For example:
//gsoap ns service method-action: getQuote "" int ns__getQuote(char *symbol, float &_result); |
Or, alternatively for the input action (part of the request):
//gsoap ns service method-input-action: getQuote "" int ns__getQuote(char *symbol, float &_result); |
To specify the HTTP "location" of REST methods to a perform POST/GET/PUT action, use:
//gsoap namespace-prefix service method-action: method-name action |
where method-name is the unqualified name of the method and action is a string without spaces and blanks (the string can be quoted when preferred). This directive requires that the protocol: directive for this method is set to HTTP, POST, GET, or PUT. A response action and fault action are defined by:
//gsoap namespace-prefix service method-output-action: method-name action //gsoap namespace-prefix service method-fault-action: method-name action |
To override the SOAP or REST protocol of an operation (SOAP by default), use:
//gsoap namespace-prefix service method-protocol: method-name protocol |
where protocol is one of
|
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 |
When literal encoding is required for a particular service method, use:
//gsoap namespace-prefix service method-encoding: method-name literal |
or when the SOAP-ENV:encodingStyle attribute is different from the SOAP 1.1/1.2 encoding style, use:
//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 |
When literal encoding is required for a particular service method response when the request message is encoded, use:
//gsoap namespace-prefix service method-response-encoding: method-name literal |
or when the SOAP-ENV:encodingStyle attribute is different from the SOAP 1.1/1.2 encoding style, use:
//gsoap namespace-prefix service method-response-encoding: method-name encoding-style |
Note that the method-response-encoding is set to the value of method-encoding by default. 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 |
For example:
struct SOAP_ENV__Header { char *h__transaction; struct UserAuth *h__authentication; }; |
Suppose method ns__login uses both header parts (at most), then this is declared as:
//gsoap ns service method-header-part: login h__transaction //gsoap ns service method-header-part: login h__authentication int ns__login(...); |
Suppose method ns__search uses only the first header part, then this is declared as:
//gsoap ns service method-header-part: search h__transaction int ns__search(...); |
Note that the method name and header part names MUST be namespace qualified. The headers MUST be present in all operations that declared the header parts. 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 |
Similarly, to specify the header parts for the method output (method response message), use:
//gsoap namespace-prefix service method-output-header-part: method-name header-part |
The declarations above only affect the WSDL. For example:
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(...); |
The headers MUST be present in all operations that declared the header parts. 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 |
You can repeat this directive for all multipartRelated MIME attachments you want to associate with the method. To specify MIME attachments for the method input (method request message), use:
//gsoap namespace-prefix service method-input-mime-type: method-name mime-type |
Similarly, to specify MIME attachments for the method output (method response message), use:
//gsoap namespace-prefix service method-output-mime-type: method-name mime-type |
You can repeat these directives for all multipartRelated MIME attachments you want to associate with the method.
The use of directives is best illustrated with an example. The example uses a hypothetical stock quote service and exchange rate service, actual services such as these are available for free on the web.
//gsoap ns1 service namespace: urn:GetQuote int ns1__getQuote(char *symbol, float &result); //gsoap ns2 service namespace: urn: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 port: http://www.mydomain.com/quotex.cgi //gsoap ns3 service namespace: urn:quotex int ns3__getQuote(char *symbol, char *country, float &result); |
The quotex.h example is a new Web Service created by combining two existing Web Services: a Stock Quote service and a Currency Exchange service. Namespace prefix ns3 is used for the new quotex Web Service with namespace URI urn:quotex, service name quotex, and endpoint port http://www.mydomain.com/quotex.cgi. Since the new Web Service invokes the ns1__getQuote and ns2__getRate service operations, the service namespaces and other details such as style and encoding of these methods are given by directives. After invoking the gSOAP soapcpp2 tool on the quotex.h header file:
> soapcpp2 quotex.h |
the WSDL of the new quotex Web Service is saved as quotex.wsdl. Since the service name, endpoint port, and namespace URI were provided in the header file, the generated WSDL file can be published together with the compiled Web Service installed as a CGI application. 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" |
The automatic generation and inclusion of the namespace mapping table requires compiler directives for all namespace prefixes to associate each namespace prefix with a namespace URI. Otherwise, namespace URIs have to be manually added to the table (they appear as http://tempuri.org).
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 soapcpp2 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 ... }; |
Second example:
[ class myBase // base class need not be (de)serialized { ... }; ] class ns__myDerived : myBase { ... }; |
Third example:
[ 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 ] }; |
In this example, class ns__myClass has three transient fields: b, s, and n which will not be (de)serialized in SOAP. Field n is transient because the type is declared within a transient block. Pointers, references, and arrays of transient types are transient. The single class method is encapsulated within [ and ] to prevent gSOAP from creating (de)serializers for the char[1024] type. gSOAP will generate (de)serializers for all types that are not declared within a [ and ] transient block.
Volatile-declared data types in gSOAP 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. In certain cases it could also be problematic to have classes augmented with serializer methods. When you need to (de)serialize such data types "as is", 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 */ }; |
Note that we qualified the structure volatile in the gSOAP header file to inform the gSOAP soapcpp2 compiler that it should not attempt to redeclare it. We can now readily serialize and deserialize the tm structure. The following program fragment serializes the local time stored in a tm structure to stdout:
struct soap *soap = soap_new(); ... time_t T = time(NULL); struct tm *t = localtime(&T); struct soap *soap = soap_new(); soap_write_tm(soap, t); soap_destroy(soap); soap_end(soap); soap_free(soap); // detach and free context |
It is also possible to serialize the tm fields as XML attributes using the @qualifier, see Section 11.6.7. 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; |
We used the typedef name time__struct_tm rather than time__ tm, because a schema name clash will occur with the latter since taking off the time prefix will result in the same name being used. Classes should be declared volatile to prevent modification of these classes by ithe gSOAP soapcpp2 source code output. Note that 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 }; |
The user is required to supply the following routines for each extern typedef'ed name T:
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) |
The function prototypes can be found in soapH.h. 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; |
More information on custom (de)serialization will be provided in this document or in a separate document in the future. The writing of the (de)serializer code requires the use of the low-level gSOAP API.
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.3) 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; |
This will produce the necessary xsd:date information in the WSDL output by the gSOAP soapcpp2 compiler. But the XML serialization of this type at run time won't include the xsi:type attribute. Secondly, to include the proper schema definitions in the WSDL produced by the gSOAP soapcpp2 compiler, you should use qualified struct, class, and enum names with a leading underscore, as in:
struct _ns__myStruct { ... }; |
This ensures that myStruct is associated with a schema, and therefore included in the appropriate schema in the generated WSDL. The leading underscore prevents the XML serialization of xsi:type attributes for this type in the SOAP/XML payload.
gSOAP provides five callback functions for customized I/O and HTTP handling:
|
|
|
In addition, a void*user field in the struct soap data structure is available to pass user-defined data to the callbacks. The following example uses I/O function callbacks for customized serialization of data into a fixed-size 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; } int 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_TREE); 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_destroy(&soap); soap_end(&soap); soap_done(&soap); // disable callbacks } |
A fixed-size buffer to store the outbound message sent is not flexible to handle large content. To store the message in an expanding buffer, use for example:
struct buffer { size_t len; size_t max; char *buf; }; int main() { struct buffer *h = malloc(sizeof(struct buffer)); h->len = 0; h->max = 0; h->buf = NULL; soap.user = (void *)h; // pass buffer as a handle to the callback soap.fsend = mysend; // assign callback ... if (h->len) { ... // use h->buf[0..h->len-1] content // then cleanup: h->len = 0; h->max = 0; free(h->buf); h->buf = NULL; } ... } int mysend(struct soap *soap, const char *s, size_t n) { struct buffer *h = (struct buffer*)soap->user; // get buffer through handle int m = h->max, k = h->len + n; // need to increase space? if (m == 0) m = 1024; else while (k > = m) m *= 2; if (m != h->max) { char *buf = malloc(m); memcpy(buf, h->buf, h->len); h->max = m; if(h->buf) free(h->buf); h->buf = buf; } memcpy(h->buf + h->len, s, n); h->len += n; return SOAP_OK; } |
The soap_done function can be called to reset the callback to the default internal gSOAP I/O and HTTP handlers. 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 service operation 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); |
The client program is:
#include "soapH.h" ... SOAP_SOCKET myopen(struct soap *soap, const char *endpoint, const char *host, int port) { if (strncmp(endpoint, "file:", 5)) { printf("File name expected\n"); return SOAP_INVALID_SOCKET; } if ((soap->sendfd = soap->recvfd = open(host, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) < 0) return SOAP_INVALID_SOCKET; return soap->sendfd; } 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; } int 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 } |
SOAP 1.1 and 1.2 specify that XML elements may be ignored when present in a SOAP payload on the receiving side. gSOAP ignores XML elements that are unknown, unless the XML attribute mustUnderstand="true" is present in the XML element. It may be undesirable for elements to be ignored when the outcome of the omission is uncertain. The soap.fignore callback can be set to a function that returns SOAP_OK in case the element can be safely ignored, or SOAP_MUSTUNDERSTAND to throw an exception, or to perform some application-specific action. For example, to throw an exception as soon as an unknown element is encountered on the input, use:
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); |
To selectively throw an exception as soon as an unknown element is encountered but element ns:xyz can be safely ignored, use:
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} |
Function soap_match_tag compares two tags. The third parameter may be a pattern where * is a wildcard and - is a single character wildcard. So for example soap_match_tag(tag, "ns:*") will match any element in namespace ns or when no namespace prefix is present in the XML message. 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"; |
This sets the HTTP version and reconfigures the engine to revert to HTTP 1.0. Note that you cannot use HTTP chunking with HTTP 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:
To implement client-side 307 redirect, add the following lines of code:
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_strdup(soap, soap->endpoint); // endpoint from HTTP 301, 302, 303, 307 Location header else { ... report and handle error break; } } else break; } |
To implement your own HTTP (HTTPS) GET request responses, 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. This method does not work with CGI. 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; } |
The example below produces a WSDL file upon a HTTP GET with path ?wsdl:
int http_get(struct soap *soap) { FILE *fd = NULL; 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 (;;) { size_t 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; } |
Using one-way SOAP/XML message, you can also return a SOAP/XML response:
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 ...); } |
where ns1__mySendMethodResponse is a one-way message declared in a gSOAP header file as:
int ns1__mySendMethodResponse(... params ..., void); |
The generated soapClient.cpp includes the sending-side stub function.
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); |
When a client or a service communicates with another client or service that supports keep alive, the attribute soap.keep_alive will be set to 1, otherwise it is reset to 0 (indicating that the other party will close the connection). The connection maybe terminated on either end before the communication completed, for example when the server keep-alive connection has timed out. This generates a "Broken Pipe" signal on Unix/Linux platforms. This signal can be caught with a signal handler:
signal(SIGPIPE, sigpipe_handle); |
where, for example:
void sigpipe_handle(int x) { } |
Alternatively, broken pipes can be kept silent by setting:
soap.socket_flags = MSG_NOSIGNAL; |
This setting will not generate a sigpipe but read/write operations return SOAP_EOF instead. Note that Win32 systems do not support signals and lack the MSG_NOSIGNAL flag. The sigpipe handling and flags are not very portable. 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; // 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_free((struct soap*)soap); return NULL; } |
To prevent a malicious client from keeping a thread waiting forever by keeping the connection open, timeouts are set in the process_request routine as shown. See Section 19.19 for more details on timeout settings. 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. The client-side can also set the TCP keep-alive socket properties, using the soap.tcp_keep_alive flag (set to 1 to enable), soap.tcp_keep_idle to set the TCP_KEEPIDLE value, soap.tcp_keep_intvl to set the TCP_KEEPINTVL value, and soap.tcp_keep_cnt to set the TCP_KEEPCNT value. If a client is in the middle of soap call that might take a long time and the server goes away/down the caller does not get any feedback until the soap.recv_timeout is reached. Enabling TCP keep alive on systems that support it allows for a faster connection teardown detection for applications that need it.
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"; ... |
A client SOAP request will have the following HTTP header:
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= ... |
A client MUST set the soap.userid and soap.passwd strings for each call that requires client authentication. The strings are reset after each successful or unsuccessful call. 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; ... } |
When the authentication fails, the service response with a SOAP Fault message and a HTTP error code "401 Unauthorized". The HTTP error codes are described in Section 10.2.
HTTP NTLM authentication is enabled at the client-side by installing libntlm from http://www.nongnu.org/libntlm and compiling all project source codes with -DWITH_NTLM. In your application code set the soap.userid, soap.passwd, and soap.authrealm strings to a username, password, and the authentication domain respectively. A server may request NTLM authentication and denies access (HTTP 401 authentication required or HTTP 407 HTTP proxy authentication required) 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 NTLM authentication username and password:
struct soap soap; soap_init1(&soap, SOAP_IO_KEEPALIVE); if (soap_call_ns__method(&soap, ...)) { if (soap.error == 401) { soap.userid = "Zaphod"; soap.passwd = "Beeblebrox"; soap.authrealm = "Ursa-Minor"; if (soap_call_ns__method(&soap, ...)) ... |
The following NTLM handshake between the client C and server S is performed:
|
where stages 1 and 2 indicates a client attempting to connect without authorization information, which is the first method call in the code above. Stage 3 to 6 happen with the proper client authentication set with soap.userid, soap.passwd, and soap.authrealm provided. NTLM authenticates connections, not requests. When the connection is kept alive, subsequent messages can be exchanged without re-authentication. To avoid the overhead of the first rejected call, use:
struct soap soap; soap_init1(&soap, SOAP_IO_KEEPALIVE); soap.userid = "Zaphod"; soap.passwd = "Beeblebrox"; soap.authrealm = "Ursa-Minor"; soap.ntlm_challenge = ""; if (soap_call_ns__method(&soap, ...)) ... |
When the authentication fails (stage 1 and 2), the service response with a SOAP Fault message and a HTTP error code "401 Unauthorized". The HTTP error codes are described in Section 10.2. On windows, an alternative is to use the WinInet module, which has built-in NTLM support. The WinInet for gSOAP module is available in the mod_gsoap directory of the gSOAP package. Instructions for WinInet use are included there.
For HTTP 407 Proxy Authentication Required, set the proxy userid and passwd:
struct soap soap; soap_init1(&soap, SOAP_IO_KEEPALIVE); soap.proxy_host = "..."; soap.proxy_port = ...; if (soap_call_ns__method(&soap, ...)) { if (soap.error == 407) { soap.proxy_userid = "Zaphod"; soap.proxy_passwd = "Beeblebrox"; soap.authrealm = "Ursa-Minor"; if (soap_call_ns__method(&soap, ...)) ... |
To avoid the overhead of the first rejected call, use:
struct soap soap; soap_init1(&soap, SOAP_IO_KEEPALIVE); soap.proxy_host = "..."; soap.proxy_port = ...; soap.proxy_userid = "Zaphod"; soap.proxy_passwd = "Beeblebrox"; soap.authrealm = "Ursa-Minor"; soap.ntlm_challenge = ""; if (soap_call_ns__method(&soap, ...)) ... |
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 or domain soap.proxy_port = 8080; soap.proxy_userid = "guest"; soap.proxy_passwd = "guest"; ... |
A client SOAP request will have the following HTTP header:
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= ... |
When X-Forwarded-For headers are returned by the proxy, the header can be accessed in the soap.proxy_from string. The CONNECT method is used for HTTP proxy authentication:
CONNECT server.example.com:80 HTTP/1.1 |
In some cases, it may be necessary to use the Host HTTP header with the CONNECT protocol:
CONNECT server.example.com:80 HTTP/1.1 Host: server.example.com:80 |
If so, compile the gSOAP code with -DWITH_CONNECT_HOST to include the Host HTTP header with the CONNECT protocol.
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 context 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 for soap_call_ns__method calls. The soap.accept_timeout specifies the timeout for soap_accept(&soap) calls. The soap.send_timeout and soap.recv_timeout specify the timeout for non-blocking socket I/O operations. Example:
struct soap soap; soap_init(&soap); soap.send_timeout = 10; soap.recv_timeout = 10; |
This will result in a timeout if no data can be send in 10 seconds and no data is received within 10 seconds after initiating a send or receive operation over the socket. A value of zero disables timeout, for example:
soap.send_timeout = 0; soap.recv_timeout = 0; |
When a timeout occurs in send/receive operations, a SOAP_EOF exception will be raised ("end of file or no input"). Negative timeout values measure timeouts in microseconds, for example:
#define uSec *-1 #define mSec *-1000 soap.accept_timeout = 10 uSec; soap.send_timeout = 20 mSec; soap.recv_timeout = 20 mSec; |
The macros improve readability. 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 context 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=soap.linger_time. The soap.linger_time determines the wait time (the time resolution is system dependent, though according to some experts only zero and nonzero values matter). The linger option can be used to manage the number of connections that remain in TIME_WAIT state at the server side. 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 possible security implications such as port hijacking). Note that multiple socket options can be explicitly set with setsockopt as follows:
int sock = soap_bind(soap, host, port, backlog); if (soap_valid_socket(sock)) { setsockopt(sock, ..., ..., ..., ...); setsockopt(sock, ..., ..., ..., ...); |
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. HTTPS/SSL support must be configured for the Web server (not CGI-based Web Service application itself). To enable SSL for stand-alone gSOAP servers, first install OpenSSL and use option -DWITH_OPENSSL to compile the sources with your C or C++ compiler (or use -DWITH_GNUTLS if you prefer GNUTLS), for example:
> c++ -DWITH_OPENSSL -o myprog myprog.cpp stdsoap2.cpp soapC.cpp soapServer.cpp -lssl -lcrypto |
SSL support for stand-alone gSOAP Web services is enabled by calling soap_ssl_accept to perform the SSL/TLS handshake after soap_accept. In addition, a key file, a CA file (or path to certificates), DH file (if RSA is not used), and password need to be supplied. Instructions on how to do this can be found in the OpenSSL documentation http://www.openssl.org. See also Section 19.24. 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; soap_ssl_init(); /* init OpenSSL (just once) */ if (CRYPTO_thread_setup()) // OpenSSL { 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 (not used with GNUTLS) */ "cacert.pem", /* optional cacert file to store trusted certificates */ NULL, /* optional capath to directory with trusted certificates */ "dh512.pem", /* DH file name or DH key len bits (minimum is 512, e.g. "512") to generate DH param, 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; pthread_create(&tid, NULL, &process_request, (void*)tsoap); } soap_done(&soap); /* deallocates SSL context */ CRYPTO_thread_cleanup(); // OpenSSL return 0; } void *process_request(void *soap) { pthread_detach(pthread_self()); if (soap_ssl_accept((struct soap*)soap)) soap_print_fault(tsoap, stderr); else soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); soap_end((struct soap*)soap); soap_free((struct soap*)soap); // done and free context return NULL; } |
The soap_ssl_server_context function initializes the server-side SSL context. The server.pem key file is the server's private key concatenated with its certificate. The cacert.pem is used to authenticate clients and contains the client certificates. Alternatively a directory name can be specified. This directory is assumed to contain the certificates. The dh512.pem file specifies that DH will be used for key agreement instead of RSA. A numeric value greater than 512 can be provided instead as a string constant (e.g. "512") to allow the engine to generate the DH parameters on the fly (this can take a while) rather than retrieving them from a file. The randfile entry can be used to seed the PRNG. The last entry enable server-side session caching. A unique server name is required. The GNUTLS mutex lock setup is automatically peformed in the gSOAP engine, but only when POSIX threads are detected and available. OpenSSL requires mutex locks to be explicitly setup in your code for multithreaded applications, for which we need to call CRYPTO_thread_setup() and CRYPTO_thread_cleanup(). These routines can be found in openssl/crypto/threads/th-lock.c and are also used in the SSL example codes samples/ssl. 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(MUTEX_TYPE)); 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; } |
For Unix and Linux, make sure you have signal handlers set in your service and/or client applications to catch broken connections (SIGPIPE):
signal(SIGPIPE, sigpipe_handle); |
where, for example:
void sigpipe_handle(int x) { } |
By default, clients are not required to authenticate. To support client authentication use the following:
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); } |
This requires each client to authenticate with its certificate. The cacert file and capath are optional. Either one can be specified when clients must run on non-trusted systems (capath is not used with GNUTLS). 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. The client.pem and server.pem contain the client/server private key concatenated with the certificate. The keyfiles (client.pem and server.pem) are created by concatenating the private key PEM with the certificate PEM. The keyfile SHOULD NEVER be shared with any party. With OpenSSL, you can encrypt the keyfiles with a password to offer some protection and the password is used in the client/server code to read the keyfile. GNUTLS does not support this feature and cannot encrypt or decrypt a keyfile. 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 context.
To utilize HTTPS/SSL, you need to install the OpenSSL library on your platform or GNUTLS for a light-weight SSL/TLS library. After installation, compile all the sources of your application with option -DWITH_OPENSSL (or -DWITH_GNUTLS when using GNUTLS). For example on Linux:
> c++ -DWITH_OPENSSL myclient.cpp stdsoap.cpp soapC.cpp soapClient.cpp -lssl -lcrypto |
or Unix:
> c++ -DWITH_OPENSSL myclient.cpp stdsoap.cpp soapC.cpp soapClient.cpp -lxnet -lsocket -lnsl -lssl -lcrypto |
or you can add the following line to soapdefs.h:
#define WITH_OPENSSL |
and compile with option -DWITH_SOAPDEFS_H to include soapdefs.h in your project. A client program simply uses the prefix https: instead of http: in the endpoint URL of a service operation call to a Web Service to use encrypted transfers (if the service supports HTTPS). You need to specify the client-side key file and password of the keyfile:
soap_ssl_init(); /* init OpenSSL (just once) */ 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 (not used with GNUTLS) */ "cacerts.pem", /* cacert file to store trusted certificates (needed to verify server) */ NULL, /* capath to directory 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 and the cacerts.pem or capath (not used with GNUTLS) must be set so that the CA certificates of the server(s) are accessible at run time. The cacert.pem file included in the package contains the certificates of common CAs. This file must be supplied with the client, if server authentication is required. Althernatively, you can use the plugin/cacerts.h and plugin/cacerts.c code to embed CA certificates in your client code. Other client-side SSL options are SOAP_SSL_SKIP_HOST_CHECK to skip the host name verification check and SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE to allow connecting to a host with an expired certificate. For example,
soap_ssl_init(); /* init OpenSSL (just once) */ if (soap_ssl_client_context(&soap, SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION - SOAP_SSL_SKIP_HOST_CHECK, - SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE, "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 (not used with GNUTLS) */ "cacerts.pem", /* cacert file to store trusted certificates (needed to verify server) */ NULL, /* capath to directory 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", "", ...); |
For systems based on Microsoft windows, the WinInet module can be used instead, see mod_gsoap/gsoap_win/wininet. 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); } |
This also assumes that the server does not require clients to authenticate (the keyfile is absent). Make sure you have signal handlers set in your application to catch broken connections (SIGPIPE):
signal(SIGPIPE, sigpipe_handle); |
where, for example:
void sigpipe_handle(int x) { } |
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 context. Caution: concurrent client calls MUST be made using separate soap structs copied with soap_copy from an originating struct initialized with soap_ssl_client_context. In addition, the thread initialization code discussed in Section 19.21 MUST be used to properly setup OpenSSL in a multi-threaded client application.
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. Althernatively, you can use the plugin/cacerts.h and plugin/cacerts.c code to embed CA certificates in your client code. For systems based on Microsoft windows, the WinInet module can be used instead, see the README.txt located in the package under mod_gsoap/gsoap_win/wininet. The other .pem files in the gSOAP distribution are examples of self-signed certificates for testing purposes (cacert.pem, client.pem, server.pem). The client.pem and server.pem contain the private key and certificate of the client or server, respectively. The keyfiles (client.pem and server.pem) are created by concatenating the private key PEM with the certificate PEM. The keyfile SHOULD NEVER be shared with any party. With OpenSSL, you can encrypt the keyfiles with a password to offer some protection and the password is used in the client/server code to read the keyfile. GNUTLS does not support this feature and cannot encrypt or decrypt a keyfile. 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, for more details and a walk-through see http://www.icewarp.com/Knowledgebase/617.htm. 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.
You now have a self-signed CA root certificate cacert.pem and a server.pem (or client.pem) certificate in PEM format. The cacert.pem certificate is used in the cafile parameter of the soap_ssl_client_context (or soap_ssl_server_context) at the client (or server) side to verify the authenticity of the peer. You can also provide a capath parameter to these trusted certificates. The server.pem (or client.pem) must be provided with the soap_ssl_server_context at the server side (or soap_ssl_client_context at the client side) together with the password you entered when generating the certificate using cert.sh to access the file. These certificates must be present to grant authentication requests by peers. In addition, the server.pem (and client.pem) include the host name of the machine on which the application runs (e.g. localhost), so you need to generate new certificates when migrating a server (or client). Finally, you need to generate Diffie-Helmann (DH) parameters for the server if you wish to use DH instead of RSA. There are two options:
> openssl dhparam -outform PEM -out dh.pem 512 |
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); ... |
The following table lists the names of the hardware and software engines:
|
Set the full path to libssl.lib and libcrypto.lib under the MSVC++ "Projects" menu, then choose "Link": "Object/Modules". Please make sure libssl32.dll and libeay32.dll can be loaded by gSOAP applications, thus they must be installed properly on the target machine. If you're using compilation settings such as /MTd then link to the correct libeay32MTd.lib and ssleay32MTd.lib libraries. 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 ... |
This will send a compressed SOAP/XML request to a service, provided that Zlib is installed and linked with the application and the -DWITH_GZIP option was used to compile the sources. Receiving compressed SOAP/XML over HTTP either in gzip or deflate formats is automatic. The SOAP_ENC_ZLIB flag does not have to be set at the server side to accept compressed messages. Reading and receiving gzip compressed SOAP/XML without HTTP headers (e.g. with other transport protocols) is also automatic. 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 ... |
To verify and monitor compression rates, you can use the values soap.z_ratio_in and soap.z_ratio_out. These two float values lie between 0.0 and 1.0 and express the ratio of the compressed message length over uncompressed message length.
soap_call_ns__myMethod(&soap, ...); ... printf("Compression ratio: %f%% (in) %f%% (out)\n", 100*soap.z_ratio_out, 100*soap.z_ratio_in); ... |
Note: lower ratios mean higher compression rates. 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 context.
Client-side cookie support is optional. To enable cookie support, compile all sources with option -DWITH_COOKIES, for example:
> c++ -DWITH_COOKIES -o myclient stdsoap2.cpp soapC.cpp soapClient.cpp |
or add the following line to stdsoap.h:
#define WITH_COOKIES |
Client-side cookie support is fully automatic. So just (re)compile stdsoap2.cpp with -DWITH_COOKIES to enable cookie-based session control in your client. 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 context 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; |
The cookie database is a linked list pointed to by soap.cookies where each node is declared as:
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; }; |
Since the cookie database is linked to a soap struct, each thread has a local cookie database in a multi-threaded implementation.
Server-side cookie support is optional. To enable cookie support, compile all sources with option -DWITH_COOKIES, for example:
> c++ -DWITH_COOKIES -o myserver ... |
gSOAP provides the following cookie API for server-side cookie session control:
|
The following global variables are used to define the current domain and path:
|
The cookie_path value is used to filter cookies intended for this service according to the path prefix rules outlined in RFC2109. 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 context 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 ... |
The attributes soap.proxy_host and soap.proxy_port keep their values through a sequence of service operation calls, so they only need to be set once. When X-Forwarded-For headers are returned by the proxy, the header can be accessed in the soap.proxy_from string.
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 stdsoap2.h.
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:
Use -DWITH_LEANER to make the executable even smaller by removing DIME and MIME attachment handling, wchar_t* serialization, and support for XML DOM operations. Note that DIME/MIME attachments are not essential to achieve SOAP/XML interoperability. DIME attachments are a convenient way to exchange non-text-based (i.e. binary) content, but are not required for basic SOAP/XML interoperability. Attachment requirements are predictable. That is, applications won't suddenly decide to use DIME or MIME instead of XML to exchange content. 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. winsock2 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; |
These functions are supposed to provide a (minimal) transport stack. See Section 19.7 for more details on the use of these callbacks. All callback function pointers should be non-NULL, except fpoll. 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. Another approach to combine multiple client and service applications into one executable is by using C++ namespaces to structurally separate the definitions or by creating C libraries for the client/server objects as explained in subsequent sections. This is automated with wsdl2h option -q. Both approaches are demonstrated by example in the gSOAP distribution, the samples/link (C only) and samples/link++ (C++ with C++ namespaces) examples.
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). Use wsdl2h option -qname to generate definitions in the C++ name namespace. This option can also be used in combination with C++ proxy and server object generation, using soapcpp2 options -i (or -j) and -p. 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 ... } |
When compiling this header file with the gSOAP soapcpp2 compiler, all type definitions, the (de)serializers for these types, and the stub/skeleton codes will be placed in this namespace. The XML namespace mapping table (saved in a .nsmap file) will not be placed in the code namespace to allow it to be linked as a global object. You can use option -n to create local XML namespace tables, see Section 9.1 (but remember that you explicitly need to initialize the soap.namespaces to point to a table at run time). The generated files are prefixed with the code namespace name instead of the usual soap file name prefix to enable multiple client/server codes to be build in the same project directory (a code namespace automatically sets the -p compiler option, see Section 9.1 for options). 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 generated envC.cpp file holds the SOAP Header and Fault serializers and you can link this file with your client or server application.
The gSOAP soapcpp2 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.37. Do not link soapClientLib.cpp or soapServerLib.cpp together with soapC.cpp, soapClient.cpp, and soapServer.cpp. The library versions already include all of the necessary definitions. To build multiple libraries in the same project directory, you can define a C++ code namespace in your header file (see Section 19.35) 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.35. 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 -penv env.h |
The generated envC.cpp file holds the SOAP Header and Fault serializers and you can create a (dynamic) library for it to link this code with your client or server application. You MUST compile the stdsoap2.cpp library using -DWITH_NONAMESPACES:
> c++ -DWITH_NONAMESPACES -c stdsoap2.cpp |
This omits the reference to the global namespaces table, which is nowhere to be defined since we will use XML namespaces for each client/service separately. Therefore, you MUST explicitly set the namespaces value of the gSOAP context in your code every time after initialization of the soap struct with the soap_set_namespaces(struct soap*, const struct Namespace*) function. 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 |
Then we generate the code for client1 and client2:
> soapcpp2 -c -n -pmyClient1 client1.h > soapcpp2 -c -n -pmyClient2 client2.h |
This generates myClient1ClientLib.c and myClient2ClientLib.c (among many other files). These two files should be compiled and linked with your application. The source code of your application should include the generated envH.h, myClient1H.h, myClient2.h files and myClient1.nsmap, myClient2.nsmap files:
#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 ... |
It is important to use soapcpp2 option -n, see Section 9.1, to rename the namespace tables so we can include them all without running into redefinitions. Note: Link conflicts may still occur in the unlikely situation that identical service operation 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 service operation 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 > c++ -c envC.cpp |
We also compile stdsoap2.cpp without namespaces:
> c++ -c -DWITH_NONAMESPACES stdsoap2.cpp |
Note: when you forget to use -DWITH_NONAMESPACES you will get an unresolved link error for the global namespaces table. You can define a dummy table to avoid having to recompile 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); } |
We then compile it as a library and we use option -n to rename the namespace table to avoid link conflicts later:
> soapcpp2 -n quote.h > c++ -c quoteClientLib.cpp |
If you don't want to use a C++ code namespace, you should compile quote.h "as is" with soapcpp2 option -pquote:
> soapcpp2 -n -pquote quote.h > c++ -c quoteClientLib.cpp |
Third, we create the Currency Exchange Rate header file specification:
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); } |
Similar to the Quote example above, we compile it as a library and we use option -n to rename the namespace table to avoid link conflicts:
> soapcpp2 -n rate.h |
Fourth, we consider linking the libraries to the main program. The main program can import the quoteServiceProxy.h and rateServiceProxy.h files to obtain client proxies to invoke the services. The proxy implementations are defined in quoteClient.cpp. The -n option also affects the generation of the C++ proxy codes to ensure that the gSOAP context is properly initialized with the appropriate namespace table (so you don't have to initialize explicitly - this feature is only available with C++ proxy and server object classes).
#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; } |
Compile and link this application with stdsoap2.o, envC.o, quoteServerProxy.o, and rateServerProxy.o. 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); } |
We compile this with:
> soapcpp2 -n calc.h |
The effect of the -n option is that it creates local namespace tables, and a modified calcServiceObject.h server class definitions that properly initialize the gSOAP run time with the table.
#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; } |
In fact, the calc::Service class is derived from the struct soap. So the context is available as this, which can be passed to all gSOAP functions that require a soap struct context. The example above serves requests over stdin/out. Use the bind and accept calls to create a stand-alone server to service inbound requests over sockets, see 7.2.3.
This is the same example as above, but the clients are build with pure C. We create a env.h that contains the joint SOAP Header and SOAP Fault definitions. You may have to copy-paste these from the other header files. Then, compile it as follows:
> soapcpp2 -c -penv env.h > cc -c envC.c |
We also compile stdsoap2.c without namespaces:
> cc -c -DWITH_NONAMESPACES stdsoap2.c |
Second, we create the Delayed Stock Quote header file specification, which may be obtained using the WSDL importer.
//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); |
We compile it as a library and we use options -n and -p to rename the namespace table to avoid link conflicts:
> soapcpp2 -c -n -pquote quote.h > cc -c quoteClientLib.c |
Third, we create the Currency Exchange Rate header file specification:
//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); |
We compile it as a library and we use options -n and -p to rename the namespace table to avoid link conflicts:
> soapcpp2 -c -n -prate rate.h > cc -c rateClientLib.c |
The main program is:
#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; } |
Compile and link this application with stdsoap2.o, envC.o, quoteClientLib.o, and rateClientLib.o. 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.
We build a C application for multiple services served on one port. We create a env.h that contains the joint SOAP Header and SOAP Fault definitions. You may have to copy-paste these from the other header files. Then, compile it as follows:
> soapcpp2 -c -penv env.h > cc -c envC.c |
We also compile stdsoap2.c without namespaces:
> cc -c -DWITH_NONAMESPACES stdsoap2.c |
Say we have a service definition in quote.h. We compile it as a library and we use options -n and -p to rename the namespace table to avoid link conflicts:
> soapcpp2 -c -n -pquote quote.h > cc -c quoteClientLib.c |
We do the same for a service definition in rate.h:
> soapcpp2 -c -n -prate rate.h > cc -c rateClientLib.c |
To serve both the quote and rate services on the same port, we chain the service dispatchers as follows:
struct soap *soap = soap_new(); soap_bind(soap, NULL, 8080, 100); soap_accept(soap); if (soap_begin_serve(soap)) soap_send_fault(&abc); // send fault to client else if (quote_serve_request(soap) == SOAP_NO_METHOD) { if (rate_serve_request(soap)) soap_send_fault(soap); // send fault to client } else if (soap.error) soap_send_fault(soap); // send fault to client soap_destroy(soap); soap_end(soap); soap_free(soap); |
This chaining can be arbitrarily deep. When the previous request fails with a SOAP_NO_METHOD then next request dispatcher can be tried. The server should also define the service operations:
int ns__getQuote(struct soap *soap, char *symbol, float *Result); { *Result = ... ; return SOAP_OK; } int ns__getRate(struct soap *soap, char *country1, char *country2, float *Result); { *Result = ... ; return SOAP_OK; } |
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 generated envC.cpp file holds the SOAP Header and Fault serializers, which need to be part of the base library functions. 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=__declspec(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.35. 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 context:
soap_init(&soap); soap_set_namespaces(&soap, namespaces); |
where the namespaces[] table should be defined in the client/server source. These tables are generated in the .nsmap files. You can rename the tables using option -n, see Section 9.1.
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*); |
Other functions that deal with plug-ins are:
int soap_copy(struct soap *soap); void soap_done(struct soap *soap); |
The soap_copy function returns a new dynamically allocated gSOAP context that is a copy of another, such that no data is shared between the copy and the original context. The soap_copy function invokes the plug-in copy callbacks to copy the plug-ins' local data. The soap_copy function returns a gSOAP error code or SOAP_OK. The soap_done function de-registers all plugin-ins, so this function should be called to cleanly terminate a gSOAP run-time context. 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); |
Then, we write the plugin registry function and the callbacks:
#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 contexts 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; } |
The fdelete callback of struct soap_plugin MUST be set to register the plugin. It is the responsibility of the plug-in to handle registry (init), copy, and deletion of the plug-in data and callbacks. 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:
The example plug-in should be used as follows:
struct soap soap; soap_init(&soap); soap_register_plugin(&soap, plugin); ... soap_done(&soap); |
Note: soap_register_plugin(...) is an alias for soap_register_plugin_arg(..., NULL). That is, it passes NULL as an argument to plug-in's registry callback. 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" size_t bytes_in; size_t bytes_out; ... if (soap_register_plugin(&soap, logging)) soap_print_fault(&soap, stderr); // failed to register ... soap_set_logging_inbound(&soap, stdout); soap_set_logging_outbound(&soap, stdout); ... process messages ... soap_set_logging_inbound(&soap, NULL); // disable logging soap_set_logging_outbound(&soap, NULL); // disable logging soap_get_logging_stats(&soap, &bytes_out, &bytes_in); |
If you use soap_copy to copy the soap struct with the plug-in, the plug-in's data will be shared by the copy. Therefore, the statistics are not 100% guaranteed to be accurate for multi-threaded services since race conditions on the counters may occur. Mutex is not used to update the counters to avoid introducing expensive synchronization points. If 100% server-side accuracy is required, add mutex at the points indicated in the logging.c code.
Client-side and server-side use of RESTful HTTP GET operations are supported with the HTTP GET plug-in plugin/httpget.c. The HTTP GET plug-in allows your server to handle RESTful HTTP GET requests and at the same time still serve SOAP-based POST requests. The plug-in provides support to client applications to issue HTTP GET operations to a server. Note that HTTP GET requests can also be handled at the server side 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 of runtime. 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]; |
An HTTP GET handler can simply produce some HTML content, or any other type of content by sending data:
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 } |
If you use soap_copy to copy the soap struct with the plug-in, the plug-in's data will be shared by the copy. Therefore, the statistics are not 100% guaranteed to be accurate for multi-threaded services since race conditions on the counters may occur. Mutex is not used to update the counters to avoid introducing expensive synchronization points. If 100% server-side accuracy is required, add mutex at the points indicated in the httpget.c code. The client-side use of HTTP GET is provided by the soap_get_connect operation. To receive a SOAP/XML (response) message ns:methodResponse, use:
#include "httpget.h" ... soap_register_plugin(&soap, http_get); ... if (soap_get_connect(&soap, endpoint, action)) ... connect error ... else if (soap_recv_ns__methodResponse(&soap, ... params ...)) ... error ... else ... ok ... soap_destroy(&soap); soap_end(&soap); soap_done(&soap); |
To receive any HTTP Body data into a buffer, use:
#include "httpget.h" ... char *response = NULL; soap_register_plugin(&soap, http_get); ... if (soap_get_connect(&soap, endpoint, NULL)) ... connect error ... else if (soap_begin_recv(&soap)) ... error ... else response = soap_get_http_body(&soap); soap_end_recv(&soap); ... use the 'response' string (NULL indicates no body or error) soap_destroy(&soap); soap_end(&soap); soap_done(&soap); |
Client-side and server-side use of RESTful HTTP POST, PUT, and DELETE operations are supported with the HTTP POST plug-in plugin/httppost.c. The HTTP POST plug-in allows your server to handle RESTful HTTP POST requests and at the same time still serve SOAP-based POST requests. The plug-in also provides support for client applications to issue HTTP POST operations to a server. To simplify the server-side handling of POST requests, handlers can be associated with media types:
struct http_post_handlers my_handlers[] = { { "image/jpg", jpeg_handler }, { "image/ *", image_handler }, { "text/html", html_handler }, { "text/ *", text_handler }, { "text/ *;*", text_handler }, { "POST", generic_POST_handler }, { "PUT", generic_PUT_handler }, { "DELETE", generic_DELETE_handler }, { NULL } }; |
Note that '*' can be used as a wildcard and some media types may have optional parameters (after ';'). The handlers are functions that will be invoked when a POSTed request message matching media type is send to the server. An example image handler that checks the specific image type:
int image_handler(struct soap *soap) { const char *buf; size_t len; // if necessary, check type in soap->http_content if (soap->http_content && !soap_tag_cmp(soap->http_content, "image/gif") return 404; // HTTP error 404 if (soap_http_body(soap, &buf, &len) != SOAP_OK) return soap->error; // ... now process image in buf // reply with empty HTTP OK response: soap_response(soap, SOAP_OK); soap_end_send(soap); return SOAP_OK; } |
The HTTP POST plug-in provides a soap_http_body operation as illustrated above to copy the HTTP Body content into a buffer. The above example returns HTTP OK. If content is supposed to be returned, then use:
soap->http_content = "image/jpeg"; // a jpeg image to return back soap_response(soap, SOAP_FILE); // SOAP_FILE sets custom http content soap_send_raw(soap, buf, len); // send image soap_end_send(soap); |
For client applications to use HTTP POST, use the soap_post_connect operation:
char *buf; // holds the HTTP request/response body data size_t len; // length of data ... if (soap_post_connect(soap, "URL", "SOAP action or NULL", "media type") || soap_send_raw(soap, buf, len); || soap_end_send(soap)) ... error ... if (soap_begin_recv(&soap) || soap_http_body(&soap, &buf, &len) || soap_end_recv(&soap)) ... error ... // ... use buf[0..len-1] soap_end(soap); |
Similarly, soap_put_connect and soap_delete_connect commands are provided for PUT and DELETE handling.
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 |
Once registered, MD5 checksums are produced for all outbound messages. Inbound messages with MD5 checksums in the HTTP header are automatically verified. 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 ... |
Unfortunately, this eliminates streaming.
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 |
This code supports both basic and digest authentication. 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 } |
For more details, including how to configure HTTP Digest authentication for proxies, please see the doc/httpda/html/index.html documentation in the gSOAP package.
The WSA WS-Addressing plug-in and the source code are extensively documented in the doc/wsa directory of the gSOAP package. Please refer to the documentation included in the package. The plug-in code is located in the plugin directory:
|
To enable WS-Addressing 2005 (and support for 8/2004), the service definitions header file for soapcpp2 should include the following imports:
#import "import/wsa5.h" |
This imports the SOAP header elements required by WS-Addressing. For more details, please see the doc/wsa/html/index.html documentation in the gSOAP package.
The WSRM WS-ReliableMessaging plug-in and the source code are extensively documented in the doc/wsrm directory of the gSOAP package. Please refer to the documentation included in the package. The plug-in code is located in the plugin directory:
|
Also needed are:
|
To enable WS-ReliableMessaging, the service definitions header file for soapcpp2 should include the following imports:
#import "import/wsrm.h" #import "import/wsa5.h" |
This imports the SOAP header elements required by WS-ReliableMessaging. For more details, please see the doc/wsrm/html/index.html documentation in the gSOAP package.
The WSSE WS-Security plug-in and the source code are extensively documented in the doc/wsse directory of the gSOAP package. Please refer to the documentation included in the package for details. The plug-in code is located in the plugin directory:
|
Also needed are:
|
To enable WS-Secrutiy, the service definitions header file for soapcpp2 should include the following imports:
#import "import/wsse.h" |
This imports the SOAP header elements required by WS-Security. For more details, please see the doc/wsse/html/index.html documentation in the gSOAP package.
The WS-Discovery implementation is documented in the doc/wsdd directory of the gSOAP package. Please refer to the documentation included in the package for details. Basically, to add WS-Discovery support the following event handlers must be defined and linked:
void wsdd_event_Hello(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int MetadataVersion) |
void wsdd_event_Bye(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int MetadataVersion) |
soap_wsdd_mode wsdd_event_Probe(struct soap *soap, const char *MessageID, const char *ReplyTo, const char *Types, const char *Scopes, const char *MatchBy, struct wsdd__ProbeMatchesType *ProbeMatches) |
void wsdd_event_ProbeMatches(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, struct wsdd__ProbeMatchesType *ProbeMatches) |
soap_wsdd_mode wsdd_event_Resolve(struct soap *soap, const char *MessageID, const char *ReplyTo, const char *EndpointReference, struct wsdd__ResolveMatchesType *ResolveMatches) |
void wsdd_event_ResolveMatches(struct soap *soap, unsigned int InstanceId, const char *SequenceId, unsigned int MessageNumber, const char *MessageID, const char *RelatesTo, const char *EndpointReference, const char *Types, const char *Scopes, const char *MatchBy, const char *XAddrs, unsigned int MetadataVersion) |
These event handlers will be invoked when inbound WS-Discovery messages arrive using:
if (!soap_valid_socket(soap_bind(soap, NULL, port, 100))) .. error if (soap_wsdd_listen(soap, timeout)) ... error |
which will listen for timeout seconds to inbound WS-Discovery messages on a port and dispatches them to the event handlers. A negative timeout is measured in ns.