Using SOAP Client

 

1.    Introduction

SoapAgent is a C++ library for accessing web services. With the library, you can access web services using just a few lines of code with no SOAP or XML knowledge required.  The library is an independent implementation of SOAP 1.1 specification. It supports Web Service Definition Language (WSDL).

 

The library was built using Microsoft VC++ 6.0 as a multi-threaded DLL.     

 

1.1.            Key Features

 

1.2.            Limitations

The version 3.0 of the library has the following limitations:

2.    Installation

The installation is very easy. After downloading the library, run the installation program. It will copy the following files into the specified directory:

 

Sample:                  A directory contains a sample SOAP client function.

SoapActor.lib:                The exported library.

SoapClient.doc:                The programming guide     

SoapClient.lic:                A generated license file. It is required for using SoapAgent.dll

SoapClientApi.h:  The main header file.

SoapActor.dll:                 The SoapAgent implementation library.

SoapTest.exe:                       A small test program.

XMLParser.dll:                The XML Parser implementation.

dbsock.dll:                A SOAP transport DLL.

SoapAgents.dll:                 A COM wrapper of the SOAP client library.

license.txt:                The license agreement.

readme.txt:                Last minutes changes and other information.

 

The SAMPLE subdirectory contains a sample project that demonstrates how to use the library.

3.    Using the library

3.1.            Environment Settings

 

3.2.            SoapAgent definition

All SOAP client services are provided inside a SoapAgent class. Its definition is in the SoapClientApi.h file. The file defines a pure virtual class and two exported functions from the class factory:

 

class SOAPAPI SoapAgent

{

       public:

              // execute a SOAP method given an WSDL file

              virtual HRESULT ExecuteMethod(const TCHAR* szWSDL, const TCHAR*  szMethodName, TCHAR** ppInputName, TCHAR** ppInputValue, vector<t_string>** ppOutputArray)=0;

              // execute a SOAP method given a message template

              virtual HRESULT ExecuteMethod(const TCHAR* szServerURI, const TCHAR* szTemplateFile, const TCHAR* szSoapAction, TCHAR**ppNameArray, TCHAR** ppInputArray, vector<t_string>**ppOutputArray)=0;

              // set the proxy

              virtual void SetProxy(const TCHAR* szProxyServer, short nPort=80, const TCHAR* szProxyUser=NULL, const TCHAR* bstrProxyPwd=NULL)=0;

              // get error description

              virtual TCHAR *GetErrorString()=0;

              // set the http request header

              virtual void SetHeader(const TCHAR* szName, const TCHAR * szValue)=0;

              // set soap header

              virtual void SetSoapHeader(const TCHAR* szName, const TCHAR * szValue, const TCHAR** pAttributeNames=NULL, const TCHAR** pAttributeValues=NULL)=0;

              // clear the request and response objects

              virtual void Clear()=0;

              // get the complete response string or a particular element

              virtual TCHAR* GetResponse(const TCHAR* szElementName, bool bUseNamespace)=0;

              // instantiate a template file using soap response

              virtual TCHAR* InstantiateTemplate(const TCHAR* szFileName, const TCHAR * szXMLString)=0;

};

 

extern "C" SoapAgent* MakeSoapAgent(const TCHAR* szUserName, const TCHAR* szPassword, int nDebugMode=0,const TCHAR* szCertFile=NULL);

extern "C" void DestroySoapAgent(SoapAgent* );

 

The SoapAgent specification is implemented inside the library, as the matter of fact, there are several implementations of the class. The class library creates a type of SoapAgent object based on the current configuration and the nature of the invocation.

 

There is no constructor and destructors in SoapAgent definition, so it cannot be constructed using the new operator. Instead, the library offers two exported functions: MakeSoapAgent and DestroySoapAgent,  for object management.

 

3.3.            Using SoapAgent Objects

 

Here are some simple steps for using the  SoapAgent object:

1.    Create a SoapAgent Object:
     SoapAgent * pMyAgent= MakeSoapAgent(NULL, NULL);

2.        Define an array of parameters to be passed to the remote server.

3.        Call the ExecuteMethod function and specify the WSDL and name of the method to be executed.

