Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
PHP SoapClient with timeout and authentication
<?php
//**NOTE**: This has *NOT* been tested yet; please provide feedback in the comments
//Drop-in replacement for PHP's SoapClient class supporting connect and response/transfer timeout and authentication
//Usage: Exactly as PHP's SoapClient class, except that some new options are 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
// sslversion The SSL version (2 or 3) to use. By default PHP will try to determine this itself, although in some cases this must be set manually
// sslverifyhost 1 to check the existence of a common name in the SSL peer certificate. 2 to check the existence of a common name and also verify that
// it matches the hostname provided. In production environments the value of this option should be kept at 2 (default value)
// credentials Set to null for no authentication or <username>:<password> (eg: 'admin:secret') for authentication
// useragent User defined user-agent
//PHP 5.2.3 minimum
//Based on https://gist.github.com/2490351 and http://tcsoftware.net/downloads/php/download.php?file=SoapClientAuth
class SoapClientEx extends SoapClient
{
private $timeout = 0;
private $connecttimeout = 0;
private $sslverifypeer = true;
private $sslverifyhost = 2;
private $sslversion = 3;
private $credentials = null;
private $useragent = null;
public function __construct($wsdl, $options) {
//"POP" our own defined options from the $options array before we call our parent constructor
//to ensure we don't pass unknown/invalid options to our parent
if (isset($options['timeout'])) {
$this->__setTimeout($options['timeout']);
unset($options['timeout']);
}
if (isset($options['connecttimeout'])) {
$this->__setConnectTimeout($options['connecttimeout']);
unset($options['connecttimeout']);
}
if (isset($options['sslverifypeer'])) {
$this->__setSSLVerifyPeer($options['sslverifypeer']);
unset($options['sslverifypeer']);
}
if (isset($options['sslverifyhost'])) {
$this->__setSSLVerifyHost($options['sslverifyhost']);
unset($options['sslverifyhost']);
}
if (isset($options['sslversion'])) {
$this->__setSSLVersion($options['sslversion']);
unset($options['sslversion']);
}
if (isset($options['credentials'])) {
$this->__setCredentials($options['credentials']);
unset($options['credentials']);
}
if (isset($options['useragent'])) {
$this->__setUseragent($options['useragent']);
unset($options['useragent']);
}
stream_wrapper_unregister('https');
stream_wrapper_unregister('http');
stream_wrapper_register('https', 'StreamWrapperHttpAuth');
stream_wrapper_register('http', 'StreamWrapperHttpAuth');
//Now call parent constructor
parent::__construct($wsdl, $options);
stream_wrapper_restore('https');
stream_wrapper_restore('http');
}
public function __setTimeout($timeoutms)
{
if (!is_int($timeoutms) && !is_null($timeoutms) || $timeoutms<0)
throw new Exception("Invalid timeout value");
$this->timeout = $timeoutms;
}
public function __getTimeout()
{
return $this->timeout;
}
public function __setConnectTimeout($connecttimeoutms)
{
if (!is_int($connecttimeoutms) && !is_null($connecttimeoutms) || $connecttimeoutms<0)
throw new Exception("Invalid connecttimeout value");
$this->connecttimeout = $connecttimeoutms;
}
public function __getConnectTimeout()
{
return $this->connecttimeout;
}
public function __setSSLVerifyPeer($sslverifypeer)
{
if (!is_bool($sslverifypeer))
throw new Exception("Invalid sslverifypeer value");
$this->sslverifypeer = $sslverifypeer;
StreamWrapperHttpAuth::$sslverifypeer = $sslverifypeer;
}
public function __getSSLVerifyPeer()
{
return $this->sslverifypeer;
}
public function __setSSLVerifyHost($sslverifyhost)
{
if (!is_int($sslverifyhost))
throw new Exception("Invalid sslverifyhost value");
$this->sslverifyhost = $sslverifyhost;
StreamWrapperHttpAuth::$sslverifyhost = $sslverifyhost;
}
public function __getSSLVerifyHost()
{
return $this->sslverifyhost;
}
public function __setSSLVersion($sslversion)
{
if (!is_int($sslversion))
throw new Exception("Invalid sslversion value");
$this->sslversion = $sslversion;
StreamWrapperHttpAuth::$sslversion = $sslversion;
}
public function __getSSLVersion()
{
return $this->sslversion;
}
public function __setCredentials($credentials)
{
if ($credentials !== null) {
if (!is_string($credentials))
throw new Exception("Invalid credentials value");
if (strpos($credentials, ':')===false)
throw new Exception("Credentials must contain a ':' as username/password separator");
}
$this->credentials = $credentials;
StreamWrapperHttpAuth::$credentials = $credentials;
}
public function __getCredentials()
{
return $this->credentials;
}
public function __setUseragent($useragent)
{
if ($useragent !== null) {
if (!is_string($useragent))
throw new Exception("Invalid useragent value");
}
$this->useragent = $useragent;
}
public function __getUseragent()
{
return $this->useragent;
}
public function __doRequest($request, $location, $action, $version, $one_way = false)
{
if (($this->timeout===0) && ($this->connecttimeout===0))
{
// 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);
if ($curl === false)
throw new Exception('Curl initialisation failed');
$headers = array(
sprintf('Content-Type: %s', $version == 2 ? 'application/soap+xml' : 'text/xml'),
sprintf('SOAPAction: %s', $action),
//'Content-Length: ' . strlen($request),
//'Expect: 100-continue',
//'Connection: Keep-Alive'
);
if ($this->__getUseragent() !== null)
array_push($headers, sprintf('User-Agent: %s', preg_replace('/[^\x20-\x7F]/', '', $this->__getUseragent())));
$options = array(
CURLOPT_VERBOSE => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $request,
CURLOPT_HEADER => false,
CURLOPT_NOSIGNAL => true, //http://www.php.net/manual/en/function.curl-setopt.php#104597
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => $this->sslverifypeer,
CURLOPT_SSL_VERIFYHOST => $this->sslverifyhost,
CURLOPT_SSLVERSION => $this->sslversion,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_FAILONERROR => false,
CURLOPT_HTTPAUTH => CURLAUTH_ANY,
);
if ($this->__getCredentials() !== null)
$options[CURLOPT_USERPWD] = $this->__getCredentials();
if ($this->timeout>0) {
if (defined('CURLOPT_TIMEOUT_MS')) { //Timeout in MS supported?
$options[CURLOPT_TIMEOUT_MS] = $this->timeout;
} else { //Round(up) to second precision
$options[CURLOPT_TIMEOUT] = ceil($this->timeout/1000);
}
}
if ($this->connecttimeout>0) {
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { //ConnectTimeout in MS supported?
$options[CURLOPT_CONNECTTIMEOUT_MS] = $this->connecttimeout;
} else { //Round(up) to second precision
$options[CURLOPT_CONNECTTIMEOUT] = ceil($this->connecttimeout/1000);
}
}
if (curl_setopt_array($curl, $options) === false)
throw new Exception('Failed setting CURL options');
$response = curl_exec($curl);
$info = curl_getinfo($curl);
if ($info['http_code'] == 401)
throw new Exception('Access Denied', 401);
else if (curl_errno($curl) != 0) {
throw new Exception(curl_error($curl), curl_errno($curl));
} elseif ($info['http_code'] != 200) {
var_dump($response);
throw new Exception(sprintf('Error: %d', $info['http_code']), $info['http_code']);
}
curl_close($curl);
}
// Return?
if (!$one_way)
return $response;
}
}
class StreamWrapperHttpAuth {
public static $credentials = null;
public static $sslversion = 3;
public static $sslverifypeer = true;
public static $sslverifyhost = 2;
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);
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSLVERSION => StreamWrapperHttpAuth::$sslversion,
CURLOPT_SSL_VERIFYPEER => StreamWrapperHttpAuth::$sslverifypeer,
CURLOPT_SSL_VERIFYHOST => StreamWrapperHttpAuth::$sslverifyhost
);
if (StreamWrapperHttpAuth::$credentials !== null) {
$options[CURLOPT_HTTPAUTH] = $authType;
$options[CURLOPT_USERPWD] = StreamWrapperHttpAuth::$credentials;
}
if (curl_setopt_array($this->curlHandle, $options) === false)
throw new Exception('Failed setting CURL options');
$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 Exception(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 Exception ('Access Denied', 401);
}
} else if (curl_errno($this->curlHandle) != 0) {
throw new Exception(curl_error($this->curlHandle), curl_errno($this->curlHandle));
} else {
throw new Exception(sprintf('Error: %d', $info['http_code']), $info['http_code']);
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment