Proxy & Debug SOAP Requests in PHP

crm_soap_php1Last week, we needed to debug soap requests in php due an issue on our live environment. We attempted to integrate our Magento store with an external provider that used SOAP to communicate to our severs. Everything was working fine on our testing/staging environments, but when we went live, they contacted us due an issue in the connection:

Error Fetching http body, No Content-Length, connection closed or chunked data.

After a quick search, we found an easy fix by falling back to HTTP 1.0 on the client connection:

$client = new SoapClient("http://yourserver.com/api/?wsdl",

array(
 'stream_context' => stream_context_create(array('http' => array('protocol_version' => 1.0) ) )
 )
 );

However, to make things more interesting, the external service wasn’t willing to do any changes on their side, which means that we needed to find another solution, on our side.

It did seem to be an environment related issue rather than a code problem. Anyway, we tried to debug the whole connection flow, to see if we could find clues of how to solve the problem.

We have a sample script that we use to debug connections locally (using xdebug), with the code below:

class Debug_SoapClient extends SoapClient
{
 public function __doRequest($request, $location, $action, $version, $one_way = 0)
 {
 $aHeaders = array(
 'Method: POST',
 'Connection: Close',
 'Content-Type: text/xml',
 'SOAPAction: "'.$action.'"'
 );

$ch = curl_init($location);
 curl_setopt_array(
 $ch,
 array(
 CURLOPT_VERBOSE => false,
 CURLOPT_RETURNTRANSFER => true,
 CURLOPT_POST => true,
 CURLOPT_POSTFIELDS => $request,
 CURLOPT_HEADER => false,
 CURLOPT_HTTPHEADER => $aHeaders,
 CURLOPT_SSL_VERIFYPEER => true,
 CURLOPT_COOKIE => "XDEBUG_SESSION=PHPSTORM"
 )
 );

$ret = curl_exec($ch);
 return $ret;
 }
}

//Use soap as always, just replacing the classname while instantiating the object:
$client = new Debug_SoapClient('http://myurl.com/api/?wsdl');

However, we can’t use xdebug on live environment, so we tried to find a way to debug the connection flow, so that we could compare the differences of using HTTP 1.0 ┬ávs the standard way.

After a couple of unsuccessful attempts, we found a hacky way to proxy-forward the requests through curl, so that we could debug the input and the output of each call.

Firstly, we created a test php file that would log the environment variables on a file for each request, and then it would forward it to the soap api:

file_put_contents(__DIR__ . '/soap.log', var_export($_SERVER, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_REQUEST, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_GET, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_POST, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($HTTP_RAW_POST_DATA, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_GET, true), FILE_APPEND);
// Override the REQUEST_URI variable so that the framework can understand and process the soap request
$_SERVER['REQUEST_URI'] = '/api/?wsdl';
include 'index.php';

Note that sometimes $_POST might be empty but $HTTP_RAW_POST_DATA might contain data.

After comparing the logs of the two requests, we noticed few differences on the $_SERVER variables, but even overriding the values didn’t help.

More research revealed that the issue might be related with a bug in some php versions:

It is an HTTP 1.1 issue with some versions of PHP not properly decoding chunked data (even some versions of PHP 5.3.x will still see this error, the documentation on PHP’s official site is wrong). You have two options in this case:

(1) Update your version of PHP to 5.4.x or later.
(2) Force the client library to use HTTP 1.0

We tried to connect using another app-server with an older php version, and it worked without having to fallback to http 1.0.

Then we thought about proxy-ing the requests to the other server internally, just to test if that would solve the issue. Eventually we managed to get the requests forwarded with another script file:

header('Content-Type: text/xml; charset=UTF-8');
$ch = curl_init('https://external.server.com/soaptest.php/' . ($_GET ? '?' . http_build_query($_GET) : 'api/index/index/'));
curl_setopt_array(
 $ch,
 array(
 CURLOPT_VERBOSE => false,
 CURLOPT_RETURNTRANSFER => true,
 CURLOPT_POST => $_SERVER['REQUEST_METHOD'] == 'POST' ? true : false,
 CURLOPT_POSTFIELDS => $HTTP_RAW_POST_DATA,
 CURLOPT_HEADER => false,
 CURLOPT_SSL_VERIFYPEER => false,
 CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
 )
 );
$ret = curl_exec($ch);
file_put_contents(__DIR__ . '/soap.log', var_export($ret, true), FILE_APPEND);
echo $ret;

With this script curl-ing the previous one on the other server, we managed to get the SOAP calls to work. It’s something that we did only for debugging purposes, but hopefully it can be useful for you in case you face similar issues and you want to discard possibilities. Sadly, that still didn’t solve the issue for the non http 1.0 requests, but at least we were able to compare all the inputs and all the outputs and we noticed that it was all working fine both ways until the actual soap call:

– Instantiating the class with the url

– Authenticating to soap by calling login()

– Calling to a custom soap call: ie.┬ásales_order.info would return the proper results in both cases, but only the http 1.0 connection would properly retrieve them. The standard connection would retry the call and eventually display the error message showed below.

Eventually, we solved the issue by temporarily making the requests directly hitting the old php version server, until the php version was upgraded on the non working server.

One thought on “Proxy & Debug SOAP Requests in PHP

Leave a Reply

Your email address will not be published. Required fields are marked *