4.        Process the returned results.

5.        Destroy the SoapAgent using the DestroySoapAgent function.

 

This is best illustrated by the sample program below.

3.3.1.      A Sample Program

The sample shows how to write a SOAP client using SQLData SoapAgent object. The function invokes the Method1 method provided by a test web service at http://www.SoapClient.com, which echoes the two input string parameters.

 

SoapResponder()

{

       // initialize input parameters.

       TCHAR * ppParamNames[3]={"bstrParam1", "bstrParam2", NULL};

       TCHAR * ppParamValues[3]={ "My First Param", "My Second Param", NULL};

       // create a soapagent in debug mode (mode=4)

       SoapAgent *pSoapAgent= MakeSoapAgent(NULL, NULL, 4);

       if(pSoapAgent==NULL)

              return -1;

 

       HRESULT hr;

       if(SUCCEEDED(hr=pSoapAgent->ExecuteMethod(

              "https://soapclient.com/xml/soapresponder.wsdl", // WSDL file

              "Method1",          // method name to be invoked.

              vParamNames, // vector of input parameter names.

              vParamValues,       // vector of input parameter values.

              &pOutputValues       // pointer to a vector of output parameters

              )))

       {

              // print out results

              int nSize = pOutputValues->size();

              for(int i=0; i<nSize; i++)

                     _tprintf("%s\n", (*pOutputValues)[i].c_str());

       }

       else

       {

              // obtain error string when failed.

              _tprintf("Error String %s\n", pSoapAgent->GetErrorString());

       }

       DestroySoapAgent(pSoapAgent);

       return 0;

}

3.4.            Parsing Parameters

3.4.1.      Simple Data Types

Parameters of simple data types are passed as arrays of strings to the ExecuteMethod function. They are bound to the proper variables in the request message using the WSDL file.

 

For example, to invoke a SOAP function with 5 parameters of different types as follows:

 

        <param1 xsi:type="xsd:int">5643</param1>
        <param2 xsi:type="xsd:boolean">false</param2>
        <param3 xsi:type="xsd:string">Delaware</param3>
        <param4 xsi:type="xsd:double">9683.66666667</param4>
        <param5 xsi:type="xsd:timeInstant">1904-01-01T04:47:47-08:00</param5>
 
You would construct input parameter array as follows:

                TCHAR* ppParamValues[6]={“5643”,”false”,”Delaware”,

 “9683.667”,” 1904-01-01T04:47:47-08:00”, NULL};

                TCHAR* ppParamNames[6]={“Param1”,” Param2”,” Param3”,

 “Param4”,”Param5”, NULL};

 

The SoapAgent object constructs a request message similar to the following:

 

<?xml version="1.0"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
        <SOAP-ENV:Body>
                <myRequestMessage>
                    <param1 xsi:type="xsd:int">5643</param1>
                    <param2 xsi:type="xsd:boolean">false</param2>
                    <param3 xsi:type="xsd:string">Delaware</param3>
                    <param4 xsi:type="xsd:double">9683.66666667</param4>
                    <param5 xsi:type="xsd:timeInstant">1904-01-01T04:47:47-08:00</param5>
                    </myRequestMessage>
                </SOAP-ENV:Body>
        </SOAP-ENV:Envelope>

 

The type information is obtained from the WSDL file and filled in automatically.

 

3.4.2.      Array Data Types

The library supports single-dimension array parameters. Array parameters are passed in the similar fashion as the simple data types. Suppose there is a function with the following parameters:

 

1.        A string variable.

2.        A Boolean variable.

3.        An array of three integers.

 

You may use an input parameter array as the following:

 

TCHAR* ppParamValues[6]={“Delaware”,”false”,”-23”,

 “34”,”-16”, NULL};

TCHAR* ppParamNames[6]={“param1”,”param2”,”param3.item0”,

 “param3.item1”,”param3.item2”, NULL};

 

The parameters are put into the request message. The message would be something like:

 

        <myRequestMessage>
                <param1 xsi:type="xsd:int">Delaware</param1>
                <param2 xsi:type="xsd:boolean">false</param2>
                <param3 SOAP-ENC:arrayType="xsd:int[3]">
                    <item>-23</item>
                    <item>34</item>
                    <item>-16</item>
                </param3>
        </myRequestMessage>
 

The library binds variable by names. So, the order of the parameters is insignificant. This is different from the previous releases.

 

3.4.3.      Struct Types

Struct is also referred to as complex data type or custom types.  For instance, the following schema segment defines SOAPStruct as a complex type with three elements.

 

<complexType name="SOAPStruct">

          <sequence>

        <element name="varInt" type="xsd:int"/>

        <element name="varFloat" type="xsd:float"/>

        <element name="varString" type="xsd:string"/>

        </sequence>

</complexType>

 

To pass parameter of complex types, variable names should use the following format:

      VariableName.ElementName

For example, if inputStruct is of type SOAPStruct,

 

TCHAR* ppParamNames[4]={“inputStruct.varString”,”inputStruct.varInt”,

”inputStruct.varFloat”,NULL};

TCHAR* ppParamValues[4]={“SQLData SOAP Client”,”450”,”23.4”, NULL};

 

Note that the elements in the parameters may not be in the same order as it is defined. The library will bind the variables to the right position at runtime. The same variable-name encoding rule can be applied to embedded structure as well, allow you to pass nested structures of complex types. A zip code variable may be encoded as

                customers.address.zipcode

where address is a nested structure in customer.

3.5.            Message Templates

A message template is a file that contains a SOAP message with special tokens in it. The tokens are substituted at run-time using input values. Tokens are enclosed by “{$” and “}” in the file. For example, the following is a message template for a SOAP function with a single string parameter:

 

<?xml version="1.0"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
        <SOAP-ENV:Body>
                <{$MethodName}Request>
                    <param3 xsi:type="xsd:string">[$param3]</param3>
                </<{$MethodName}Request>
        </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

{$MethodName} and {$param3} are token variables in the message template. They are replaced by values of MethodName and param3 when the template is instantiated.

 

3.5.1.      Template Applications

There are many situations where template messages can be used, and they prove to be very powerful:

 

1.        WSDL Unavailable:  When there is no service description file available to aid parameter bindings, it is very difficult to construct SOAP request dynamically. Many SDKs or Toolkits simply don’t work without a WSDL file.  A message template file makes the job much easier.

2.        Complex Data Types: There are situations where many user-defined types involved in a SOAP method.  A template message can greatly simplify messages construction and parameter binding.

3.        When a web service offers optional parameters or variable number of parameters, message template provides an easy solution.

4.        Sending Literal Document: You can send arbitrary XML document using template messages. Different from RPC style messages, literal document is not necessarily SOAP section 5 compliant. For instance, UDDI messages do not use SOAP (section 5) binding, and they can be handled easily using template messages.

 

There are many advantages in using template messages:

1.        Build Generic Applications: Template messages allow you to take out service specific elements from your applications. So that a client application can be used to access many web services by simply using different template message files. SoapClient.com web site uses this technique to access user-defined web services.

2.        Handle Compatibility Issues: It is unfortunate that SOAP servers sometimes don’t work with each other. Even they are all SOAP 1.1 compliant in the future; there will still be differences of schemas and encoding styles for the same web service. A separate message template can be created for each service to deal with such problems.

3.        Universal Data Handling: With template file, any structure that can be expressed in XML format could be put into a pre-constructed file. There is really no data type that can’t be handled theoretically. Data binding becomes simple string substitution.

4.        Lower Maintenance Cost: Most of the SOAP SDK and Toolkits do static service binding by creating objects that match to the service definition file. Problems occur when the function signature changes, which is more likely than the traditional programming model. The client side object would have to be recompiled, linked and tested. With message templates, it is as simple as editing a text file.

 

3.5.2.      Invoke Method with Template

There are two ExecuteMethod functions in the SoapAgent class: one uses WSDL files and another uses message template. The latter is defined as follows:

 

virtual HRESULT ExecuteMethod(const TCHAR* szServerURI, const TCHAR* szTemplateFile, const TCHAR* szSoapAction, TCHAR**ppNameArray, TCHAR** ppInputArray, vector<t_string>**ppOutputArray);

