gSOAP WS-ReliableMessaging: The wsa plugin for client applications and stand-alone services

Main Page | Class List | File List | Class Members | File Members | Related Pages

# The wsa plugin for client applications and stand-alone services

## WS-Addressing Setup

The material in this section relates to the WS-Addressing specification.

To use the wsa plugin:

1. Run wsdl2h -t typemap.dat on a WSDL of a service that requires WS-Addressing headers. The typemap.dat file included in the gSOAP package is used to recognize and translate Addressing header blocks.
2. Run soapcpp2 -a on the header file produced by wsdl2h. To enable addressing-based service operation selection, you MUST use soapcpp2 option -a. This allows the service to dispatch methods based on the WS-Addressing action information header value (when the wsa plugin is registered).
3. (Re-)compile and link stdsoap2.c/pp or libgsoap, (dom.c/pp when needed), wsaapi.c and the soapcpp2-generated source files.
4. Use the wsa plugin API functions described below.

An example wsa client/server application can be found in samples/wsa.

A gSOAP service definitions header file with a "wsa import" to support WS-Addressing is automatically generated by wsdl2h for a set of WSDLs that use WS-Addressing. The wsdl2h-generated header file should be further processed by soapcpp2 to generate the binding code. The wsaapi.h and wsaapi.c implement the WS-Addressing API described in this document.

A wsdl2h-generated service definitions header file might include the following imports:

    #import "soap12.h"
    #import "wsa.h" // or wsa3.h (2003/03), wsa4.h (2004/03), wsa5.h (2005/03)

The wsa.h header file is imported from import/wsa.h when soapcpp2 is run on this file. The wsa.h import can be manually added to enable WS-Addressing when needed. The gSOAP service definitions header file is processed with soapcpp2 to generate the client-side and/or server-side binding code.

Note that the wsa.h, wsa3.h, wsa4.h, and wsa5.h header files are located in the import directory of the gSOAP package. These files define the WS-Addressing information header elements and types. The soap12.h header file enables SOAP 1.2 messaging.

For developers: the WS-Addressing header blocks in wsa.h (and others) were generated from the WS-Addressing schema with the wsdl2h tool and WS/WS-typemap.dat as follows:

        > wsdl2h -cegy -o wsa.h -t WS/WS-typemap.dat WS/WS-Addressing.xsd

Refer to wsa.h for more details.

## Client-side Usage

### Constructing WS-Addressing Information Headers

To associate WS-Addressing information headers with service operations, the SOAP Header struct SOAP_ENV__Header must have been defined and for each service operation that uses WS-Addressing method-header-part directives should be used in the gSOAP service definitions header file as follows:

    #import "wsa.h"

    //gsoap ns service method-header-part: example wsa__MessageID
    //gsoap ns service method-header-part: example wsa__RelatesTo
    //gsoap ns service method-header-part: example wsa__From
    //gsoap ns service method-header-part: example wsa__ReplyTo
    //gsoap ns service method-header-part: example wsa__FaultTo
    //gsoap ns service method-header-part: example wsa__To
    //gsoap ns service method-header-part: example wsa__Action
    //gsoap ns service method-action: example urn:example/examplePort/example
    int ns__example(char *in, struct ns__exampleResponse *out);

Note that the use of wsa versions determines the wsa prefix, e.g. use wsa5 for the latest WS-Addressing as in wsa5__MessageID.

In the client-side code, the WS-Addressing information headers are set with soap_wsa_request() by passing an optional message UUID string, a mandatory destination address URI string, and a mandatory request action URI string. The wsa plugin should be registered with the current soap struct context. An optional source address information header can be added with soap_wsa_add_From() (must be invoked after the soap_wsa_request call).

For example:

    #include "wsaapi.h"
    soap_register_plugin(soap, soap_wsa);

    if (soap_wsa_request(soap, RequestMessageID, ToAddress, RequestAction))
     || soap_wsa_add_From(soap, FromAddress)) // optional: add a 'From' address
      ... // error: out of memory

    if (soap_call_ns__example(soap, ToAddress, NULL, ...))
      soap_print_fault(soap, stderr); // an error occurred
    else
      // process the response

### Information Headers for Relaying Server Responses

To relay the response to another destination, the WS-Addressing ReplyTo information header is added with soap_wsa_add_ReplyTo() by passing a reply address URI string. The service returns HTTP 202 ACCEPTED to the client when the response message relay was successful.

For example:

    #include "wsaapi.h"
    soap_register_plugin(soap, soap_wsa);

    if (soap_wsa_request(soap, RequestMessageID, ToAddress, RequestAction)
     || soap_wsa_add_From(soap, FromAddress) // optional: add a 'From' address
     || soap_wsa_add_ReplyTo(soap, ReplyToAddress))
      ... // error: out of memory

    if (soap_call_ns__example(soap, ToAddress, NULL, ...))
    {
      if (soap->error == 202) // HTTP ACCEPTED
        printf("Request was accepted and results were forwarded\n");
      else
        soap_print_fault(soap, stderr); // an error occurred
    }
    else
      // unexpected OK: for some reason the response was not relayed

Note: the response message will be relayed when the From address is absent or different than the ReplyTo address

### Information Headers for Relaying Server Faults

To relay a server fault message to another destination, the WS-Addressing FaultTo information header is added with soap_wsa_add_FaultTo() by passing a relay address URI string. The service returns HTTP 202 ACCEPTED to the client when the fault was relayed.

For example:

    #include "wsaapi.h"
    soap_register_plugin(soap, soap_wsa);

    if (soap_wsa_request(soap, RequestMessageID, ToAddress, RequestAction)
     || soap_wsa_add_From(soap, FromAddress) // optional: add a 'From' address
     || soap_wsa_add_FaultTo(soap, FaultToAddress))
      ... // error: out of memory

    if (soap_call_ns__example(soap, ToAddress, NULL, ...))
    {
      if (soap->error == 202) // HTTP ACCEPTED
        printf("A fault occurred and the fault details were forwarded\n");
      else
        soap_print_fault(soap, stderr); // a connection error occurred
    }
    else
      // process response

Note that the call can still return a fault, such as a connection error when the service is not responding. In addition to the fault relay, the responses can be relayed with soap_wsa_add_ReplyTo().

### Error Handling

SOAP and HTTP errors set the soap->error attribute, as shown in this example:

    if (soap_call_ns__example(soap, ToAddress, NULL, ...))
    {
      if (soap->error == 202) // HTTP ACCEPTED
        printf("A fault occurred and the fault details were forwarded\n");
      else
        soap_print_fault(soap, stderr); // a connection error occurred
    }
    else
      // process response

When a WS-Addressing error occurred, the wsa error code is stored in the SOAP Fault Subcode field. This information can be retrieved with:

    wsa__FaultSubcodeValues fault;
    if (soap_wsa_check_fault(soap, &fault))
    {
      switch (fault)
      {
        case wsa__InvalidMessageInformationHeader: ...
        case wsa__MessageInformationHeaderRequired: ...
        case wsa__DestinationUreachable: ...
        case wsa__ActionNotSupported: ...
        case wsa__EndpointUnavailable: ...
      }
    }

When using wsa5.h, please refer to the standards and fault codes for this implementation. For the wsa5.h 2005/03 standard, several faults have an additional parameter (SOAP Fault detail):

    wsa5__FaultCodesType fault;
    char *info;
    if (soap_wsa_check_fault(soap, &fault, &info))
    {
      switch (fault)
      {
        case wsa5__InvalidAddressingHeader:
          if (info)
            printf("The invalid addressing header element is %s\n", info);
        ...
      }
    }

### Combining WS-Addressing with WS-Security

WS-Security can be combined with WS-Addressing. To sign WS-Addressing header blocks, use the soap_wsse_set_wsu_id() WSSE-plugin call to set the wsu:Id attribute and signing of these attributed elements. For example, suppose we use WS-Addressing 2005:

    #include "wsaapi.h"
    #include "wsseapi.h"
    soap_register_plugin(soap, soap_wsa);
    soap_register_plugin(soap, soap_wsse);

    soap_wsse_set_wsu_id(soap, "wsa5:From wsa5:To wsa5:ReplyTo wsa5:FaultTo wsa5:Action");
    if (soap_wsa_request(soap, RequestMessageID, ToAddress, RequestAction)
     || soap_wsa_add_From(soap, FromAddress) // optional: add a 'From' address
     || soap_wsa_add_FaultTo(soap, FaultToAddress))
      ... // error: out of memory
    if (soap_call_ns__example(soap, ToAddress, NULL, ...))
      ... // error

Note: soap_wsse_set_wsu_id() should only be set once. Each new call overrides the previous.

For more details on WS-Security, please see the WSSE plugin documentation.

## Server-side Usage

The wsa plugin should be registered with:

    soap_register_plugin(soap, soap_wsa);

Once the plugin is registered, the soap_bind(), soap_accept(), and soap_serve() functions can be called to process requests and semi-automatically handle the WS-Addressing header blocks.

Important: to dispatch service operations based on the WS-Addressing wsa:Action information header, you must use soapcpp2 option -a. The generates a new dispatcher (in soapServer.c) based on the action value.

A service operation implementation should use soap_wsa_check() to verify the validity of the WS-Addressing information headers in the SOAP request message. To allow response message to be automatically relayed based on the ReplyTo information header, the service operation should return soap_wsa_reply() with an optional message UUID string and a mandatory response action string.

For example:

    int ns__example(struct soap *soap, char *in, struct ns__exampleResponse *out)
    { if (soap_wsa_check(soap))
        return soap->error;
      // ... service logic
      return soap_wsa_reply(soap, ResponseMessageID, ResponseAction);
    }

To return a SOAP fault that is automatically relayed to a fault service based on the FaultTo information header, the soap_wsa_sender_fault(), soap_wsa_receiver_fault(), soap_wsa_sender_fault_subcode(), and soap_wsa_receiver_fault_subcode() functions should be used instead of the soap_sender_fault(), soap_receiver_fault(), soap_sender_fault_subcode(), and soap_receiver_fault_subcode(), respectively.

For example, the following service operation illustrates the use of soap_wsa_check() to verify and process WS-Addressing header blocks and soap_wsA_reply() to enable responses to be relayed as per ReplyTo address in the WS-Addressing header:

    int ns__example(struct soap *soap, char *in, struct ns__exampleResponse *out)
    { if (soap_wsa_check(soap))
        return soap->error;
      // ... service logic
      // ... an error occurred, need to return fault possibly to fault service:
        return soap_wsa_sender_fault(soap, "Exception in service operation", NULL);
      // ... normal execution continues
      return soap_wsa_reply(soap, ResponseMessageID, ResponseAction);
    }

## HTTPS Server-side Usage

To enable HTTPS (SSL/TSL) servers, compile the sources with -DWITH_OPENSSL (and link with libgsoapssl, libssl, and libcrypto). Because WS-Addressing may relay messages over HTTPS as a sender (client), you must initialize the SSL context for server and client uses. Therefore, the context must have access to all the certificates need to verify the authenticity of the ReplyTo and FaultTo HTTPS servers. To do so, use the following SSL initialization before soap_bind():

    struct soap *soap = soap_new();
    if (soap_ssl_server_context(soap,
      SOAP_SSL_DEFAULT,
      "server.pem", // the keyfile (server should authenticate)
      "password",   // password to read the key file
      "cacert.pem", // cacert file to store trusted certificates (role as client)
      NULL,         // optional capath
      NULL,         // DH file name or DH param key len bits, when NULL use RSA
      NULL,         // file with random data to seed randomness
      "myserver"    // unique server identification for SSL session cache
    ))
    { soap_print_fault(soap, stderr);
      ...
    }
    soap->bind_flags = SO_REUSEADDR;
    if (!soap_valid_socket(soap_bind(soap, NULL, port, 100)))
    { soap_print_fault(soap, stderr);
      ...
    }

## Implementing a Server for Handling ReplyTo Response Messages

To implement a separate server for handling relayed SOAP response messages based on the ReplyTo information header in the request message, the gSOAP header file should include a one-way service operation for the response message.

For example, suppose a service operation returns an exampleResponse message. We declare the one-way exampleResponse operation as follows:

    #import "wsa.h"

    //gsoap ns service method-header-part: exampleResult wsa__MessageID
    //gsoap ns service method-header-part: exampleResult wsa__RelatesTo
    //gsoap ns service method-header-part: exampleResult wsa__From
    //gsoap ns service method-header-part: exampleResult wsa__ReplyTo
    //gsoap ns service method-header-part: exampleResult wsa__FaultTo
    //gsoap ns service method-header-part: exampleResult wsa__To
    //gsoap ns service method-header-part: exampleResult wsa__Action
    //gsoap ns service method-action: exampleResult urn:example/examplePort/exampleResponse
    int ns__exampleResponse(char *out, void);

Note that the action information is important, because it is used by the service dispatcher (assuming soapcpp2 option -a is used).

The implementation in the server code uses soap_wsa_check() to check the presense and validity of the WS-Addressing information header in the message. The soap_send_empty_response() function should be used to return an acknowledgment HTTP header with HTTP 202 ACCEPTED to the sender:

    int ns__exampleResponse(struct soap *soap, char *out)
    { if (soap_wsa_check(soap))
        return soap_send_empty_response(soap, 500); // HTTP 500 Internal Server Error
      // ... service logic
      return soap_send_empty_response(soap, SOAP_OK); // HTTP 202 ACCEPTED
    }

## Implementing a Server for Handling FaultTo Fault Messages

To implement a separate server for handling relayed SOAP fault messages based on the FaultTo information header in the request message, the gSOAP header file for soapcpp2 should include a SOAP fault service operation. This operation accepts fault messages that are relayed by other services.

Basically, we use a trick to generate the SOAP-ENV:Fault struct via a one-way service operation. This allows us both to implement a one-way service operation that accepts faults and to automatically generate the fault struct for fault data storage and manipulation.

The fault operation in the header file should be declared as follows (for the 2004/08 standard):

    //gsoap SOAP_ENV service method-action: Fault http://schemas.xmlsoap.org/ws/2004/08/addressing/fault
    int SOAP_ENV__Fault
    (       _QName                   faultcode,             // SOAP 1.1
            char                    *faultstring,           // SOAP 1.1
            char                    *faultactor,            // SOAP 1.1
            struct SOAP_ENV__Detail *detail,                // SOAP 1.1
            struct SOAP_ENV__Code   *SOAP_ENV__Code,        // SOAP 1.2
            struct SOAP_ENV__Reason *SOAP_ENV__Reason,      // SOAP 1.2
            char                    *SOAP_ENV__Node,        // SOAP 1.2
            char                    *SOAP_ENV__Role,        // SOAP 1.2
            struct SOAP_ENV__Detail *SOAP_ENV__Detail,      // SOAP 1.2
            void
    );

Because each service operation has a struct to hold its input parameters, we automatically generate the (original) SOAP_ENV__Fault struct on the fly!

Note: it is important to associate the wsa fault action with this operation as shown above.

The implementation of the service operation in the server code is:

    int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail)
    {
      ... = faultcode; // SOAP 1.1 fault code string (QName)
      ... = faultstring; // SOAP 1.1 fault string
      ... = faultactor; // SOAP 1.1 fault actor string
      ... = detail; // SOAP 1.1 fault detail struct
      ... = SOAP_ENV__Code; // SOAP 1.2 fault code struct
      ... = SOAP_ENV__Reason; // SOAP 1.2 reason struct
      ... = SOAP_ENV__Node; // SOAP 1.2 node string
      ... = SOAP_ENV__Role; // SOAP 1.2 role string
      ... = SOAP_ENV__Detail; // SOAP 1.2 detail struct
      return SOAP_OK;
    }

Note that SOAP 1.1 or SOAP 1.2 parameters are set based on the 1.1/1.2 messaging requirements.