Permalink
Browse files

Merge branch 'dfoster_feature/http_with_curl' into develop

New HTTP implementation for PHP < 5.3.7.

This implementation uses the cURL library. One of cURL's limitations is that streaming is not supported. The stream interface is emulated for existing code that expects it.

Conflicts:
	README.md
  • Loading branch information...
2 parents 5bf69d0 + 924f953 commit 3ca199c7174d32f7cbaf5437c13a23e77821dcfc @davidfstr davidfstr committed Sep 19, 2012
View
@@ -27,6 +27,10 @@ PHP 5.3.7+ is highly recommended.
OpenSSL support for PHP is required to access Splunk over `https://` URLs.
+If using PHP < 5.3.7, the cURL extension is required as well.
+Under this configuration the SDK will not support streaming large results
+when accessing Splunk over `https://` URLs.
+
Tested PHP versions:
* PHP 5.4.x
@@ -45,12 +49,8 @@ Tested PHP versions:
* PHP 5.2.7 - Recalled due to security flaw.
Earliest PHP supported by PHPUnit 3.6.
-&dagger; Suffers from [bug 54137] which interferes with HTTPS communication,
-especially to a Splunk server on localhost. If you see the error message
-`SSL: Connection reset by peer`, you are probably triggering this bug.
-A possible workaround is to run your PHP script on a different server than
-the Splunk indexer server, although this does not always resolve the issue.
-The SDK team is developing a better workaround for the next release.
+&dagger; Suffers from [bug 54137]. The SDK's workaround for this prevents
+streaming of large result sets when accessing Splunk over `https://` URLs.
[bug 45092]: https://bugs.php.net/bug.php?id=45092
[bug 48182]: https://bugs.php.net/bug.php?id=48182
View
@@ -75,13 +75,22 @@ private function requestWithParams(
public function request(
$method, $url, $requestHeaders=array(), $requestBody='')
{
- if ((substr($url, 0, strlen('http:')) !== 'http:') &&
- (substr($url, 0, strlen('https:')) !== 'https:'))
+ $isHttp = (substr($url, 0, strlen('http:')) === 'http:');
+ $isHttps = (substr($url, 0, strlen('https:')) === 'https:');
+
+ if (!$isHttp && !$isHttps)
{
throw new InvalidArgumentException(
'URL scheme must be either HTTP or HTTPS.');
}
+ // The HTTP stream wrapper in PHP < 5.3.7 has a bug which
+ // injects junk at the end of HTTP requests, which breaks
+ // SSL connections. Fallback to cURL-based requests.
+ if ($isHttps && (version_compare(PHP_VERSION, '5.3.7') < 0))
+ return $this->requestWithCurl(
+ $method, $url, $requestHeaders, $requestBody);
+
$requestHeaderLines = array();
foreach ($requestHeaders as $k => $v)
$requestHeaderLines[] = "{$k}: {$v}";
@@ -108,6 +117,7 @@ public function request(
throw new Splunk_ConnectException($errmsg, $errno);
}
+ $headers = array();
$headerLines = $http_response_header;
$statusLine = array_shift($headerLines);
foreach ($headerLines as $line)
@@ -135,4 +145,79 @@ public function request(
else
return $response;
}
+
+ private function requestWithCurl(
+ $method, $url, $requestHeaders=array(), $requestBody='')
+ {
+ $opts = array(
+ CURLOPT_URL => $url,
+ CURLOPT_TIMEOUT => 60, // secs
+ CURLOPT_RETURNTRANSFER => TRUE,
+ CURLOPT_HEADER => TRUE,
+ // disable SSL certificate validation
+ CURLOPT_SSL_VERIFYPEER => FALSE,
+ );
+
+ foreach ($requestHeaders as $k => $v)
+ $opts[CURLOPT_HTTPHEADER][] = "$k: $v";
+
+ switch ($method)
+ {
+ case 'get':
+ $opts[CURLOPT_HTTPGET] = TRUE;
+ break;
+ case 'post':
+ $opts[CURLOPT_POST] = TRUE;
+ $opts[CURLOPT_POSTFIELDS] = $requestBody;
+ break;
+ default:
+ $opts[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
+ break;
+ }
+
+ if (!($curl = curl_init()))
+ throw new Splunk_ConnectException('Unable to initialize cURL.');
+ if (!(curl_setopt_array($curl, $opts)))
+ throw new Splunk_ConnectException(curl_error($curl));
+ // NOTE: The entire HTTP response is read into memory here,
+ // which could be very large. Unfortunately the cURL
+ // interface does not provide a streaming alternative.
+ // To avoid this problem, use PHP 5.3.7+, which doesn't
+ // need cURL to perform HTTP requests.
+ if (!($response = curl_exec($curl)))
+ throw new Splunk_ConnectException(curl_error($curl));
+
+ $status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+ $headerText = substr($response, 0, $headerSize);
+ $body = (strlen($response) == $headerSize)
+ ? ''
+ : substr($response, $headerSize);
+
+ $headers = array();
+ $headerLines = explode("\r\n", trim($headerText));
+ $statusLine = array_shift($headerLines);
+ foreach ($headerLines as $line)
+ {
+ list($key, $value) = explode(':', $line, 2);
+ $headers[$key] = trim($value);
+ }
+
+ $statusLineComponents = explode(' ', $statusLine, 3);
+ $httpVersion = $statusLineComponents[0];
+ $reason = count($statusLineComponents) == 3 ? $statusLineComponents[2] : '';
+
+ $response = new Splunk_HttpResponse(array(
+ 'status' => $status,
+ 'reason' => $reason,
+ 'headers' => $headers,
+ 'body' => $body,
+ ));
+
+ if ($status >= 400)
+ throw new Splunk_HttpException($response);
+ else
+ return $response;
+ }
}
@@ -34,12 +34,14 @@
class Splunk_HttpResponse
{
private $state;
- private $body; // lazy
+ private $body; // lazy
+ private $bodyStream; // lazy
public function __construct($state)
{
$this->state = $state;
$this->body = NULL;
+ $this->bodyStream = NULL;
}
// === Accessors ===
@@ -48,14 +50,42 @@ public function __get($key)
{
if ($key === 'body')
return $this->getBody();
+ else if ($key === 'bodyStream')
+ return $this->getBodyStream();
else
return $this->state[$key];
}
private function getBody()
{
+ if (array_key_exists('body', $this->state))
+ return $this->state['body'];
+
if ($this->body === NULL)
- $this->body = Splunk_Util::stream_get_contents($this->bodyStream);
+ {
+ if (!array_key_exists('bodyStream', $this->state))
+ throw new Splunk_UnsupportedOperationException(
+ 'Response object does not contain body stream.');
+
+ $this->body = Splunk_Util::stream_get_contents(
+ $this->state['bodyStream']);
+ }
return $this->body;
}
+
+ private function getBodyStream()
+ {
+ if (array_key_exists('bodyStream', $this->state))
+ return $this->state['bodyStream'];
+
+ if ($this->bodyStream === NULL)
+ {
+ if (!array_key_exists('body', $this->state))
+ throw new Splunk_UnsupportedOperationException(
+ 'Response object does not contain body.');
+
+ $this->bodyStream = Splunk_StringStream::create($this->state['body']);
+ }
+ return $this->bodyStream;
+ }
}
@@ -22,13 +22,18 @@
*/
class Splunk_StringStream
{
+ /** (Prevent construction.) **/
+ private function __construct()
+ {
+ }
+
/**
* @return resource A stream that reads from the specified byte string.
*/
public static function create($string)
{
$stream = fopen('php://memory', 'rwb');
- fwrite($stream, $string);
+ Splunk_Util::fwriteall($stream, $string);
fseek($stream, 0);
/*
View
@@ -65,7 +65,12 @@ public static function fwriteall($stream, $data)
{
$numBytesWritten = fwrite($stream, $data);
if ($numBytesWritten === FALSE)
- throw new Splunk_IOException();
+ {
+ $errorInfo = error_get_last();
+ $errmsg = $errorInfo['message'];
+ $errno = $errorInfo['type'];
+ throw new Splunk_IOException($errmsg, $errno);
+ }
if ($numBytesWritten == strlen($data))
return;
$data = substr($data, $numBytesWritten);
View
@@ -3,6 +3,12 @@
require "./boxes.rb"
+def _compare_versions(v1, v2)
+ v1 = v1.split(".").map {|x| x.to_i }
+ v2 = v2.split(".").map {|x| x.to_i }
+ v1 <=> v2
+end
+
Vagrant::Config.run do |config|
PHP_VERSIONS.each do |php_version|
box_name = "php-" + php_version
@@ -24,6 +30,10 @@ Vagrant::Config.run do |config|
config.vm.provision :shell, :path => "provision/libxml-latest.sh"
# openssl (required by PHP for https:// URL support)
config.vm.provision :shell, :path => "provision/openssl-latest.sh"
+ # libcurl (required by SDK when PHP < 5.3.7)
+ if _compare_versions(php_version, "5.3.7") < 0
+ config.vm.provision :shell, :path => "provision/libcurl-latest.sh"
+ end
# PHP
config.vm.provision :shell, :path => "provision/php-" + php_version + ".sh"
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+sudo apt-get update
+sudo apt-get install -y libcurl4-openssl-dev
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.2.10.tar.gz
tar xzf php-5.2.10.tar.gz
cd php-5.2.10/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-recommended /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.2.11.tar.gz
tar xzf php-5.2.11.tar.gz
cd php-5.2.11/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-recommended /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.2.17.tar.gz
tar xzf php-5.2.17.tar.gz
cd php-5.2.17/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-recommended /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.2.9.tar.gz
tar xzf php-5.2.9.tar.gz
cd php-5.2.9/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-recommended /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.3.0.tar.gz
tar xzf php-5.3.0.tar.gz
cd php-5.3.0/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-development /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.3.3.tar.gz
tar xzf php-5.3.3.tar.gz
cd php-5.3.3/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-development /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.3.5.tar.gz
tar xzf php-5.3.5.tar.gz
cd php-5.3.5/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-development /usr/local/lib/php.ini
@@ -6,7 +6,7 @@ cd /tmp
wget http://museum.php.net/php5/php-5.3.6.tar.gz
tar xzf php-5.3.6.tar.gz
cd php-5.3.6/
-./configure --with-openssl
+./configure --with-openssl --with-curl=/usr
make
sudo make install
sudo cp php.ini-development /usr/local/lib/php.ini

0 comments on commit 3ca199c

Please sign in to comment.