Home > Technology, Work > Timing Out PHP Soap Calls

Timing Out PHP Soap Calls

So I’ve got an interesting technical post for you today. I know I don’t normally post technical things here on my blog, but I felt this was such an interesting exercise in triumphing over a big issue here at work that I wanted to post about it.

We call several third-party vendors for web-based services in my department. We’re really keen on keeping transaction times low, so we have a set timeout for each vendor to ensure we don’t wait too long. Most of our vendor calls are Curl calls, but we have a couple that use Soap.

Curl has built-in functionality to enable a timeout. PHP’s internal Soap library does not. Thus, PHP’s documentation says to enable the following to set a limit on Soap calls:

ini_set("default_socket_timeout", 5); // 5 seconds

This is all fine and dandy. Or, at least, I thought it was. When we first had an outage with one Soap-based vendor, this timeout mechanism worked correctly. However the second time, it did not. We still waited up to 60 seconds for the call despite our code having not changed. Highly distressing is the word.

I researched this issue at great length, and used one of our own Soap services to test out a theory. I put this into the code:

ob_implicit_flush();
echo " ";
sleep(15);

The ob_implicit_flush() function call forces PHP to send any output as it immediately becomes available. Normally, PHP sends it all at the close of the script, or if you use other output buffering functions. Here, I’m forcing some content to be passed back to the caller then sleeping beyond the wait time of 5 seconds.

The results? It waited. So the socket timeout feature in PHP only applies until you receive content. If you receive any content within the socket timeout interval, it will keep the socket open and continue to wait. The timeout actually serves from the opening of the connection to the reception of content, not the entire length of time the socket will remain open.

Thus, I had to find a new route to keeping our Soap calls short. My next attempt was limiting the script execution time via either of these two functions:

set_time_limit(5); // 5 seconds
ini_set("max_execution_time", 5); // 5 seconds

Unfortunately, this didn’t help. First of all, both of these two functions have the exact same effect and use the same underlying PHP functionality. Secondly, they only set limits for internal PHP execution. Any time you have an external data source or blocking system call, this is not calculated in the execution time (at least on Linux; on Windows everything is considered). So any database calls, Soap calls, system calls… These are untimed.

At this point I was at my wits end. I could not figure out a way to limit Soap calls save for building a barebones script to make the Soap call and calling that script with a Curl call.

I ventured into the Soap documentation on the PHP website to see if there was a way I could use the SoapClient class to build the Soap request XML and to parse a Soap response XML into an object, thus allowing me to transport the XML in whatever way I chose. No dice. However, I did discover something interesting while looking at the documentation.

PHP allows you to extend the SoapClient class, I knew that. What I did not know is that you could override certain functions, one of them being __doRequest(). By overriding this function, you can make the request to the remote server however you like.

So I tested this out. And holy crap, it worked. The input to the function is the actual Soap XML, not a Soap object, and the function simply returns the Soap response XML, not an object. It is also passed a few other things, such as the location of the Soap web service. We’re in business.

I built a class extending SoapClient and enabled timeout functionality. When a timeout is used, it actually uses Curl for the call and sets the timeout there. When no timeout is required, it uses the default mechanism to send the request. See part of my class below. It may not be totally robust, but hey, I just needed a timeout. And I couldn’t give you the entire class functionality either. I gotta save something for myself.

class SoapClientTimeout extends SoapClient
{
	private $timeout;
 
	public function __setTimeout($timeout)
	{
		if (!is_int($timeout) && !is_null($timeout))
		{
			throw new Exception("Invalid timeout value");
		}
 
		$this->timeout = $timeout;
	}
 
	public function __doRequest($request, $location, $action, $version, $one_way = FALSE)
	{
		if (!$this->timeout)
		{
			// Call via parent because we require no timeout
			$response = parent::__doRequest($request, $location, $action, $version, $one_way);
		}
		else
		{
			// Call via Curl and use the timeout
			$curl = curl_init($location);
 
			curl_setopt($curl, CURLOPT_VERBOSE, FALSE);
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
			curl_setopt($curl, CURLOPT_POST, TRUE);
			curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
			curl_setopt($curl, CURLOPT_HEADER, FALSE);
			curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
			curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout);
 
			$response = curl_exec($curl);
 
			if (curl_errno($curl))
			{
				throw new Exception(curl_error($curl));
			}
 
			curl_close($curl);
		}
 
		// Return?
		if (!$one_way)
		{
			return ($response);
		}
	}
}

Sphere: Related Content

Categories: Technology, Work Tags: , , ,
  1. December 15th, 2009 at 02:45 | #1

    Nice post. But when using class SoapClientTimeout, I got error (exception) like that: transfer closed with 391 bytes remaining to read. Can you fix it for me. Thanks!

    • December 15th, 2009 at 09:00 | #2

      You could try pushing your timeout a little longer to accommodate… See the whole point of using this class instead of the socket timeout is that if it isn’t done in a certain amount of time, it stops. With the socket timeout, if it starts within the timeout period, it’s allowed to finish.

      Do you get that error every time, regardless of the timeout length?

  2. December 15th, 2009 at 21:22 | #3

    - Do you get that error every time, regardless of the timeout length?
    - Yes!

    I checked log on Soap Server, request from Soap Client is already executed successfully. :(

    • December 16th, 2009 at 10:48 | #4

      It might be a problem with your CURL installation. Try just performing CURL calls outside of the SOAP class and debug there.

  3. April 15th, 2010 at 10:59 | #5

    Thanks so much for this – this solved a problem I was having with a bug in php where timeouts don’t work for https calls.

  1. December 28th, 2009 at 15:09 | #1
  2. March 23rd, 2010 at 05:52 | #2