added cURL tests

This commit is contained in:
Andreas Schamberger 2011-10-16 19:41:34 +02:00
parent c54b2925fe
commit cfe1a966ab
3 changed files with 485 additions and 304 deletions

View File

@ -1,305 +1,305 @@
<?php <?php
/* /*
* This file is part of the BeSimpleSoapClient. * This file is part of the BeSimpleSoapClient.
* *
* (c) Christian Kerl <christian-kerl@web.de> * (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com> * (c) Francis Besset <francis.besset@gmail.com>
* *
* This source file is subject to the MIT license that is bundled * This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE. * with this source code in the file LICENSE.
*/ */
namespace BeSimple\SoapClient; namespace BeSimple\SoapClient;
/** /**
* cURL wrapper class for doing HTTP requests that uses the soap class options. * cURL wrapper class for doing HTTP requests that uses the soap class options.
* *
* @author Andreas Schamberger * @author Andreas Schamberger
*/ */
class Curl class Curl
{ {
/** /**
* HTTP User Agent. * HTTP User Agent.
* *
* @var string * @var string
*/ */
const USER_AGENT = 'PHP-SOAP/\BeSimple\SoapClient'; const USER_AGENT = 'PHP-SOAP/\BeSimple\SoapClient';
/** /**
* Curl resource. * Curl resource.
* *
* @var resource * @var resource
*/ */
private $ch; private $ch;
/** /**
* Maximum number of location headers to follow. * Maximum number of location headers to follow.
* *
* @var int * @var int
*/ */
private $followLocationMaxRedirects; private $followLocationMaxRedirects;
/** /**
* Request response data. * Request response data.
* *
* @var string * @var string
*/ */
private $response; private $response;
/** /**
* Constructor. * Constructor.
* *
* @param array $options * @param array $options
* @param int $followLocationMaxRedirects * @param int $followLocationMaxRedirects
*/ */
public function __construct(array $options, $followLocationMaxRedirects = 10) public function __construct(array $options = array(), $followLocationMaxRedirects = 10)
{ {
// set the default HTTP user agent // set the default HTTP user agent
if (!isset($options['user_agent'])) { if (!isset($options['user_agent'])) {
$options['user_agent'] = self::USER_AGENT; $options['user_agent'] = self::USER_AGENT;
} }
$this->followLocationMaxRedirects = $followLocationMaxRedirects; $this->followLocationMaxRedirects = $followLocationMaxRedirects;
// make http request // make http request
$this->ch = curl_init(); $this->ch = curl_init();
$curlOptions = array( $curlOptions = array(
CURLOPT_ENCODING => '', CURLOPT_ENCODING => '',
CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FAILONERROR => false, CURLOPT_FAILONERROR => false,
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_HEADER => true, CURLOPT_HEADER => true,
CURLOPT_USERAGENT => $options['user_agent'], CURLOPT_USERAGENT => $options['user_agent'],
CURLINFO_HEADER_OUT => true, CURLINFO_HEADER_OUT => true,
); );
curl_setopt_array($this->ch, $curlOptions); curl_setopt_array($this->ch, $curlOptions);
if (isset($options['compression']) && !($options['compression'] & SOAP_COMPRESSION_ACCEPT)) { if (isset($options['compression']) && !($options['compression'] & SOAP_COMPRESSION_ACCEPT)) {
curl_setopt($this->ch, CURLOPT_ENCODING, 'identity'); curl_setopt($this->ch, CURLOPT_ENCODING, 'identity');
} }
if (isset($options['connection_timeout'])) { if (isset($options['connection_timeout'])) {
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $options['connection_timeout']); curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $options['connection_timeout']);
} }
if (isset($options['proxy_host'])) { if (isset($options['proxy_host'])) {
$port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080; $port = isset($options['proxy_port']) ? $options['proxy_port'] : 8080;
curl_setopt($this->ch, CURLOPT_PROXY, $options['proxy_host'] . ':' . $port); curl_setopt($this->ch, CURLOPT_PROXY, $options['proxy_host'] . ':' . $port);
} }
if (isset($options['proxy_user'])) { if (isset($options['proxy_user'])) {
curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_password']); curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $options['proxy_user'] . ':' . $options['proxy_password']);
} }
if (isset($options['login'])) { if (isset($options['login'])) {
curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($this->ch, CURLOPT_USERPWD, $options['login'].':'.$options['password']); curl_setopt($this->ch, CURLOPT_USERPWD, $options['login'].':'.$options['password']);
} }
if (isset($options['local_cert'])) { if (isset($options['local_cert'])) {
curl_setopt($this->ch, CURLOPT_SSLCERT, $options['local_cert']); curl_setopt($this->ch, CURLOPT_SSLCERT, $options['local_cert']);
curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $options['passphrase']); curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $options['passphrase']);
} }
} }
/** /**
* Destructor. * Destructor.
*/ */
public function __destruct() public function __destruct()
{ {
curl_close($this->ch); curl_close($this->ch);
} }
/** /**
* Execute HTTP request. * Execute HTTP request.
* Returns true if request was successfull. * Returns true if request was successfull.
* *
* @param string $location * @param string $location
* @param string $request * @param string $request
* @param array $requestHeaders * @param array $requestHeaders
* @return bool * @return bool
*/ */
public function exec($location, $request = null, $requestHeaders = array()) public function exec($location, $request = null, $requestHeaders = array())
{ {
curl_setopt($this->ch, CURLOPT_URL, $location); curl_setopt($this->ch, CURLOPT_URL, $location);
if (!is_null($request)) { if (!is_null($request)) {
curl_setopt($this->ch, CURLOPT_POST, true); curl_setopt($this->ch, CURLOPT_POST, true);
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request); curl_setopt($this->ch, CURLOPT_POSTFIELDS, $request);
} }
if (count($requestHeaders) > 0) { if (count($requestHeaders) > 0) {
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $requestHeaders); curl_setopt($this->ch, CURLOPT_HTTPHEADER, $requestHeaders);
} }
$this->response = $this->execManualRedirect($this->followLocationMaxRedirects); $this->response = $this->execManualRedirect($this->followLocationMaxRedirects);
return ($this->response === false) ? false : true; return ($this->response === false) ? false : true;
} }
/** /**
* Custom curl_exec wrapper that allows to follow redirects when specific * Custom curl_exec wrapper that allows to follow redirects when specific
* http response code is set. SOAP only allows 307. * http response code is set. SOAP only allows 307.
* *
* @param resource $ch * @param resource $ch
* @param int $maxRedirects * @param int $maxRedirects
* @param int $redirects * @param int $redirects
* @return mixed * @return mixed
*/ */
private function execManualRedirect($redirects = 0) private function execManualRedirect($redirects = 0)
{ {
if ($redirects > $this->followLocationMaxRedirects) { if ($redirects > $this->followLocationMaxRedirects) {
// TODO Redirection limit reached, aborting // TODO Redirection limit reached, aborting
return false; return false;
} }
curl_setopt($this->ch, CURLOPT_HEADER, true); curl_setopt($this->ch, CURLOPT_HEADER, true);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($this->ch); $response = curl_exec($this->ch);
$httpResponseCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); $httpResponseCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if ($httpResponseCode == 307) { if ($httpResponseCode == 307) {
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $headerSize); $header = substr($response, 0, $headerSize);
$matches = array(); $matches = array();
preg_match('/Location:(.*?)\n/', $header, $matches); preg_match('/Location:(.*?)\n/', $header, $matches);
$url = trim(array_pop($matches)); $url = trim(array_pop($matches));
// @parse_url to suppress E_WARNING for invalid urls // @parse_url to suppress E_WARNING for invalid urls
if (($url = @parse_url($url)) !== false) { if (($url = @parse_url($url)) !== false) {
$lastUrl = parse_url(curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL)); $lastUrl = parse_url(curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL));
if (!isset($url['scheme'])) { if (!isset($url['scheme'])) {
$url['scheme'] = $lastUrl['scheme']; $url['scheme'] = $lastUrl['scheme'];
} }
if (!isset($url['host'])) { if (!isset($url['host'])) {
$url['host'] = $lastUrl['host']; $url['host'] = $lastUrl['host'];
} }
if (!isset($url['path'])) { if (!isset($url['path'])) {
$url['path'] = $lastUrl['path']; $url['path'] = $lastUrl['path'];
} }
$newUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query'] ? '?' . $url['query'] : ''); $newUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query'] ? '?' . $url['query'] : '');
curl_setopt($this->ch, CURLOPT_URL, $newUrl); curl_setopt($this->ch, CURLOPT_URL, $newUrl);
return $this->execManualRedirect($redirects++); return $this->execManualRedirect($redirects++);
} }
} }
return $response; return $response;
} }
/** /**
* Error code mapping from cURL error codes to PHP ext/soap error messages * Error code mapping from cURL error codes to PHP ext/soap error messages
* (where applicable) * (where applicable)
* *
* http://curl.haxx.se/libcurl/c/libcurl-errors.html * http://curl.haxx.se/libcurl/c/libcurl-errors.html
* *
* @var array(int=>string) * @var array(int=>string)
*/ */
protected function getErrorCodeMapping() protected function getErrorCodeMapping()
{ {
return array( return array(
1 => 'Unknown protocol. Only http and https are allowed.', //CURLE_UNSUPPORTED_PROTOCOL 1 => 'Unknown protocol. Only http and https are allowed.', //CURLE_UNSUPPORTED_PROTOCOL
3 => 'Unable to parse URL', //CURLE_URL_MALFORMAT 3 => 'Unable to parse URL', //CURLE_URL_MALFORMAT
5 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_PROXY 5 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_PROXY
6 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_HOST 6 => 'Could not connect to host', //CURLE_COULDNT_RESOLVE_HOST
7 => 'Could not connect to host', //CURLE_COULDNT_CONNECT 7 => 'Could not connect to host', //CURLE_COULDNT_CONNECT
9 => 'Could not connect to host', //CURLE_REMOTE_ACCESS_DENIED 9 => 'Could not connect to host', //CURLE_REMOTE_ACCESS_DENIED
28 => 'Failed Sending HTTP SOAP request', //CURLE_OPERATION_TIMEDOUT 28 => 'Failed Sending HTTP SOAP request', //CURLE_OPERATION_TIMEDOUT
35 => 'Could not connect to host', //CURLE_SSL_CONNECT_ERROR 35 => 'Could not connect to host', //CURLE_SSL_CONNECT_ERROR
41 => 'Can\'t uncompress compressed response', //CURLE_FUNCTION_NOT_FOUND 41 => 'Can\'t uncompress compressed response', //CURLE_FUNCTION_NOT_FOUND
51 => 'Could not connect to host', //CURLE_PEER_FAILED_VERIFICATION 51 => 'Could not connect to host', //CURLE_PEER_FAILED_VERIFICATION
52 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_GOT_NOTHING 52 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_GOT_NOTHING
53 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_NOTFOUND 53 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_NOTFOUND
54 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_SETFAILED 54 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_SETFAILED
55 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_ERROR 55 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_ERROR
56 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_RECV_ERROR 56 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_RECV_ERROR
58 => 'Could not connect to host', //CURLE_SSL_CERTPROBLEM 58 => 'Could not connect to host', //CURLE_SSL_CERTPROBLEM
59 => 'Could not connect to host', //CURLE_SSL_CIPHER 59 => 'Could not connect to host', //CURLE_SSL_CIPHER
60 => 'Could not connect to host', //CURLE_SSL_CACERT 60 => 'Could not connect to host', //CURLE_SSL_CACERT
61 => 'Unknown Content-Encoding', //CURLE_BAD_CONTENT_ENCODING 61 => 'Unknown Content-Encoding', //CURLE_BAD_CONTENT_ENCODING
65 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_FAIL_REWIND 65 => 'Failed Sending HTTP SOAP request', //CURLE_SEND_FAIL_REWIND
66 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_INITFAILED 66 => 'SSL support is not available in this build', //CURLE_SSL_ENGINE_INITFAILED
67 => 'Could not connect to host', //CURLE_LOGIN_DENIED 67 => 'Could not connect to host', //CURLE_LOGIN_DENIED
77 => 'Could not connect to host', //CURLE_SSL_CACERT_BADFILE 77 => 'Could not connect to host', //CURLE_SSL_CACERT_BADFILE
80 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_SSL_SHUTDOWN_FAILED 80 => 'Error Fetching http body, No Content-Length, connection closed or chunked data', //CURLE_SSL_SHUTDOWN_FAILED
); );
} }
/** /**
* Gets the curl error message. * Gets the curl error message.
* *
* @return string * @return string
*/ */
public function getErrorMessage() public function getErrorMessage()
{ {
$errorCodeMapping = $this->getErrorCodeMapping(); $errorCodeMapping = $this->getErrorCodeMapping();
$errorNumber = curl_errno($this->ch); $errorNumber = curl_errno($this->ch);
if (isset($errorCodeMapping[$errorNumber])) { if (isset($errorCodeMapping[$errorNumber])) {
return $errorCodeMapping[$errorNumber]; return $errorCodeMapping[$errorNumber];
} }
return curl_error($this->ch); return curl_error($this->ch);
} }
/** /**
* Gets the request headers as a string. * Gets the request headers as a string.
* *
* @return string * @return string
*/ */
public function getRequestHeaders() public function getRequestHeaders()
{ {
return curl_getinfo($this->ch, CURLINFO_HEADER_OUT); return curl_getinfo($this->ch, CURLINFO_HEADER_OUT);
} }
/** /**
* Gets the whole response (including headers) as a string. * Gets the whole response (including headers) as a string.
* *
* @return string * @return string
*/ */
public function getResponse() public function getResponse()
{ {
return $this->response; return $this->response;
} }
/** /**
* Gets the response body as a string. * Gets the response body as a string.
* *
* @return string * @return string
*/ */
public function getResponseBody() public function getResponseBody()
{ {
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
return substr($this->response, $headerSize); return substr($this->response, $headerSize);
} }
/** /**
* Gets the response content type. * Gets the response content type.
* *
* @return string * @return string
*/ */
public function getResponseContentType() public function getResponseContentType()
{ {
return curl_getinfo($this->ch, CURLINFO_CONTENT_TYPE); return curl_getinfo($this->ch, CURLINFO_CONTENT_TYPE);
} }
/** /**
* Gets the response headers as a string. * Gets the response headers as a string.
* *
* @return string * @return string
*/ */
public function getResponseHeaders() public function getResponseHeaders()
{ {
$headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); $headerSize = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
return substr($this->response, 0, $headerSize); return substr($this->response, 0, $headerSize);
} }
/** /**
* Gets the response http status code. * Gets the response http status code.
* *
* @return string * @return string
*/ */
public function getResponseStatusCode() public function getResponseStatusCode()
{ {
return curl_getinfo($this->ch, CURLINFO_HTTP_CODE); return curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
} }
/** /**
* Gets the response http status message. * Gets the response http status message.
* *
* @return string * @return string
*/ */
public function getResponseStatusMessage() public function getResponseStatusMessage()
{ {
preg_match('/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $this->response, $matches); preg_match('/HTTP\/(1\.[0-1]+) ([0-9]{3}) (.*)/', $this->response, $matches);
return trim(array_pop($matches)); return trim(array_pop($matches));
} }
} }

