Skip to content

Instantly share code, notes, and snippets.

@hakre
Forked from styxit/SoapClientTimeout.class.php
Last active September 8, 2021 09:01
Show Gist options
  • Save hakre/da6435f967d40c66f479 to your computer and use it in GitHub Desktop.
Save hakre/da6435f967d40c66f479 to your computer and use it in GitHub Desktop.

Extend from SoapClient Examples

Just a collection gist of some assorted example SOAPClients. Code must not be stable or useful under all circumstances, these are examples. Most of the code is outdated, so you won't need to use it any longer in production code. I've just collected and compiled this together out of interest, the information normally is scattered around.

If you need to a start with PHP's SOAPClient start with the PHP manual page of it and read through the comments as well. Double check with exisiting bug-reports if given as many things are fixed since a comment was left.

SoapClientTimeout - Allow timeout settings with SOAP Requests

Example SOAPClient that overrides SOAPClient::__doRequest() to use curl to make the HTTP request.

SoapClientAuth - Allow HTTPAuth Access to WSDL

Example SOAPClient that registers a new stream-wrapper based on curl to allow WSDL retrieval from password protected sources.

<?php
/**
* SoapClientAuth for accessing Web Services protected by HTTP authentication
* Author: tc
* Last Modified: 04/08/2011
* Update: 14/03/2012 - Fixed issue with CURLAUTH_ANY not authenticating to NTLM servers
* Download from: http://tcsoftware.net/blog/
*
* Copyright (C) 2011 tc software (http://tcsoftware.net)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SoapClientAuth
* The interface and operation of this class is identical to the PHP SoapClient class
* (http://php.net/manual/en/class.soapclient.php) except this class will perform HTTP authentication for both SOAP
* messages and while downloading WSDL over HTTP and HTTPS. Provide the options login and password in the options array
* of the constructor.
*
* @author tc
* @copyright Copyright (C) 2011 tc software
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @link http://php.net/manual/en/class.soapclient.php
* @link http://tcsoftware.net/
*/
class SoapClientAuth extends SoapClient
{
public $Username = NULL;
public $Password = NULL;
/**
*
* @param string $wsdl
* @param array $options
*/
function __construct($wsdl, array $options = NULL)
{
stream_wrapper_unregister('https');
stream_wrapper_unregister('http');
stream_wrapper_register('https', 'StreamWrapperHttpAuth');
stream_wrapper_register('http', 'StreamWrapperHttpAuth');
if ($options) {
$this->Username = $options['login'];
StreamWrapperHttpAuth::$Username = $this->Username;
$this->Password = $options['password'];
StreamWrapperHttpAuth::$Password = $this->Password;
}
parent::SoapClient($wsdl, ($options ? $options : array()));
stream_wrapper_restore('https');
stream_wrapper_restore('http');
}
function __doRequest($request, $location, $action, $version)
{
$headers = array(
'User-Agent: PHP-SOAP',
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "' . $action . '"',
'Content-Length: ' . strlen($request),
'Expect: 100-continue',
'Connection: Keep-Alive'
);
$this->__last_request_headers = $headers;
$ch = curl_init($location);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_FAILONERROR, FALSE);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_USERPWD, $this->Username . ':' . $this->Password);
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_VERBOSE, TRUE);
curl_setopt($ch, CURLOPT_CERTINFO, TRUE);
$response = curl_exec($ch);
if (($info = curl_getinfo($ch)) && $info['http_code'] == 200) {
return $response;
} else if ($info['http_code'] == 401) {
throw new RuntimeException ('Access Denied', 401);
} else if (curl_errno($ch) != 0) {
throw new RuntimeException(curl_error($ch), curl_errno($ch));
} else {
throw new RuntimeException('Error', $info['http_code']);
}
}
}
/**
* Class StreamWrapperHttpAuth
*/
class StreamWrapperHttpAuth
{
public static $Username = NULL;
public static $Password = NULL;
private $path = NULL;
private $position = 0;
private $buffer = NULL;
private $curlHandle = NULL;
public function stream_close()
{
if ($this->curlHandle) {
curl_close($this->curlHandle);
}
}
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->path = $path;
$response = $this->postRequest($this->path);
$this->buffer = ($response !== FALSE ? $response : NULL);
$this->position = 0;
return $response !== FALSE;
}
public function stream_eof()
{
return $this->position > strlen($this->buffer);
}
public function stream_flush()
{
$this->position = 0;
$this->buffer = NULL;
}
public function stream_read($count)
{
if ($this->buffer) {
$data = substr($this->buffer, $this->position, $count);
$this->position += $count;
return $data;
}
return FALSE;
}
public function stream_write($data)
{
return ($this->buffer ? TRUE : FALSE);
}
public function stream_seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->position = $offset;
break;
case SEEK_CUR:
$this->position += $offset;
break;
case SEEK_END:
$this->position = strlen($this->buffer) + $offset;
break;
}
return TRUE;
}
public function stream_tell()
{
return $this->position;
}
public function stream_stat()
{
return array('size' => strlen($this->buffer));
}
public function url_stat($path, $flags)
{
$response = $this->postRequest($path);
return array('size' => strlen($response));
}
protected function postRequest($path, $authType = CURLAUTH_ANY)
{
$this->curlHandle = curl_init($path);
curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($this->curlHandle, CURLOPT_FOLLOWLOCATION, TRUE);
if (StreamWrapperHttpAuth::$Username) {
curl_setopt($this->curlHandle, CURLOPT_HTTPAUTH, $authType);
curl_setopt($this->curlHandle, CURLOPT_USERPWD, StreamWrapperHttpAuth::$Username . ':' . StreamWrapperHttpAuth::$Password);
}
curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 3);
curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($this->curlHandle);
if (($info = curl_getinfo($this->curlHandle)) && $info['http_code'] == 200) {
if (curl_errno($this->curlHandle) == 0) {
return $response;
} else {
throw new RuntimeException(curl_error($this->curlHandle), curl_errno($this->curlHandle));
}
} else if ($info['http_code'] == 401) { // Attempt NTLM Auth only, CURLAUTH_ANY does not work with NTML
if ($authType != CURLAUTH_NTLM) {
return $this->postRequest($path, CURLAUTH_NTLM);
} else {
throw new RuntimeException ('Access Denied', 401);
}
} else if (curl_errno($this->curlHandle) != 0) {
throw new RuntimeException(curl_error($this->curlHandle), curl_errno($this->curlHandle));
} else {
throw new RuntimeException('Error', $info['http_code']);
}
}
}
<?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))
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment