After the last article where I’ve shown how to consume WSDL Webservices with NTLM authentication, I present a particularity about that method.
The difficulty occurs when there’s more than one WSDL operation with the same name (that is, there are overloaded methods). When you don’t need NTLM authentication, the solution is rather simple, but when you do need NTLM authentication you need to be a little creative.
1. What are overloaded methods?
In order to understand what the issue is, you need to know what overloaded methods are. There are several programming languages that allows what is called overloaded methods, which means that you can have more than one method, with the same name with different arguments.
For instance, consider the following signatures of C# methods:
array ListaOpcoes (int start, int end)
array ListaOpcoes (int start, int end, int pageNumber, int perPage)
As you can see, there can be two (or more) methods with the same name with different arguments / parameters. This is typical in C# or Java.
As a side note PHP doesn’t allow overloaded methods.
2. How to invoke a specific WSDL operation
Typically you need to pass an object or an array with the correct structure (defined in the WSDL), to invoke the correct operation. However, when you need to use NTLM authentication, you’ll need to be creative.
2.1. Sample WDSL with multiple operations with the same name
At this point I’ll present a sample from a real case WSDL. Note that, if you’re having the same issue, the best way to resolve it is to analyse the WSDL.
The WSDL has, among other stuff, the following content:
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:s="https://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.url.pt/webservices" xmlns:s2="http://www.url.pt/webservices/AbstractTypes" xmlns:s1="http://microsoft.com/wsdl/types/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://www.url.pt/webservices" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
(....)
<s:element name="ListS">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="query" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="pageNumber" type="s:int" />
<s:element minOccurs="1" maxOccurs="1" name="pageSize" type="s:int" />
<s:element minOccurs="0" maxOccurs="1" name="sortBy" type="s:string" />
</s:sequence>
</s:complexType>
</s:element>
<s:element name="List">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="processQuery" type="tns:ProcessQuery" />
</s:sequence>
</s:complexType>
</s:element>
<s:complexType name="ProcessQuery">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="QueryGenericData" type="tns:QueryGenericData" />
<s:element minOccurs="1" maxOccurs="1" name="MyProcessesOnly" type="s:boolean" />
<s:element minOccurs="1" maxOccurs="1" name="IsAdminQuery" type="s:boolean" />
<s:element minOccurs="0" maxOccurs="1" name="Status" type="tns:ArrayOfString" />
<s:element minOccurs="1" maxOccurs="1" name="OrganizationName" type="s:boolean" />
<s:element minOccurs="1" maxOccurs="1" name="KeysOnly" type="s:boolean" />
<s:element minOccurs="0" maxOccurs="1" name="Classifications" type="tns:ArrayOfClassificationKey" />
<s:element minOccurs="0" maxOccurs="1" name="Classification" type="tns:ClassificationKey" />
<s:element minOccurs="0" maxOccurs="1" name="ProcessCode" type="s:string" />
(....)
</s:sequence>
</s:complexType>
(....)
<wsdl:message name="ListSSoapIn">
<wsdl:part name="parameters" element="tns:ListS" />
<wsdl:message name="ListSoapIn">
<wsdl:part name="parameters" element="tns:List" />
</wsdl:message>
(....)
<wsdl:operation name="List">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get a result query in an XmlDocument.</wsdl:documentation>
<wsdl:input name="ListS" message="tns:ListSSoapIn" />
<wsdl:output name="ListS" message="tns:ListSSoapOut" />
</wsdl:operation>
<wsdl:operation name="List">
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Get a result query in an DataSet.</wsdl:documentation>
<wsdl:input message="tns:ListSoapIn" />
<wsdl:output message="tns:ListSoapOut" />
</wsdl:operation>
(....)
</wsdl:definitions>
First there’s the definition of the element types. The ListS
element is of a complex type which has simple types (string, int, int, string) and the List
element is a complex type that may have only one property of type ProcessQuery
.
You’ll see that ProcessQuery
is a complex type that has other simple or complex types (for brevity, I’ve only included a few).
Further down the WSDL you can see the definition of the messages. In this sample there are two defined messages: ListSSoapIn
, which receives an object of type tns:ListS
and ListSoapIn
that receives an object of type tns:List
.
At the end you can see the operation’s definitions and this WSDL has two operations with the nam eList
: one receives the message tns:ListSSoapIn
and the other operation receives the message tns:ListSoapIn
.
The issue for which I’ll present the solution is how to specify what operation to consume.
2.2. Consume a specific WSDL operation without NTLM authentication
Typically it’s rather simple to specify what operation you want to consume. You just need to create an object or an array with the correct structure (that is, with the correct structure and elements defined in the message) and then call the webservice. For instance, to consume the method List
with the message tns:ListS
, you would need the following code (using SoapClient):
$client = new SoapClient("my.wsdl");
$parameters = array(
'query' => '',
'pageNumber' => 1,
'pageSize' => 10,
'sortBy' => 'name
);
$result = $client->List($parameters);
Another way to accomplish the same thing is to create an object, with stdClass
(additional information on this StackOverflow post). For example:
$client = new SoapClient("my.wsdl");
$parameters = new stdClass();
$parameters->query = '';
$parameters->pageNumber = 1;
$parameters->pageSize = 10;
$parameters->sortBy = 'name';
$result = $client->List($parameters);
2.3. Consume a specific WSDL operation with NTLM authentication
When you need NTLM authentication, the solution isn’t that simple. The reason for this is that, when you use NTLM authentication as I’ve shown in the last article, you’re extending SoapClient
and, specifically, the method __doRequest()
, which is responsible for the execution of SOAP requests.
The way I used to overcome this issue is to define the SoapAction
and create the XML envelope that is sent with the request. For this specific method I created my own request using cURL
. Here is the code I’ve used to call the List
operation that receives the envelope with the type tns:ProcessQuery
(take a look at the comments):
// Manually create the XML envelope
$request = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.url.pt/webservices">
<soapenv:Header/>
<soapenv:Body>
<web:List>
<web:processQuery>
<web:ProcessCode>' . $params->ProcessCode . '</web:ProcessCode>
<web:Year>' . $params->Year . '</web:Year>
<web:NumberFrom>' . $params->NumberFrom . '</web:NumberFrom>
<web:NumberTo>' . $params->NumberTo . '</web:NumberTo>
<web:Subject>' . $params->Subject . '</web:Subject>
</web:processQuery>
</web:List>
</soapenv:Body>
</soapenv:Envelope>';
// Create a cURL object and set the default properties
$soap_do = curl_init();
curl_setopt($soap_do, CURLOPT_URL, 'my.wsdl);
curl_setopt($soap_do, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($soap_do, CURLOPT_TIMEOUT, 10);
curl_setopt($soap_do, CURLOPT_RETURNTRANSFER, true);
curl_setopt($soap_do, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($soap_do, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($soap_do, CURLOPT_POST, true);
// Here you set the cURL content to the string that has the XML envelope (previously created)
curl_setopt($soap_do, CURLOPT_POSTFIELDS, $request);
// This is a special property and allows you to analyse cURL requests using Fiddler
// @see https://www.telerik.com/fiddler
curl_setopt($soap_do, CURLOPT_PROXY, '127.0.0.1:8888');
// At this point we will set the HTTP headers and define the correct SOAPAction. The SOAPAction must be the same as the element (s:element).
// In this case you would set the SOAPAction as List or ListS:
curl_setopt($soap_do, CURLOPT_HTTPHEADER, array(
'Method: POST',
'User-Agent: PHP-SOAP-CURL',
'Content-Type: text/xml; charset=UTF-8',
'SOAPAction: "http://www.myurl.pt/webservices/List"',
'Accept-Encoding: gzip,deflate',
));
// Set the correct encoding for the request
curl_setopt($soap_do, CURLOPT_ENCODING, "gzip");
// Finally, don't forget to set the correct uername and password for the NTLM authentication.
curl_setopt($soap_do, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
curl_setopt($soap_do, CURLOPT_USERPWD, 'domain\user:password');
// Execute the request and get the server's response
$curlResponse = curl_exec($soap_do);
As you can see the solution was to create my own XML envelope and create the request manually, using cURL. This allowed me to set the correct envelope format (in $request
) and assure the SOAPClient
is the correct operation.
Although this isn’t a very elegant solution, due to the need to specify the XML and create the request manually, this is the best (and only) solution I’ve found for an issue that isn’t that common. Whenever you can use SoapClient or other wrapper class, but if you’re desperate for a solution, this might solution might come in handy.