SOAP Agent
A C++ SOAP Client Library v3.0
SoapAgent is a C++ library for accessing web services. With the library, users 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.
· Support WSDL.
· Flexible SOAP Message Template (See below).
· Does not depend on third party components.
· Embedded transport protocols.
· Plug-in SAX XML Parser.
· Light weight, with very small memory footprint.
· Works on Win 95/98/NT/2000.
· Compatible with many SOAP 1.1 implementations.
· Capable of handling any data types (with message templates).
The version 3.0 of the library has the following limitations:
· Works only on Windows platform.
· Support only UTF-8 encoding.
The installation is very easy. After downloading the library, run the installation program. It will copy the following files into the specified directory:
Debug: A directory contains the debug version of SoapActor.dll
Sample: A directory contains a sample SOAP client function.
SoapActor.dll: The SoapAgent implementation library.
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.
SoapTest.exe: A small test program.
XMLParser.dll: The XML Parser implementation.
dbsock.dll: A SOAP transport DLL.
license.txt: The license agreement.
readme.txt: Last minutes changes and other information.
zlib.dll: A data compression library.
The SAMPLE subdirectory contains a sample project that demonstrates how to use the library.
· Include the SoapClientApi..h file in your project, remember to add the SoapAgent directory in the INCLUDE path.
· Make sure your linker can locate SoapActor.lib by adding it to your LIB path.
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* *
ppInputArray, std::basic_string<TCHAR> **ppOutputArray, unsigned
int*pOutSize)=0;
// execute a SOAP method given a message template
virtual HRESULT ExecuteMethod(const TCHAR* szServerURI,
const TCHAR* szTemplateFile, const TCHAR* szMethodName, TCHAR**ppNameArray,
TCHAR** ppInputArray, std::basic_string<TCHAR> **ppOutputArray, unsigned
int*pOutSize)=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 request header
virtual void SetHeader(const TCHAR* szName, const TCHAR
* szValue)=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;
};
extern
"C" SoapAgent* MakeSoapAgent(const TCHAR* szUserName, const TCHAR*
szPassword, int nDebugMode=0);
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.
Here are some simple steps for using the SoapAgent objects:
1. Create
a SoapAgnet 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.
The sample shows how to write a SOAP client using the SoapAgent object. The function invokes the Method1 function provided by a test web service at http://www.SoapClient.com, which echoes the two input string parameters.
SoapResponder()
{
// initialize the parameter array for the method call. The
array must end with NULL.
TCHAR* pParams[3]={"My First Param", "My Second
Param", NULL};
SoapAgent *pSoapAgent= MakeSoapAgent(NULL, NULL);
std::string * pOutput;
unsigned int nSize=0;
HRESULT hr;
if(SUCCEEDED(hr=pSoapAgent->ExecuteMethod(
"http://www.soapclient.com/xml/soapresponder.wsdl",
// WSDL file
"Method1", //
method name to be invoked.
pParams, //
array of input parameters.
&pOutput, //
array of output parameters
&nSize //
number of output variable in pOutput
)))
{
// 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;
}
Parameters of simple data types are passed as 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 may use an input parameter array:
TCHAR*
ppParams[6]={“5643”,”false”,”Delaware”,
“9683.667”,” 1904-01-01T04:47:47-08:00”, NULL};
Note that the last element in the array is NULL, this is the only way SoapAgent object can figure out the size of the array. 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>
As can be seen from the example, the order of your parameter is very important. It must match that defined in the WSDL file.
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* ppParams[6]={“Delaware”,”false”,”-23”,
“34”,”-16”, NULL};
The parameters are put into the request message in sequential order:
<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 assumption here is that the number of elements in the array is fixed.
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.
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.
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 because web services are often provided by third parties. The client side object would have to be recompiled, linked and tested. With message templates, it is as simple as editing a text file.
There are two ExecuteMethod functions in the SoapAgent class: one uses WSDL files and another uses message templates. The latter is defined as follows:
virtual
HRESULT ExecuteMethod(const TCHAR* szServerURI, const TCHAR* szTemplateFile,
const TCHAR* szMethodName, TCHAR**ppNameArray, TCHAR** ppInputArray,
std::basic_string<TCHAR> **ppOutputArray, unsigned int*pOutSize);
It requires the following parameters:
· szServerURI: The absolute URI for the web service, sometime referred to as the endpoint.
· szTemplateFile: The name of the message template file.
· ppNameArray: An array of names of the input variables.
· ppInputArray: An array of string values of the input variables.
· ppOutputArray: An array of output variables in the SOAP response message.
· pOutSize: The number of elements in the ppOutputArray.
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.
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 name space prefix.
The library has built-in support for SSL. It switches to SSL automatically when the endpoint uses HTTPS. Protocol switches under the following situations:
· Executing Method with WSDL: SSL will be used if the soap:address element in the service definition specified HTTPS in the URL.
· Executing Method with template: SSL will be used if the HTTPS is specified in the szServerURL parameter.
extern
"C" SoapAgent* MakeSoapAgent(const TCHAR* szUserName, const TCHAR*
szPassword, int nDebugMode=0);
· szUserName : The name of the user if authentication is required, NULL otherwise.
· szPassword: The password string for accessing a web service. It can be NULL if authentication is not required.
· nDebugMode: This is an integer value from 0 to 5 that specifies the logging mode, with 5 being the most verbose. The library creates a log file, named debug.log, under the current directory if nDebugMode is none zero, and puts detailed processing information into the file. nDebugMode=4 is adequate in most of the situations.
The function returns a SoapAgent object.
The class factory creates a SoapAgent object when the function is called. The class factory may caches the object for later use when needed.
extern
"C" void DestroySoapAgent(SoapAgent* pSoapAgent);
· pSoapAgent : A pointer the SoapAgent object.
None.
The function should always be called in pair with MakeSoapAgnet. It releases a SoapAgent object. The object may not necessarily be deleted from memory if the class factory is cache enabled.
virtual
HRESULT ExecuteMethod(const TCHAR* szWSDL, const TCHAR* szMethodName, TCHAR* * ppInputArray,
std::basic_string<TCHAR> **ppOutputArray, unsigned int*pOutSize);
· szWSDL : The address of the web service description file. It can be a local file or a remote file given by an absolute URI.
· szMethodName: The name of the method to be invoked.
· ppInputArray: An array of string pointers to be passed as input parameters. The last element in the array must be NULL. All parameters are passed as strings, although the library may perform conversion when needed.
· ppOutputArray: An array of string pointers that are returned from the server, including the returned value and those parameters passed by references. Note that the library owns the memory allocated for the returned values, the caller should not free the returned pointers.
· pOutSize: A pointer that points to the total number of returned parameters in ppOutputArray.
The function returns S_OK if the method call was successful, which only means that it successfully received the response from the server. The method itself may have failed, however, on the server side due to other reasons. The GetErrorString may be used for examining the causes of a failure.
This is the main function to access remote methods using the library. Note that the ppInputArray may be empty if no input parameter is required. There may also be no returned value from SOAP server even under normal situation (i.e., a one-way message).
HRESULT
ExecuteMethod(const TCHAR* szServerURI, const TCHAR* szTemplateFile, const
TCHAR* szMethodName, TCHAR**ppNameArray, TCHAR** ppInputArray,
std::basic_string<TCHAR> **ppOutputArray, unsigned int*pOutSize);
· szServerURL : The URL where the service can be accessed. It is also called the end point.
· szTemplateFile: The name of the message template file.
· szMethodName: The name of the remote method.
· ppNameArray: This is an array of variable names to be used in the template instantiation. The last element of the array must be NULL.
· ppInputArray: An array of string pointers to be used as values in the message template. The last element in the array must be NULL.
· ppOutputArray: An array of string pointers that are returned from the server, including the returned value and those parameters passed by references. Note that the library owns the memory allocated for the returned values, the caller should not free the returned pointers.
· pOutSize: A pointer that points to the total number of returned parameters in ppOutputArray.
The function returns S_OK if the method call was successful, which only means that it successfully received the response from the server. The method itself may have failed, however, on the server side due to other reasons. The GetErrorString may be used for examining the causes of a failure.
This function accesses the remote method using a template message. Template tokens in the message template are substituted by the values given in the ppInputArray. Note that the ppInputArray may be empty if no input parameter is required. There may also be no returned value from SOAP server even under normal situation (i.e., a one-way message).
void
SetProxy(const TCHAR* szProxyServer, short nPort=80, const TCHAR* szProxyUser=NULL,
const TCHAR* bstrProxyPwd=NULL);
· szProxyServer: The name of the proxy server.
· nPort: The port number of the proxy server.
· szProxyUser: The authorized user name for the proxy server if authentication is required.
· bstrProxyPwd: The password.
None
Call this function only when there is a proxy server between the SOAP client and the SOAP server. The function must be called before ExecuteMethod for the proxy to take effect. The function adds the Authorization header in the message if user name and password are provided.
TCHAR
*GetErrorString();
· None
The string contains error messages.
The function returns error occurred on both the client side and/or server side. These may include:
1. Any error conditions that happened before posting data to the server.
2. Error condition occurred during data transmission.
3. Error returned by the remote function.
4. Fault conditions defined by the SOAP protocol.
TCHAR* GetResponse(const
TCHAR* szElementName, bool bUseNamespace)=0;
· szElementName: The name of the element. It can contain names of multiple levels separated by '|'. For example, "Envelope|Body" will return the body part of the response. The complete response message will be returned if the parameter is NULL.
· bUseNamespace: The parameter should be true if szElementName contains name space prefix. Otherwise false.
The string represents the object identified by szElementName. It may be an XML structure if the object is not a leaf node.
The function is usually used to deal with complicated structures from a SOAP response. The ExecuteMethod function puts all simple values into the ppValues array in most cases.
The following code gets a SOAP response body:
GetResponse("Envelope|Body",
false);