[19].Gsoap 高级功能

19  Advanced Features

19.1  Internationalization

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.

19.2  Customizing the WSDL and Namespace Mapping Table File Contents With gSOAP Directives

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

SOAP default SOAP transport (supports 1.1 and 1.2)
SOAP1.1 SOAP 1.1 only
SOAP1.2 SOAP 1.2 only
SOAP-GET one-way SOAP with HTTP GET
SOAP1.1-GET one-way SOAP 1.1 with HTTP GET
SOAP1.2-GET one-way SOAP 1.1 with HTTP GET
HTTP REST HTTP (POST or one-way PUT)
POST REST HTTP POST
GET one-way REST HTTP GET
PUT one-way REST HTTP PUT
DELETE REST HTTP DELETE


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.

19.2.1  Example

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).

19.3  Transient Data Types

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.

19.4  Serialization "as is" with Volatile Data Types

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.

19.5  How to Declare User-Defined Serializers and Deserializers

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.

19.6  How to Serialize Data Without Generating XSD Type Attributes

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.

19.7  Function Callbacks for Customized I/O and HTTP Handling

gSOAP provides five callback functions for customized I/O and HTTP handling:

Callback (function pointer)
SOAP_SOCKET (*soap.fopen)(struct soap *soap, const char *endpoint, const char *host, int port)
Called from a client proxy to open a connection to a Web Service located at endpoint. Input parameters host and port are micro-parsed from endpoint. Should return a valid file descriptor, or SOAP_INVALID_SOCKET and soap->error set to an error code. Built-in gSOAP function: tcp_connect
int (*soap.fclose)(struct soap *soap)
Called by client proxy multiple times, to close a socket connection before a new socket connection is established and at the end of communications when the SOAP_IO_KEEPALIVE flag is not set and soap.keep_alive ≠ 0 (indicating that the other party supports keep alive). Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: tcp_disconnect




