|
<?php |
|
/** |
|
* SoapClientTimeout Class |
|
* |
|
* Example class on how to extend PHP's SOAPClient with a curl based HTTP transport layer. |
|
*/ |
|
|
|
/** |
|
* Class SoapClientTimeout |
|
* |
|
* Drop-in replacement for PHP's SoapClient class supporting connect and response/transfer timeout |
|
* |
|
* Usage: Exactly as PHP's SoapClient class, with three new options available: |
|
* |
|
* timeout The response/transfer timeout in milliseconds; 0 == default SoapClient / CURL timeout |
|
* connecttimeout The connection timeout; 0 == default SoapClient / CURL timeout |
|
* sslverifypeer FALSE to stop SoapClient from verifying the peer's certificate (not recommended) |
|
* |
|
* Limitations: |
|
* |
|
* - Does not take php ini setting default_socket_timeout into account. |
|
* |
|
* @author hakre <https://hakre.wordpress.com> |
|
* @author Styxit <https://github.com/styxit> |
|
* @author Robert F. Ludwick <https://therobzone.wordpress.com/about-robert/> |
|
* @link http://web.archive.org/web/20140802215339/http://www.darqbyte.com/2009/10/21/timing-out-php-soap-calls |
|
* @link https://therobzone.wordpress.com/2009/10/21/timing-out-php-soap-calls/ |
|
*/ |
|
class SoapClientTimeout extends SoapClient |
|
{ |
|
/** |
|
* @var int timeout in milliseconds |
|
*/ |
|
private $timeout = 0; |
|
|
|
/** |
|
* @var int connection timeout in milliseconds |
|
*/ |
|
private $connectTimeout = 0; |
|
|
|
/** |
|
* @var bool |
|
*/ |
|
private $sslVerifyPeer = true; |
|
|
|
private $password = null; |
|
private $login = null; |
|
|
|
/** |
|
* @param mixed $wsdl |
|
* @param array $options |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function __construct($wsdl, array $options) |
|
{ |
|
if (!function_exists('curl_init')) { |
|
throw new RuntimeException('Curl extension needed'); |
|
} |
|
|
|
$options = $this->__consumeCurlOnlyOptions($options); |
|
|
|
$this->__extractLoginInformation($options); |
|
|
|
parent::__construct($wsdl, $options); |
|
} |
|
|
|
/** |
|
* @param int|null $milliseconds timeout in milliseconds |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function __setTimeout($milliseconds) |
|
{ |
|
$this->__validateOptionalMilliseconds($milliseconds, 'timeout'); |
|
|
|
$this->timeout = $milliseconds; |
|
} |
|
|
|
/** |
|
* @return int|null timeout in milliseconds |
|
*/ |
|
public function __getTimeout() |
|
{ |
|
return $this->timeout; |
|
} |
|
|
|
/** |
|
* @param int|null $milliseconds |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function __setConnectTimeout($milliseconds) |
|
{ |
|
$this->__validateOptionalMilliseconds($milliseconds, 'connect timeout'); |
|
|
|
$this->connectTimeout = $milliseconds; |
|
} |
|
|
|
/** |
|
* @return int|null |
|
*/ |
|
public function __getConnectTimeout() |
|
{ |
|
return $this->connectTimeout; |
|
} |
|
|
|
/** |
|
* @param $sslVerifyPeer |
|
* |
|
* @throws InvalidArgumentException |
|
*/ |
|
public function __setSslVerifyPeer($sslVerifyPeer) |
|
{ |
|
if (!is_bool($sslVerifyPeer)) |
|
throw new InvalidArgumentException( |
|
sprintf("Invalid ssl verify peer value %s", var_export($sslVerifyPeer, true)) |
|
); |
|
|
|
$this->sslVerifyPeer = $sslVerifyPeer; |
|
} |
|
|
|
/** |
|
* @return bool |
|
*/ |
|
public function __getSSLVerifyPeer() |
|
{ |
|
return $this->sslVerifyPeer; |
|
} |
|
|
|
/** |
|
* @param string $request |
|
* @param string $location |
|
* @param string $action |
|
* @param int $version |
|
* @param bool $one_way |
|
* |
|
* @return string|null |
|
* @throws RuntimeException |
|
*/ |
|
public function __doRequest($request, $location, $action, $version, $one_way = FALSE) |
|
{ |
|
// Call via Curl and use the timeout a |
|
$curl = curl_init($location); |
|
if ($curl === false) { |
|
throw new RuntimeException('Curl initialisation failed'); |
|
} |
|
|
|
/** @var $headers array of headers to be sent with request */ |
|
$headers = array( |
|
'User-Agent: PHP-SOAP', |
|
'Content-Type: text/xml; charset=utf-8', |
|
'SOAPAction: "' . $action . '"', |
|
'Content-Length: ' . strlen($request), |
|
); |
|
|
|
$options = array( |
|
CURLOPT_VERBOSE => false, |
|
CURLOPT_RETURNTRANSFER => true, |
|
CURLOPT_POST => true, |
|
CURLOPT_POSTFIELDS => $request, |
|
CURLOPT_HEADER => $headers, |
|
CURLOPT_HTTPHEADER => array(sprintf('Content-Type: %s', $version == 2 ? 'application/soap+xml' : 'text/xml'), sprintf('SOAPAction: %s', $action)), |
|
CURLOPT_SSL_VERIFYPEER => $this->sslVerifyPeer |
|
); |
|
|
|
// Timeout in milliseconds |
|
$options = $this->__curlSetTimeoutOption($options, $this->timeout, 'CURLOPT_TIMEOUT'); |
|
|
|
// ConnectTimeout in milliseconds |
|
$options = $this->__curlSetTimeoutOption($options, $this->timeout, 'CURLOPT_CONNECTTIMEOUT'); |
|
|
|
// Set login information |
|
if ($this->password && $this->login) { |
|
$options[CURLOPT_USERPWD] = $this->login . ':' . $this->password; |
|
$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; |
|
} |
|
|
|
$this->__setCurlOptions($curl, $options); |
|
|
|
$response = curl_exec($curl); |
|
|
|
if (curl_errno($curl)) { |
|
$errorMessage = curl_error($curl); |
|
$errorNumber = curl_errno($curl); |
|
curl_close($curl); |
|
throw new RuntimeException($errorMessage, $errorNumber); |
|
} |
|
|
|
curl_close($curl); |
|
|
|
// Return? |
|
if ($one_way) { |
|
return null; |
|
} else { |
|
return $response; |
|
} |
|
} |
|
|
|
/** |
|
* small wrapper to fail on each option for better error messages |
|
* |
|
* @param $curl |
|
* @param array $options |
|
*/ |
|
private function __setCurlOptions($curl, array $options) |
|
{ |
|
foreach ($options as $option => $value) { |
|
if (false !== curl_setopt($curl, $option, $value)) { |
|
continue; |
|
} |
|
|
|
throw new InvalidArgumentException( |
|
sprintf('Failed setting CURL option %d (%s) to %s', $option, $this->__getCurlOptionName($option), var_export($value, true)) |
|
); |
|
} |
|
} |
|
|
|
/** |
|
* @param int $option |
|
* |
|
* @return string |
|
*/ |
|
private function __getCurlOptionName($option) |
|
{ |
|
static $curlOptions; |
|
|
|
if (null === $curlOptions) { |
|
$constants = get_defined_constants(true); |
|
$constants = isset($constants['curl']) ? $constants['curl'] : array(); |
|
$curlOptions = array(); |
|
foreach ($constants as $name => $value) { |
|
if (strpos($name, 'CURLOPT') !== 0) { |
|
continue; |
|
} |
|
if (isset($curlOptions[$value])) { |
|
$curlOptions[$value] .= ", $name"; |
|
} else { |
|
$curlOptions[$value] = $name; |
|
} |
|
|
|
} |
|
unset($constants); |
|
} |
|
|
|
return isset($curlOptions[$option]) ? $curlOptions[$option] : "UNDEFIND_CURLOPT_($option)"; |
|
} |
|
|
|
/** |
|
* Conditionally set a timeout value in milliseconds by CURL option name. |
|
* |
|
* Safely degrades to full second values with outdated installations. |
|
* |
|
* Supported CURL option (pairs) are: |
|
* |
|
* CURLOPT_TIMEOUT: The maximum number of seconds to allow cURL functions to execute. |
|
* CURLOPT_TIMEOUT_MS: The maximum number of milliseconds to allow cURL functions to execute. If libcurl is |
|
* built to use the standard system name resolver, that portion of the connect will |
|
* still use full-second resolution for timeouts with a minimum timeout allowed of one |
|
* second. |
|
* Added in cURL 7.16.2. Available since PHP 5.2.3. |
|
* |
|
* CURLOPT_CONNECTTIMEOUT: The number of seconds to wait while trying to connect. Use 0 to wait indefinitely. |
|
* CURLOPT_CONNECTTIMEOUT_MS: The number of milliseconds to wait while trying to connect. Use 0 to wait |
|
* indefinitely. If libcurl is built to use the standard system name resolver, that |
|
* portion of the connect will still use full-second resolution for timeouts with a |
|
* minimum timeout allowed of one second. |
|
* Added in cURL 7.16.2. Available since PHP 5.2.3. |
|
* |
|
* Implicitly supported as a fall-back option is: |
|
* |
|
* CURLOPT_NOSIGNAL has to be set to 1 to prevent problems on some systems where timeout is below 1000 |
|
* milliseconds. This is because of "cURL Error (28): Timeout was reached" for timeout < 1000 ms in effect on |
|
* *nix standard system name resolver. <http://www.php.net/manual/en/function.curl-setopt.php#104597> |
|
* |
|
* CURLOPT_NOSIGNAL: TRUE to ignore any cURL function that causes a signal to be sent to the PHP process. This is |
|
* turned on by default in multi-threaded SAPIs so timeout options can still be used. |
|
* Added in cURL 7.10. |
|
*/ |
|
private function __curlSetTimeoutOption($options, $milliseconds, $name) |
|
{ |
|
if ($milliseconds > 0) { |
|
if (defined("{$name}_MS")) { |
|
$options["{$name}_MS"] = $milliseconds; |
|
} else { |
|
$seconds = ceil($milliseconds / 1000); |
|
$options[$name] = $seconds; |
|
} |
|
|
|
if ($milliseconds <= 1000) { |
|
$options[CURLOPT_NOSIGNAL] = 1; |
|
} |
|
} |
|
|
|
return $options; |
|
} |
|
|
|
/** |
|
* Get login information from a SOAPClient $options array |
|
* |
|
* @param array $options |
|
*/ |
|
private function __extractLoginInformation(array $options) |
|
{ |
|
if (isset($options['login']) && isset($options['password'])) { |
|
$this->login = $options['login']; |
|
$this->password = $options['password']; |
|
} |
|
} |
|
|
|
/** |
|
* Set and remove curl-only options defined for this class from a SOAPClient $options array. |
|
* |
|
* @param array $options |
|
* |
|
* @return array $options with the set options removed |
|
*/ |
|
private function __consumeCurlOnlyOptions(array $options) |
|
{ |
|
$curlOnly = array( |
|
'timeout' => '__setTimeout', |
|
'connecttimeout' => '__setConnectTimeout', |
|
'sslverifypeer' => '__setSslVerifyPeer', |
|
); |
|
|
|
foreach ($options as $key => $value) { |
|
$lower = trim(strtolower($key)); |
|
if (!isset($curlOnly[$lower])) { |
|
continue; |
|
} |
|
call_user_func(array($this, $curlOnly[$lower]), $value); |
|
unset($options[$key]); |
|
} |
|
|
|
return $options; |
|
} |
|
|
|
/** |
|
* validate a timeout value that can be passed for timeout or connection timeout. |
|
* |
|
* it represents a timeout in milliseconds. |
|
* |
|
* it is optional (NULL allowed) and must otherwise be a non-negative integer. |
|
* |
|
* @param $milliseconds |
|
*/ |
|
private function __validateOptionalMilliseconds($milliseconds, $name) |
|
{ |
|
if (!is_int($milliseconds) && !is_null($milliseconds) || $milliseconds < 0) { |
|
throw new InvalidArgumentException( |
|
sprintf("Invalid %s value, must be a positive integer, %s given", $name, var_export($milliseconds, true)) |
|
); |
|
} |
|
} |
|
} |