summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authormildis <mildis@users.noreply.github.com>2019-06-04 04:51:47 +0200
committerFrédéric Guillot <fred@kanboard.net>2019-06-03 20:00:49 -0700
commitb26776e529a61666dcb941ad00ac85c1d90b03da (patch)
treee3c86ffb3d897a6dd249cb944e71627200298bf1 /app
parentf76c6c7a2a62deb5c4985067f796b447ae144178 (diff)
Add cURL support to HTTP Client
- Add HTTP_PROXY_EXCLUDE option when cURL is used - Show HTTP client backend in about page - Fallback to legacy Stream Contexts if cURL extension is not available
Diffstat (limited to 'app')
-rw-r--r--app/Core/Http/Client.php130
-rw-r--r--app/Template/config/about.php4
-rw-r--r--app/constants.php1
3 files changed, 132 insertions, 3 deletions
diff --git a/app/Core/Http/Client.php b/app/Core/Http/Client.php
index 84099a23..230a5958 100644
--- a/app/Core/Http/Client.php
+++ b/app/Core/Http/Client.php
@@ -132,7 +132,7 @@ class Client extends Base
}
/**
- * Make the HTTP request
+ * Make the HTTP request with cURL if detected, socket otherwise
*
* @access public
* @param string $method
@@ -144,10 +144,38 @@ class Client extends Base
*/
public function doRequest($method, $url, $content, array $headers, $raiseForErrors = false)
{
- if (empty($url)) {
- return '';
+ $requestBody = '';
+
+ if (! empty($url)) {
+ if (function_exists('curl_version')) {
+ if (DEBUG) {
+ $this->logger->debug('HttpClient::doRequest: cURL detected');
+ }
+ $requestBody = $this->doRequestWithCurl($method, $url, $content, $headers, $raiseForErrors);
+ } else {
+ if (DEBUG) {
+ $this->logger->debug('HttpClient::doRequest: using socket');
+ }
+ $requestBody = $this->doRequestWithSocket($method, $url, $content, $headers, $raiseForErrors);
+ }
}
+ return $requestBody;
+ }
+
+ /**
+ * Make the HTTP request with socket
+ *
+ * @access private
+ * @param string $method
+ * @param string $url
+ * @param string $content
+ * @param string[] $headers
+ * @param bool $raiseForErrors
+ * @return string
+ */
+ private function doRequestWithSocket($method, $url, $content, array $headers, $raiseForErrors = false)
+ {
$startTime = microtime(true);
$stream = @fopen(trim($url), 'r', false, stream_context_create($this->getContext($method, $content, $headers, $raiseForErrors)));
@@ -184,6 +212,91 @@ class Client extends Base
return $body;
}
+
+ /**
+ * Make the HTTP request with cURL
+ *
+ * @access private
+ * @param string $method
+ * @param string $url
+ * @param string $content
+ * @param string[] $headers
+ * @param bool $raiseForErrors
+ * @return string
+ */
+ private function doRequestWithCurl($method, $url, $content, array $headers, $raiseForErrors = false)
+ {
+ $startTime = microtime(true);
+ $curlSession = @curl_init();
+
+ curl_setopt($curlSession, CURLOPT_URL, trim($url));
+ curl_setopt($curlSession, CURLOPT_USERAGENT, self::HTTP_USER_AGENT);
+ curl_setopt($curlSession, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+ curl_setopt($curlSession, CURLOPT_TIMEOUT, HTTP_TIMEOUT);
+ curl_setopt($curlSession, CURLOPT_FORBID_REUSE, true);
+ curl_setopt($curlSession, CURLOPT_MAXREDIRS, HTTP_MAX_REDIRECTS);
+ curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curlSession, CURLOPT_FOLLOWLOCATION, true);
+
+ if ('POST' === $method) {
+ curl_setopt($curlSession, CURLOPT_POST, true);
+ curl_setopt($curlSession, CURLOPT_POSTFIELDS, $content);
+ }
+
+ if (! empty($headers)) {
+ curl_setopt($curlSession, CURLOPT_HTTPHEADER, $headers);
+ }
+
+ if (HTTP_VERIFY_SSL_CERTIFICATE === false) {
+ curl_setopt($curlSession, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_setopt($curlSession, CURLOPT_SSL_VERIFYPEER, false);
+ }
+
+ if (HTTP_PROXY_HOSTNAME) {
+ curl_setopt($curlSession, CURLOPT_PROXY, HTTP_PROXY_HOSTNAME);
+ curl_setopt($curlSession, CURLOPT_PROXYPORT, HTTP_PROXY_PORT);
+ curl_setopt($curlSession, CURLOPT_NOPROXY, HTTP_PROXY_EXCLUDE);
+ }
+
+ if (HTTP_PROXY_USERNAME) {
+ curl_setopt($curlSession, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+ curl_setopt($curlSession, CURLOPT_PROXYUSERPWD, HTTP_PROXY_USERNAME.':'.HTTP_PROXY_PASSWORD);
+ }
+
+ $body = curl_exec($curlSession);
+
+ if (! $body) {
+ $this->logger->error('HttpClient: request failed ('.$url.')');
+
+ if ($raiseForErrors) {
+ throw new ClientException('Unreachable URL: '.$url);
+ }
+
+ curl_close($curlSession);
+ return '';
+ }
+
+ if ($raiseForErrors) {
+ $statusCode = curl_getinfo($curlSession, CURLINFO_RESPONSE_CODE);
+
+ if ($statusCode >= 400) {
+ throw new InvalidStatusException('Request failed with status code '.$statusCode, $statusCode, $body);
+ }
+ }
+
+ if (DEBUG) {
+ $this->logger->debug('HttpClient: url='.$url);
+ $this->logger->debug('HttpClient: headers='.var_export($headers, true));
+ $this->logger->debug('HttpClient: payload='.$content);
+ $this->logger->debug('HttpClient: metadata='.var_export(curl_getinfo($curlSession), true));
+ $this->logger->debug('HttpClient: body='.$body);
+ $this->logger->debug('HttpClient: executionTime='.(microtime(true) - $startTime));
+ }
+
+ curl_close($curlSession);
+ return $body;
+ }
+
/**
* Get stream context
*
@@ -247,4 +360,15 @@ class Client extends Base
return $status;
}
+
+ /**
+ * Get backend used for making HTTP connections
+ *
+ * @access public
+ * @return string
+ */
+ public static function backend()
+ {
+ return function_exists('curl_version') ? 'cURL' : 'socket';
+ }
}
diff --git a/app/Template/config/about.php b/app/Template/config/about.php
index 29063841..628fb096 100644
--- a/app/Template/config/about.php
+++ b/app/Template/config/about.php
@@ -36,6 +36,10 @@
<strong><?= PHP_SAPI ?></strong>
</li>
<li>
+ <?= t('HTTP Client:') ?>
+ <strong><?= Kanboard\Core\Http\Client::backend() ?></strong>
+ </li>
+ <li>
<?= t('OS version:') ?>
<strong><?= @php_uname('s').' '.@php_uname('r') ?></strong>
</li>
diff --git a/app/constants.php b/app/constants.php
index e7d8c41c..326ff401 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -148,6 +148,7 @@ defined('HTTP_PROXY_HOSTNAME') or define('HTTP_PROXY_HOSTNAME', '');
defined('HTTP_PROXY_PORT') or define('HTTP_PROXY_PORT', '3128');
defined('HTTP_PROXY_USERNAME') or define('HTTP_PROXY_USERNAME', '');
defined('HTTP_PROXY_PASSWORD') or define('HTTP_PROXY_PASSWORD', '');
+defined('HTTP_PROXY_EXCLUDE') or define('HTTP_PROXY_EXCLUDE', 'localhost');
defined('HTTP_VERIFY_SSL_CERTIFICATE') or define('HTTP_VERIFY_SSL_CERTIFICATE', true);
defined('TOTP_ISSUER') or define('TOTP_ISSUER', 'Kanboard');