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("",

 '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);
 CURLOPT_POST => true,

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

//Use soap as always, just replacing the classname while instantiating the object:
$client = new Debug_SoapClient('');

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('' . ($_GET ? '?' . http_build_query($_GET) : 'api/index/index/'));
 CURLOPT_POST => $_SERVER['REQUEST_METHOD'] == 'POST' ? true : false,
$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. 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.

SOAP IP Address in PHP

This is an example about how to perform SOAP IP Address petitions over a certain IP address in php.

It can be useful in several situations. For instance, if you have a bunch of server behind a load balancer, all of them hosting the same sites (virtual hosts), and you need to comunicate directly to each of them. If you know the server’s ip address, you can comunicate directly to them by specifying the ip address and the host.
Basically, we need to extend the SoapClient class, overriding the __doRequest method in a way that allows us to specify a custom header in the HTTP petitions.

class HostSoapClient extends SoapClient {

 const DEBUG = true;

 * Override the method __doRequest to perform a custom CURL petition, specifying a host on the header
 * @param $request string
 * @param $location sting
 * @param $action string contain the host and the action in the format url#action
 * @param $version int
 * @param $one_way int
 * @return string
 public function __doRequest($request, $location, $action, $version, $one_way = 0) {
 $url_parts = parse_url($location);
 $ch = curl_init();
 list($host, $action) = explode('#', $action);
 if(self::DEBUG) {
 echo 'Performing request to: ' . $location . "\n";
 echo 'Host specified on the header: ' . $host . "\n";
 echo 'Request:' . $request . "\n";
 curl_setopt($ch,CURLOPT_URL, $location);
 curl_setopt($ch,CURLOPT_POSTFIELDS, $request);
 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Host: ' . $host, 'SoapAction: ' . $action));
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 $response = curl_exec($ch);
 return $response;

Usage example:

$server = ''; //The ip address where the page is hosted
$url = $server . '/api/index/index'; //Path of the api (tested on both v1 and v2)
$host = ''; //An existing hostname on the $server ip
$client = new HostSoapClient(null,
		array('trace' => 1,
			'exceptions' => 1,
			'cache_wsdl' => 0,
			'location'=> $url,
			'uri' => $host));
$session = $client->login('user', 'pass');
//Now you are connected and you can call any resource, ie.
$client->call($session, 'api.method', array('param', 'value'));