Callback (function pointer)
int (*soap.fget)(struct soap *soap)
Called by the main server loop upon an HTTP GET request. The SOAP_GET_METHOD error is returned by default. This callback can be used to respond to HTTP GET methods with content, see Section 19.10. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_get
int (*soap.fput)(struct soap *soap)
Called by the main server loop upon an HTTP PUT request. The SOAP_PUT_METHOD error is returned by default. This callback can be used to respond to HTTP PUT. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_put
int (*soap.fdel)(struct soap *soap)
Called by the main server loop upon an HTTP DELETE request. The SOAP_DELETE_METHOD error is returned by default. This callback can be used to respond to HTTP DELETE methods. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_del
int (*soap.fhead)(struct soap *soap)
Called by the main server loop upon an HTTP HEAD request. The SOAP_HEAD_METHOD error is returned by default. This callback can be used to respond to HTTP HEAD methods. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_get
int (*soap.fform)(struct soap *soap)
Called by the main server loop when a user-defined fparsehdr callback returned SOAP_FORM to signal that the HTTP body must be processed by this form handler callback. The HTTP POST form data MUST be read, otherwise keep-alive messages will end up out of sync. Should return SOAP_OK or a gSOAP error code. Built-in gSOAP function: none.
int (*soap.fpost)(struct soap *soap, const char *endpoint, const char *host, int port, const char *path, const char *action, size_t count)
Called from a client proxy to generate the HTTP header to connect to endpoint. Input parameters host, port, and path are micro-parsed from endpoint, action is the SOAP action, and count is the length of the SOAP message or 0 when SOAP_ENC_XML is set or when SOAP_IO_LENGTH is reset. Use function soap_send(struct soap *soap, char *s) to write the header contents. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_post.
int (*soap.fposthdr)(struct soap *soap, const char *key, const char *val)
Called by http_post and http_response (through the callbacks). Emits HTTP key: val header entries. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_post_header.
int (*soap.fresponse)(struct soap *soap, int soap_error_code, size_t count)
Called from a service to generate the response HTTP header. Input parameter soap_error_code is a gSOAP error code (see Section 10.2 and count is the length of the SOAP message or 0 when SOAP_ENC_XML is set or when SOAP_IO_LENGTH is reset. Use function soap_send(struct soap *soap, char *s) to write the header contents. Should return SOAP_OK, or a gSOAP error code Built-in gSOAP function: http_response
int (*soap.fparse)(struct soap *soap)
Called by client proxy and service to parse an HTTP header (if present). When user-defined, this routine must at least skip the header. Use function int soap_getline(struct soap *soap, char *buf, int len) to read HTTP header lines into a buffer buf of length len (returns empty line at end of HTTP header). Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: http_parse
int (*soap.fparsehdr)(struct soap *soap, const char *key, const char *val)
Called by http_parse (through the fparse callback). Handles HTTP key: val header entries to set gSOAP's internals. Should return SOAP_OK, SOAP_STOP (see fstop) or a gSOAP error code. Built-in gSOAP function: http_parse_header




Callback (function pointer)
int (*soap.fsend)(struct soap *soap, const char *s, size_t n)
Called for all send operations to emit contents of s of length n. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: fsend
size_t (*soap.frecv)(struct soap *soap, char *s, size_t n)
Called for all receive operations to fill buffer s of maximum length n. Should return the number of bytes read or 0 in case of an error, e.g. EOF. Built-in gSOAP function: frecv
int (*soap.fignore)(struct soap *soap, const char *tag)
Called when an unknown XML element was encountered on the input. The tag parameter is the offending XML element tag name. Should return SOAP_OK, or a gSOAP error code such as SOAP_TAG_MISMATCH to throw an exception. Built-in gSOAP function: none.
int (*soap.fconnect)(struct soap *soap, const char *endpoint, const char *host, int port)
When non-NULL, this callback is called for all client-to-server connect operations instead of the built-in socket connect code. Therefore, it can be used to override the built-in connection establishment. Parameter endpoint contains the server endpoint URL, host the domain name or IP, and port the port number. Should return SOAP_OK, or a gSOAP error code. Built-in gSOAP function: none
SOAP_SOCKET (*soap.faccept)(struct soap *soap, SOAP_SOCKERT s, struct sockaddr *a, int *n)
Called by soap_accept. This is a wrapper routine for accept. Given master socket s should return a valid socket descriptor or SOAP_INVALID_SOCKET and set soap->error to an error code. Built-in gSOAP function: tcp_accept
int (*soap.fresolve)(struct soap *soap, const char *addr, struct in_addr *inaddr)
Called by soap_bind if a host name is given and soap_connect to resolve a domain name addr. Should set in_addr *a and return SOAP_OK or return SOAP_ERR upon failure.
Built-in gSOAP function: tcp_gethost
int (*soap.fpoll)(struct soap *soap)
Used by clients to check if the server is still responsive.
Built-in gSOAP function: soap_poll
int (*soap.fserveloop)(struct soap *soap)
Called after successful invocation of a server operation in the server loop, immediately after sending the response to a client. Can be used to clean up resources (e.g. using soap_end()) while serving a long sequence of keep-alive connections. Should return SOAP_OK, or set soap->error to a gSOAP error code and return soap->error. Built-in gSOAP function: none.
void (*soap.fmalloc)(struct soap *soap, size_t n)
Use to override memory allocation for deserialized C data. Memory allocated via this callback will not be automatically released by the gSOAP engine. The application must release this data by keeping track of the allocations. Note: it is not safe to traverse deserialized data structures and free each node, since data might be shared (SOAP multiref) and some allocated data such as the HTTP SOAPAction might no be part of the structure. Built-in gSOAP function: none.
int (*soap.fheader)(struct soap *soap)
Called immediately after parsing a SOAP Header. The SOAP Header struct referenced by soap->header can be inspected and verified. The function should return SOAP_OK or a fault. Built-in gSOAP function: none.
void (*soap.fseterror)(struct soap *soap, const char **code, const char **string)
Called to set the SOAP Fault code and string values based on the value of soap->error. Allows user-defined messages to be associated with gSOAP error codes to override gSOAP's built-in error messages. Built-in gSOAP function: none.


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

 

19.8  HTTP 1.0 and 1.1

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.

19.9  HTTP 307 Temporary Redirect Support

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:

  • Redirecting a secure HTTPS address to a non-secure HTTP address via 307 creates a security vulnerability.
  • Cyclic redirects must be detected (e.g. allowing only a limited number of redirect levels).
  • Redirecting HTTP POST will result in re-serialization and re-post of the entire SOAP request. The SOAP request message must be re-posted in its entirity when re-issuing the SOAP operation to a new address.

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;
}

 

19.10  HTTP GET Support

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.

19.11  TCP and HTTP Keep-Alive

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.

19.12  HTTP Chunked Transfer Encoding

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.

19.13  HTTP Buffered Sends

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.

19.14  HTTP Authentication

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.

19.15  HTTP NTLM Authentication

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:

1: C -> S POST ...
  Content-Type: text/xml; charset=utf-8
 
2: C <- S 401 Unauthorized
  WWW-Authenticate: NTLM
 