It requires the following parameters:

 

The function takes care of all the complicated tasks such as message construction, wiring and response handling. The template file is instantiated using the supplied name-value array.

 

 

3.5.3.      An Message Template Example

This example invokes the same SOAP method as in the SoapResponder Function (Sample 1). A message template can be constructed as follows:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"

       xmlns:tns="http://www.SoapClient.com/xml/SoapResponder.wsdl"

       xmlns:xsd1="http://www.SoapClient.com/xml/SoapResponder.xsd"

       xmlns:soap="http://schemas.xmlsoap.org/wsdl/xml/"

       xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"

       SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/xml/encoding/">

  <SOAP-ENV:Body><mns:Method1 xmlns:mns="http://www.SoapClient.com/xml/SoapResponder.xsd">

      <bstrParam1 xsi:type="string">{$Param1}</bstrParam1>

      <bstrParam2 xsi:type="string">{$Param2}</bstrParam2>

      <bstrReturn xsi:type="string"></bstrReturn>

   </mns:Method1></SOAP-ENV:Body>

</SOAP-ENV:Envelope>

 

Note that there are two template tokens, namely Param1 and Param2, in the SOAP message.  Assume the name of the template file is TEST.TMPL, we can then create a function that instantiates the template and sends a complete SOAP message to the SOAP server:

 

TestMessageTemplate()

{

       // initialize the named parameter array for the method call. The array must end with NULL.

       TCHAR* ppParamNames[3]={"Param1", "Param2", NULL};

       TCHAR* ppParamValues[3]={"My First Param", "My Second Param", NULL};

       // create an soapagent in debug mode (mode=4)

       SoapAgent *pSoapAgent= MakeSoapAgent(NULL, NULL, 4);

       if(pSoapAgent==NULL)

              return -1;

 

       vector<std::string> * pOutput;

       unsigned int nSize=0;

       HRESULT hr;

       pSoapAgent->SetHeader("SoapAction", "/SoapObject");

       if(SUCCEEDED(hr=pSoapAgent->ExecuteMethod(

              "http://soapclient/xml/soapresponder.wsdl",   // end point address

              "TEST.TMPL", // template file name

              "/SoapObject",       // SOAPAction string

              ppParamNames,       // array of parameter names.

              ppParamValues,       // array of parameter values.

              &pOutput,       // vector of returned values

              )))

       {

              // print out results

              for(int i=0; i<nSize; i++)

                     _tprintf("%s\n", pOutput[i].c_str());

       }

       else

       {

              // obtain error string when failed.

              _tprintf("Error String %s\n", pSoapAgent->GetErrorString());

       }

       DestroySoapAgent(pSoapAgent);

       return 0;

}

 

The TestMessageTemplate function achieves the same result as the SoapResponder function. The differences are:

 

3.6.            Processing Responses

When WSDL is given, the SoapAgent object does good job parsing SOAP response messages and put returned variables into the ppValues array. There are situations, however, where the responses are so complex that a custom processing is needed. This can be done using the GetResponse funtion.

 

When a  SOAP responses is received, it is loaded into a XML parser and decomposed into a tree-like structure. The GetResponse function allows direct access to all nodes in the tree structure. A node in the tree is addressed using all node names, separated by the '|' character. Let's assume that the following message is received:

 

<?xml version='1.0' encoding='UTF-8'?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">

<SOAP-ENV:Body>

<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<return xsi:type="xsd:float">41.0</return>

</ns1:getTempResponse>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

 

GetResponse("Envelope", false) would returns a string that contains:

 

<SOAP-ENV:Body>

<ns1:getTempResponse xmlns:ns1="urn:xmethods-Temperature" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<return xsi:type="xsd:float">41.0</return>

</ns1:getTempResponse>

</SOAP-ENV:Body>

 

 and GetResponse("Envelope|Body|getTempResponse", false) gives:

<return xsi:type="xsd:float">41.0</return>

Finally, GetResponse("Envelope|Body|getTempResponse|return", false) returns just 41.0.

 

If the second parameter is true, element names must include the namespace prefix.