I am having some problems with a SoapServer sending me some responses with some invalid XML 1.0 chars like U+001F throwing a SoapFault "looks like we got no XML document".
My current code is:
class SoapClientNG extends \SoapClient {
public function __doRequest($request, $location, $action, $version, $one_way = 0) {
$xml = parent::__doRequest($request, $location, $action, $version, $one_way);
$response = str_ireplace('', '', $xml);
return $response;
}
}
Is there a simple way to convert all non 1.0 chars to nothing or even a way to enable SoapClient to accepts xml 1.1?
Related
I'm creating a SOAP request via PHP native SoapClient class. When I dump the request using SoapClient::__getLastRequest I'll get:
<?xml version="1.0" encoding="UTF-8"?>\n
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:microsoft-dynamics-schemas/page/itemb2cws"><SOAP-ENV:Body><ns1:ReadMultiple><ns1:filter/><ns1:setSize>100</ns1:setSize></ns1:ReadMultiple></SOAP-ENV:Body></SOAP-ENV:Envelope>
Is there any way, how to remove the line feed or any other whitespace between XML elements?
Our client is using some old version of ERP Microsoft Dynamics Nav and the server falls when gets whitespace in request.
Solved
Via https://stackoverflow.com/questions/14363147/php-soapclient-malformed-xml#:~:text=override%20the%20SoapClient::__doRequest
Just extend native PHP SoapClient and override the SoapClient::__doRequest() method.
Example:
<?php
declare(strict_types=1);
namespace App\Soap;
use SoapClient;
class Client extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$request = $this->filter($request);
parent::__doRequest($request, $location, $action, $version, $one_way);
}
private function filter(string $request): string
{
return preg_replace( '#\R+#', '', $request);
}
}
And you're done.
I want to override SoapClient::__doRequest() in my custom class and use my own curl based transport, as suggested here.
According to the PHP manual, __doRequest() has this signature:
public string SoapClient::__doRequest ( string $request , string $location , string $action , int $version [, int $one_way = 0 ] )
And the $request parameter is:
request The XML SOAP request.
But this seems to be wrong. I don't get any XML in $request but a cryptic string like:
string(1496) " 14120188025349F
Here's the simple class I've used:
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = false)
{
var_dump($request);
return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
So what is wrong? Did they somehow change the internal implementation of this without updating the manual? For reference, my PHP version is 5.4.6 on a Ubuntu 12.10 box.
I am trying to simply send RAW xml to a webservice via PHP and SoapClient. The problem is when I encode my XML it changes the order of elements in the XML that is converted to an associative array.
// Initialize the Soap Client:
$this->_transactionServicesClient = new SoapClient($soapWSDLUrl);
How or what would be the best way to send the following XML as a string to my SoapClient?
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.micros.com/pos/les/TransactionServices">
<SOAP-ENV:Body>
<ns1:PostTransaction>
<ns1:REQ>
<ns1:RequestHeader>
<ns1:InterfaceVersion>3.0.7</ns1:InterfaceVersion>
<ns1:ClientName>TRANS_SERVICES</ns1:ClientName>
</ns1:RequestHeader>
<ns1:CheckDetailEntries>
<ns1:MenuItem>
<ns1:ReferenceEntry>Pizza4</ns1:ReferenceEntry>
<ns1:Count>1</ns1:Count>
<ns1:Price>10.00</ns1:Price>
<ns1:ItemNumber>112001</ns1:ItemNumber>
<ns1:PriceLevel>1</ns1:PriceLevel>
<ns1:Seat xsi:nil="true"/>
</ns1:MenuItem>
</ns1:CheckDetailEntries>
<ns1:CheckHeaderRequest>
<ns1:CheckId>03:21:05.050505</ns1:CheckId>
<ns1:GuestCount>1</ns1:GuestCount>
<ns1:GuestInformation>
<ns1:ID>001</ns1:ID>
<ns1:FirstName>xxx</ns1:FirstName>
<ns1:LastName>xxx</ns1:LastName>
<ns1:Address1>xxx Rd</ns1:Address1>
<ns1:Address2>xx</ns1:Address2>
<ns1:Address3>xx</ns1:Address3>
<ns1:PhoneNum>xx</ns1:PhoneNum>
<ns1:UserText1>None</ns1:UserText1>
<ns1:UserText2>None</ns1:UserText2>
<ns1:UserText3>None</ns1:UserText3>
<ns1:GUID></ns1:GUID></ns1:GuestInformation>
</ns1:CheckHeaderRequest>
<ns1:OrderTypeNumber>1</ns1:OrderTypeNumber>
</ns1:REQ>
</ns1:PostTransaction>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope: My answer below
Update/Resolution: Here is the code I used to extend the SOAP Client and send my raw Soap Envelope
Here is how I extended SoapClient:
<?php
class MySoapClient extends SoapClient {
function __construct($wsdl, $options) {
parent::__construct($wsdl, $options);
$this->server = new SoapServer($wsdl, $options);
}
public function __doRequest($request, $location, $action, $version)
{
$result = parent::__doRequest($request, $location, $action, $version);
return $result;
}
function __myDoRequest($array,$op) {
$request = $array;
$location = 'http://xxxxx:xxxx/TransactionServices/TransactionServices6.asmx';
$action = 'http://www.micros.com/pos/les/TransactionServices/'.$op;
$version = '1';
$result =$this->__doRequest($request, $location, $action, $version);
return $result;
}
}
// To invoke my new custom method with my Soap Envelope already prepared.
$soapClient = new MySoapClient("http://xxxx:xxxx/TransactionServices/TransactionServices6.asmx?WSDL", array("trace" => 1));
$PostTransaction = $soapClient->__myDoRequest($orderRequest,$op);
?>
Also posted on pastie.org: http://pastie.org/3687935 before I turned this into the answer.
For testing purposes, you can subclass SoapClient and override the __doRequest method - it receives the generated XML and you can pre-process it.
But better find what's going wrong with the XML encoding. You can use SoapVar and SoapParam instances to specify the exact way given object has to be represented. Those saved my life when namespaces had to be given
Consider this example SOAP Client script:
$SOAP = new SoapClient($WDSL); // Create a SOAP Client from a WSDL
// Build an array of data to send in the request.
$Data = array('Something'=>'Some String','SomeNumber'=>22);
$Response = $SOAP->DoRemoteFunction($Data); // Send the request.
On the last line, PHP takes the arguments from the array you specified, and, using the WSDL, builds the XML request to send, then sends it.
How can I get PHP to show me the actual XML it's built?
I'm troubleshooting an application and need to see the actual XML of the request.
Use getLastRequest. It returns the XML sent in the last SOAP request.
echo "REQUEST:\n" . $SOAP->__getLastRequest() . "\n";
And remember, this method works only if the SoapClient object was created with the trace option set to TRUE. Therefore, when creating the object, use this code:
$SOAP = new SoapClient($WDSL, array('trace' => 1));
$SOAP = new SoapClient($WSDL, array('trace' => true));
$Response = $SOAP->DoRemoteFunction($Data);
echo "REQUEST:\n" . htmlentities($SOAP->__getLastRequest()) . "\n";
This will not only print the last request but also make the XML tags visible in the browser.
If you'd like to view the request without actually making a connection, you can override SoapClient's __doRequest method to return the XML:
class DummySoapClient extends SoapClient {
function __construct($wsdl, $options) {
parent::__construct($wsdl, $options);
}
function __doRequest($request, $location, $action, $version, $one_way = 0) {
return $request;
}
}
$SOAP = new DummySoapClient('http://example.com/?wsdl', array('trace' => true));
echo $SOAP->GetRequestDetail($params);
Extending Quinn's answer, you can also just log the request before you perform the request.
class SoapClientDebug extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
error_log("REQUEST:\n" .$request . "\n");
error_log("LOCATION:\n" .$location . "\n");
error_log("ACTION:\n" .$action . "\n");
error_log("VERSION:\n" .$version . "\n");
error_log("ONE WAY:\n" .$one_way . "\n");
return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
You need to enable tracing when you create your SoapClient. Like so:
$SOAP = new SoapClient($WSDL, array('trace' => true));
$Data = array('Something'=>'Some String','SomeNumber'=>22);
Then call the __getLastRequest method after you've made a service call to see the XML.
$Response = $SOAP->DoRemoteFunction($Data);
echo $SOAP->__getLastRequest();
This will output the request XML.
More reading: http://www.php.net/manual/en/soapclient.getlastrequest.php
if you are running the client locally, Fiddler is a great implementation agnostic way of looking at the messages on the wire.
If you are running it remotely then you could use something like Apache TCPMON Standalone or through eclipse*
*just linking to the first hit from Google
The problem with Quinn Comendant's answer, that $request from __doRequest() will then be processed by __call() and the user will see an array of parameters instead of real xml request. To prevent this, such workaround can be used:
class DummySoapClient extends SoapClient {
function __construct($wsdl, $options) {
parent::__construct($wsdl, $options);
}
function __doRequest($request, $location, $action, $version, $one_way = 0) {
throw new Exception($request);
}
function __call($function_name, $arguments)
{
try {
parent::__call($function_name, $arguments);
} catch (Exception $e) {
return $e->getMessage();
}
}
}
Option trace is not necessary here, because we do not call __getLastRequest() or other relevant functions.
Is there such a beastie? The simple SOAP client that ships with PHP does not understand multi-part messages. Thanks in advance.
The native PHP SoapClient class does not support multipart messages (and is strongly limited in all WS-* matters) and I also I think that neither the PHP written libraries NuSOAP nor Zend_Soap can deal with this sort of SOAP messages.
I can think of two solutions:
extend the SoapClient class and overwrite the SoapClient::__doRequest() method to get hold of the actual response string which you can then parse at your whim.
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// parse $response, extract the multipart messages and so on
}
}
This could be somewhat tricky though - but worth a try.
use a more sophisticated SOAP client library for PHP. The first and only one that comes into my mind is WSO2 WSF/PHP which features SOAP MTOM, WS-Addressing, WS-Security, WS-SecurityPolicy, WS-Secure Conversation and WS-ReliableMessaging at the cost of having to install a native PHP extension.
Even though this answer has been given a lot here already, I have put together a general solutions, that keeps in mind, that the XML can come without the wrapper.
class SoapClientExtended extends SoapClient
{
/**
* Sends SOAP request using a predefined XML
*
* Overwrites the default method SoapClient::__doRequest() to make it work
* with multipart responses.
*
* #param string $request The XML content to send
* #param string $location The URL to request.
* #param string $action The SOAP action. [optional] default=''
* #param int $version The SOAP version. [optional] default=1
* #param int $one_way [optional] ( If one_way is set to 1, this method
* returns nothing. Use this where a response is
* not expected. )
*
* #return string The XML SOAP response.
*/
public function __doRequest(
$request, $location, $action, $version, $one_way = 0
) {
$result = parent::__doRequest($request, $location, $action, $version, $one_way);
$headers = $this->__getLastResponseHeaders();
// Do we have a multipart request?
if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
// Make all line breaks even.
$result = str_replace("\r\n", "\n", $result);
// Split between headers and content.
list(, $content) = preg_split("#\n\n#", $result);
// Split again for multipart boundary.
list($result, ) = preg_split("#\n--#", $content);
}
return $result;
}
}
This only works if you initialize the SoapClientExtended with the option trace => true.
Using S. Gehrig second idea worked just fine here.
In most cases, you have just a single message packed into a MIME MultiPart message. In those cases a "SoapFault exception: [Client] looks like we got no XML document" exception is thrown. Here the following class should do just fine:
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// strip away everything but the xml.
$response = preg_replace('#^.*(<\?xml.*>)[^>]*$#s', '$1', $response);
return $response;
}
}
Follow the advice of rafinskipg from the PHP documentation:
Support for MTOM addign this code to your project:
<?php
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// parse $response, extract the multipart messages and so on
//this part removes stuff
$start=strpos($response,'<?xml');
$end=strrpos($response,'>');
$response_string=substr($response,$start,$end-$start+1);
return($response_string);
}
}
?>
Then you can do this
<?php
new MySoapClient($wsdl_url);
?>
Just to add more light to previous suggested steps. You must be getting response in following format
--uuid:eca72cdf-4e96-4ba9-xxxxxxxxxx+id=108
Content-ID: <http://tempuri.org/0>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body> content goes here </s:Body></s:Envelope>
--uuid:c19585dd-6a5a-4c08-xxxxxxxxx+id=108--
Just use following code (I am not that great with regex so using string functions)
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$response = parent::__doRequest($request, $location, $action, $version, $one_way);
// strip away everything but the xml.
$response = stristr(stristr($response, "<s:"), "</s:Envelope>", true) . "</s:Envelope>";
return $response;
}
A little bit more simple IMHO
class SoapClientUnwrappedXml extends SoapClient
{
const SOAP_ENVELOPE_REGEXP = '/^<soap:Envelope[^>]*>(.*)<\/soap:Envelope>/m';
/**
* Sends SOAP request using a predefined XML.
*
* Overwrites the default method SoapClient::__doRequest() to make it work
* with multipart responses or prefixed/suffixed by uuids.
*
* #return string The XML Valid SOAP response.
*/
public function __doRequest($request, $location, $action, $version, $one_way = 0): string
{
$result = parent::__doRequest($request, $location, $action, $version, $one_way);
$headers = $this->__getLastResponseHeaders();
if (preg_match('#^Content-Type:.*multipart\/.*#mi', $headers) !== 0) {
preg_match_all(self::SOAP_ENVELOPE_REGEXP, $result, $resultSanitized, PREG_SET_ORDER, 0);
$result = $resultSanitized[0][0] ?? $result;
}
return $result;
}
}
The code just worked for me with the option "trace => true" too.
Thanks for sharing!