View File

@ -0,0 +1,180 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapClient;
use BeSimple\SoapClient\Curl;
/**
* @author Andreas Schamberger
*/
class CurlTest extends \PHPUnit_Framework_TestCase
{
protected $webserverProcessId;
protected function startPhpWebserver()
{
if ('Windows' == substr(php_uname('s'), 0, 7 )) {
$powershellCommand = "\$app = start-process php.exe -ArgumentList '-S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures' -WindowStyle 'Hidden' -passthru; Echo \$app.Id;";
$shellCommand = 'powershell -command "& {'.$powershellCommand.'}"';
} else {
$shellCommand = "nohup php -S localhost:8000 -t ".__DIR__.DIRECTORY_SEPARATOR."Fixtures &";
}
$output = array();
exec($shellCommand, $output);
$this->webserverProcessId = $output[0]; // pid is in first element
}
protected function stopPhpWebserver()
{
if (!is_null($this->webserverProcessId)) {
if ('Windows' == substr(php_uname('s'), 0, 7 )) {
exec('TASKKILL /F /PID ' . $this->webserverProcessId);
} else {
exec('kill ' . $this->webserverProcessId);
}
$this->webserverProcessId = null;
}
}
public function testExec()
{
$this->startPhpWebserver();
$curl = new Curl();
$this->assertTrue($curl->exec('http://localhost:8000/curl.txt'));
$this->assertTrue($curl->exec('http://localhost:8000/404.txt'));
$this->stopPhpWebserver();
}
public function testGetErrorMessage()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://unknown/curl.txt');
$this->assertEquals('Could not connect to host', $curl->getErrorMessage());
$curl->exec('xyz://localhost:8000/@404.txt');
$this->assertEquals('Unknown protocol. Only http and https are allowed.', $curl->getErrorMessage());
$curl->exec('');
$this->assertEquals('Unable to parse URL', $curl->getErrorMessage());
$this->stopPhpWebserver();
}
public function testGetRequestHeaders()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals(136, strlen($curl->getRequestHeaders()));
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals(135, strlen($curl->getRequestHeaders()));
$this->stopPhpWebserver();
}
public function testGetResponse()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals(150, strlen($curl->getResponse()));
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals(1282, strlen($curl->getResponse()));
$this->stopPhpWebserver();
}
public function testGetResponseBody()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals('This is a testfile for cURL.', $curl->getResponseBody());
$this->stopPhpWebserver();
}
public function testGetResponseContentType()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals('text/plain; charset=UTF-8', $curl->getResponseContentType());
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals('text/html; charset=UTF-8', $curl->getResponseContentType());
$this->stopPhpWebserver();
}
public function testGetResponseHeaders()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals(122, strlen($curl->getResponseHeaders()));
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals(130, strlen($curl->getResponseHeaders()));
$this->stopPhpWebserver();
}
public function testGetResponseStatusCode()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals(200, $curl->getResponseStatusCode());
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals(404, $curl->getResponseStatusCode());
$this->stopPhpWebserver();
}
public function testGetResponseStatusMessage()
{
$this->startPhpWebserver();
$curl = new Curl();
$curl->exec('http://localhost:8000/curl.txt');
$this->assertEquals('OK', $curl->getResponseStatusMessage());
$curl->exec('http://localhost:8000/404.txt');
$this->assertEquals('Not Found', $curl->getResponseStatusMessage());
$this->stopPhpWebserver();
}
}

View File

@ -0,0 +1 @@
This is a testfile for cURL.