3: C -> S GET ...
  Authorization: NTLM <base64-encoded type-1-message>
 
4: C <- S 401 Unauthorized
  WWW-Authenticate: NTLM <base64-encoded type-2-message>
 
5: C -> S POST ...
  Content-Type: text/xml; charset=utf-8
  Authorization: NTLM <base64-encoded type-3-message>
 
6: C <- S 200 OK


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.

19.16  HTTP Proxy NTLM Authentication

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, ...))    ...

 

19.17  HTTP Proxy Basic Authentication

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.

19.18  Speed Improvement Tips

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:

  • Increase the buffer size SOAP_BUFLEN by changing the SOAP_BUFLEN macro in stdsoap2.h. Use buffer size 218=262144 for example.
  • Use HTTP keep-alive at the client-side, see 19.11, when the client needs to make a series of calls to the same server. Server-side keep-alive support can greatly improve performance of both client and server. But be aware that clients and services under Unix/Linux require signal handlers to catch dropped connections.
  • Use HTTP chunked transfers, see 19.12.
  • Do NOT use gzip compression, even when transferring data over a modem connection. Modems already compress data transfers.

19.19  Timeout Management for Non-Blocking Operations

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.

19.20  Socket Options and Flags

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, ..., ..., ..., ...);

 

19.21  Secure SOAP Web Services with HTTPS/SSL

  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.

19.22  Secure SOAP Clients with HTTPS/SSL

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.

19.23  SSL Authentication Callback

gSOAP provides a callback function for authentication initialization:

Callback (function pointer)
int (*soap.fsslauth)(struct soap *soap)
Initialize the authentication information for clients and services, such as the certificate chain, password, read the key and/or DH file, generate an RSA key, and initialization of the RNG. Should return a gSOAP error code or SOAP_OK. Built-in gSOAP function: ssl_auth_init

 

19.24  SSL Certificates and Key Files

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.

  • Go to the OpenSSL bin directory (/usr/local/ssl by default and /System/Library/OpenSSL on Mac OS X)
  • There should be a file called openssl.cnf
  • Create a new directory in your home account, e.g. $HOME/CA, and copy the openssl.cnf file to this directory
  • Modify openssl.cnf by changing the 'dir' value to HOME/CA
  • Copy the README.txt, root.sh, and cert.sh scripts from the gSOAP distribution package located in the samples/ssl directory to HOME/CA
  • Follow the README.txt instructions

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:

  1. Set the dhfile parameter to the numeric DH prime length in bits required (for example "1024") to let the engine generate DH parameters at initialization. This can be time consuming.
  2. Provide a file name for the dhfile parameter of soap_ssl_server_context. The file should be generated beforehand. To do so with the OpenSSL command line tool, use:

    > openssl dhparam -outform PEM -out dh.pem 512

    File dh512.pem is the output file and 512 is the number of bits used.

19.25  SSL Hardware Acceleration

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:

Name Description
openssl The default software engine for cryptographic operations
openbsd_dev_crypto OpenBSD supports kernel level cryptography
cswift CryptoSwift acceleration hardware
chil nCipher CHIL acceleration hardware
atalla Compaq Atalla acceleration hardware
nuron Nuron acceleration hardware
ubsec Broadcom uBSec acceleration hardware
aep Aep acceleration hardware
sureware SureWare acceleration hardware

 

19.26  SSL on Windows

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.

19.27  Zlib Compression

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.

19.28  Client-Side Cookie Support

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.

19.29  Server-Side Cookie Support

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:

Function
struct soap_cookie *soap_set_cookie(struct soap *soap, const char *name, const char *value, const char *domain, const char *path);
Add a cookie to the database with name name and value value. domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns pointer to a cookie node in the linked list, or NULL otherwise.
struct soap_cookie *soap_cookie(struct soap *soap, const char *name, const char *domain, const char *path);
Find a cookie in the database with name name and value value. domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns pointer to a cookie node in the linked list, or NULL otherwise.
char *soap_cookie_value(struct soap *soap, const char *name, const char *domain, const char *path);
Get value of a cookie in the database with name name. domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns the string pointer to the value, or NULL otherwise.
long soap_cookie_expire(struct soap *soap, const char *name, const char *domain, const char *path);
Get expiration value of the cookie in the database with name name (in seconds). domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. Returns the expiration value, or -1 if cookie does not exist.
int soap_set_cookie_expire(struct soap *soap, const char *name, long expire, const char *domain, const char *path);
Set expiration value expire of the cookie in the database with name name (in seconds). domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns SOAP_OK, or SOAP_EOF otherwise.
int soap_set_cookie_session(struct soap *soap, const char *name, const char *domain, const char *path);
Set cookie in the database with name name to be a session cookie. This means that the cookie will be returned to the client. (Only cookies that are modified are returned to the client). domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns SOAP_OK, or SOAP_EOF otherwise.
int soap_clr_cookie_session(struct soap *soap, const char *name, const char *domain, const char *path);
Clear cookie in the database with name name to be a session cookie. domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path. If successful, returns SOAP_OK, or SOAP_EOF otherwise.
void soap_clr_cookie(struct soap *soap, const char *name, const char *domain, const char *path);
Remove cookie from the database with name name. domain and path may be NULL to use the current domain and path given by soap_cookie_domain and soap_cookie_path.
int soap_getenv_cookies(struct soap *soap);
Initializes cookie database by reading the 'HTTP_COOKIE' environment variable. This provides a means for a CGI application to read cookies send by a client. If successful, returns SOAP_OK, or SOAP_EOF otherwise.
void soap_free_cookies(struct soap *soap);
Release cookie database.


The following global variables are used to define the current domain and path:

Attribute value
const char *cookie_domain MUST be set to the domain (host) of the service
const char *cookie_path MAY be set to the default path to the service
int cookie_max maximum cookie database size (default=32)


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;
}

 

19.30  Connecting Clients Through Proxy Servers

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.

19.31  FastCGI Support

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.

19.32  How to Create gSOAP Applications With a Small Memory Footprint

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:

  • No I/O timeouts. Note that many socket operations already obey some form of timeout handling, such as a connect timeout for example.
  • No UDP support
  • No HTTP keep alive
  • No HTTP cookies
  • No HTTP authentication
  • No HTTP chunked output (but input is OK)
  • No HTTP compressed output (but input is OK when compiled with WITH_GZIP)
  • No send/recv timeouts
  • No socket flags (no soap.socket_flag, soap.connect_flag, soap.bind_flag, soap.accept_flag)
  • No canonical XML output
  • No logging
  • Limited TCP/IP and HTTP error diagnostic messages
  • No support for time_t serialization
  • No support for LONG64/ULONG64 serialization (use typedef long xsd__long)
  • No support for hexBinary serialization (remap hexBinary to strings by adding a remap entry to typemap.dat)

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.

19.33  How to Eliminate BSD Socket Library Linkage

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.

19.34  How to Combine Multiple Client and Server Implementations into one Executable

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.

19.35  How to Build a Client or Server in a C++ Code Namespace

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.

19.36  How to Create Client/Server Libraries

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.

19.36.1  C++ Clients Example

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.

19.36.2  C Clients Example

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.

19.36.3  C Services Chaining Example

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;
}

 

19.37  How to Create DLLs

19.37.1  Create the Base stdsoap2.dll

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.

19.37.2  Creating Client and Server DLLs

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.

19.38  gSOAP Plug-ins

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:

  1. if the fcopy() callback is set by the plugin initialization, this callback will be called to allow the plugin to copy its local data upon a soap_copy() call. When soap_done() is called on the soap struct copy, the fdelete() callback is called for deallocation and cleanup of the local data.
  2. if the fcopy() callback is not set, then the plugin data will be shared (i.e. the data pointer points to the same address). The fdelete() callback will not be called upon a soap_done() on a copy of the soap struct. The fdelete() callback will be called for the original soap struct with which the plugin was registered.

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.

19.38.1  The Message Logging and Statistics Plug-in

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.

19.38.2  RESTful Interface: The HTTP GET Plug-in

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);

 

19.38.3  RESTful Interface: The HTTP POST Plug-in

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.

19.38.4  The HTTP MD5 Checksum Plug-in

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.

19.38.5  The HTTP Digest Authentication Plug-in

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.

19.38.6  The WS-Addressing Plug-in

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:

wsaapi.h and wsaapi.c WS-Addressing plugin (C and C++)


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.

19.38.7  The WS-ReliableMessaging Plug-in

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:

wsrmapi.h and wsrmapi.c WS-ReliableMessaging plugin (C and C++)


Also needed are:

threads.h and threads.c Multithreading and locking support


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.

19.38.8  The WS-Security Plug-in

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:

wsseapi.h and wsseapi.c WS-Security plugin (C and C++)


Also needed are:

smdevp.h and smdevp.c Streaming XML signature and message digest engine
mecevp.h and mecevp.c Streaming XML encryption engine
threads.h and threads.c Multithreading and locking support


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.

19.38.9  WS-Discovery

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.

你可能感兴趣的:(SOAP)