summaryrefslogtreecommitdiff
path: root/app/Core/Http
diff options
context:
space:
mode:
authorFrédéric Guillot <fred@kanboard.net>2018-03-02 12:05:41 -0800
committerFrédéric Guillot <fred@kanboard.net>2018-03-02 12:05:41 -0800
commitebe04e672c7982a1560133ee87f417560a851a10 (patch)
tree459c4fd52f85e9f9032c9e3ec7e178e032a8c8c5 /app/Core/Http
parent94ed32dedfeb32d8b89f457bae749fbec16fd9b0 (diff)
Improve HTTP client to raise exceptions
Diffstat (limited to 'app/Core/Http')
-rw-r--r--app/Core/Http/Client.php103
-rw-r--r--app/Core/Http/ClientException.php9
-rw-r--r--app/Core/Http/InvalidStatusException.php19
3 files changed, 100 insertions, 31 deletions
diff --git a/app/Core/Http/Client.php b/app/Core/Http/Client.php
index 3c4397e2..de7c6ec6 100644
--- a/app/Core/Http/Client.php
+++ b/app/Core/Http/Client.php
@@ -8,7 +8,7 @@ use Kanboard\Job\HttpAsyncJob;
/**
* HTTP client
*
- * @package http
+ * @package Kanboard\Core\Http
* @author Frederic Guillot
*/
class Client extends Base
@@ -18,7 +18,7 @@ class Client extends Base
*
* @var integer
*/
- const HTTP_TIMEOUT = 5;
+ const HTTP_TIMEOUT = 10;
/**
* Number of maximum redirections for the HTTP client
@@ -40,11 +40,12 @@ class Client extends Base
* @access public
* @param string $url
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return string
*/
- public function get($url, array $headers = array())
+ public function get($url, array $headers = [], $raiseForErrors = false)
{
- return $this->doRequest('GET', $url, '', $headers);
+ return $this->doRequest('GET', $url, '', $headers, $raiseForErrors);
}
/**
@@ -53,12 +54,13 @@ class Client extends Base
* @access public
* @param string $url
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return array
*/
- public function getJson($url, array $headers = array())
+ public function getJson($url, array $headers = [], $raiseForErrors = false)
{
- $response = $this->doRequest('GET', $url, '', array_merge(array('Accept: application/json'), $headers));
- return json_decode($response, true) ?: array();
+ $response = $this->doRequest('GET', $url, '', array_merge(['Accept: application/json'], $headers), $raiseForErrors);
+ return json_decode($response, true) ?: [];
}
/**
@@ -68,15 +70,17 @@ class Client extends Base
* @param string $url
* @param array $data
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return string
*/
- public function postJson($url, array $data, array $headers = array())
+ public function postJson($url, array $data, array $headers = [], $raiseForErrors = false)
{
return $this->doRequest(
'POST',
$url,
json_encode($data),
- array_merge(array('Content-type: application/json'), $headers)
+ array_merge(['Content-type: application/json'], $headers),
+ $raiseForErrors
);
}
@@ -87,14 +91,16 @@ class Client extends Base
* @param string $url
* @param array $data
* @param string[] $headers
+ * @param bool $raiseForErrors
*/
- public function postJsonAsync($url, array $data, array $headers = array())
+ public function postJsonAsync($url, array $data, array $headers = [], $raiseForErrors = false)
{
$this->queueManager->push(HttpAsyncJob::getInstance($this->container)->withParams(
'POST',
$url,
json_encode($data),
- array_merge(array('Content-type: application/json'), $headers)
+ array_merge(['Content-type: application/json'], $headers),
+ $raiseForErrors
));
}
@@ -105,15 +111,17 @@ class Client extends Base
* @param string $url
* @param array $data
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return string
*/
- public function postForm($url, array $data, array $headers = array())
+ public function postForm($url, array $data, array $headers = [], $raiseForErrors = false)
{
return $this->doRequest(
'POST',
$url,
http_build_query($data),
- array_merge(array('Content-type: application/x-www-form-urlencoded'), $headers)
+ array_merge(['Content-type: application/x-www-form-urlencoded'], $headers),
+ $raiseForErrors
);
}
@@ -124,14 +132,16 @@ class Client extends Base
* @param string $url
* @param array $data
* @param string[] $headers
+ * @param bool $raiseForErrors
*/
- public function postFormAsync($url, array $data, array $headers = array())
+ public function postFormAsync($url, array $data, array $headers = [], $raiseForErrors = false)
{
$this->queueManager->push(HttpAsyncJob::getInstance($this->container)->withParams(
'POST',
$url,
http_build_query($data),
- array_merge(array('Content-type: application/x-www-form-urlencoded'), $headers)
+ array_merge(['Content-type: application/x-www-form-urlencoded'], $headers),
+ $raiseForErrors
));
}
@@ -143,29 +153,45 @@ class Client extends Base
* @param string $url
* @param string $content
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return string
*/
- public function doRequest($method, $url, $content, array $headers)
+ public function doRequest($method, $url, $content, array $headers, $raiseForErrors = false)
{
if (empty($url)) {
return '';
}
$startTime = microtime(true);
- $stream = @fopen(trim($url), 'r', false, stream_context_create($this->getContext($method, $content, $headers)));
+ $stream = @fopen(trim($url), 'r', false, stream_context_create($this->getContext($method, $content, $headers, $raiseForErrors)));
$response = '';
- if (is_resource($stream)) {
- $response = stream_get_contents($stream);
- } else {
- $this->logger->error('HttpClient: request failed');
+ if (! is_resource($stream)) {
+ $this->logger->error('HttpClient: request failed ('.$url.')');
+
+ if ($raiseForErrors) {
+ throw new ClientException('Unreachable URL: '.$url);
+ }
+
+ return '';
+ }
+
+ $response = stream_get_contents($stream);
+ $metadata = stream_get_meta_data($stream);
+
+ if ($raiseForErrors && array_key_exists('wrapper_data', $metadata)) {
+ $statusCode = $this->getStatusCode($metadata['wrapper_data']);
+
+ if ($statusCode >= 400) {
+ throw new InvalidStatusException('Request failed with status code '.$statusCode, $statusCode);
+ }
}
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(@stream_get_meta_data($stream), true));
+ $this->logger->debug('HttpClient: metadata='.var_export($metadata, true));
$this->logger->debug('HttpClient: response='.$response);
$this->logger->debug('HttpClient: executionTime='.(microtime(true) - $startTime));
}
@@ -180,14 +206,15 @@ class Client extends Base
* @param string $method
* @param string $content
* @param string[] $headers
+ * @param bool $raiseForErrors
* @return array
*/
- private function getContext($method, $content, array $headers)
+ private function getContext($method, $content, array $headers, $raiseForErrors = false)
{
- $default_headers = array(
+ $default_headers = [
'User-Agent: '.self::HTTP_USER_AGENT,
'Connection: close',
- );
+ ];
if (HTTP_PROXY_USERNAME) {
$default_headers[] = 'Proxy-Authorization: Basic '.base64_encode(HTTP_PROXY_USERNAME.':'.HTTP_PROXY_PASSWORD);
@@ -195,16 +222,17 @@ class Client extends Base
$headers = array_merge($default_headers, $headers);
- $context = array(
- 'http' => array(
+ $context = [
+ 'http' => [
'method' => $method,
'protocol_version' => 1.1,
'timeout' => self::HTTP_TIMEOUT,
'max_redirects' => self::HTTP_MAX_REDIRECTS,
'header' => implode("\r\n", $headers),
'content' => $content,
- )
- );
+ 'ignore_errors' => $raiseForErrors,
+ ]
+ ];
if (HTTP_PROXY_HOSTNAME) {
$context['http']['proxy'] = 'tcp://'.HTTP_PROXY_HOSTNAME.':'.HTTP_PROXY_PORT;
@@ -212,13 +240,26 @@ class Client extends Base
}
if (HTTP_VERIFY_SSL_CERTIFICATE === false) {
- $context['ssl'] = array(
+ $context['ssl'] = [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true,
- );
+ ];
}
return $context;
}
+
+ private function getStatusCode(array $lines)
+ {
+ $status = 200;
+
+ foreach ($lines as $line) {
+ if (strpos($line, 'HTTP/1') === 0) {
+ $status = (int) substr($line, 9, 3);
+ }
+ }
+
+ return $status;
+ }
}
diff --git a/app/Core/Http/ClientException.php b/app/Core/Http/ClientException.php
new file mode 100644
index 00000000..5eb783f9
--- /dev/null
+++ b/app/Core/Http/ClientException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Kanboard\Core\Http;
+
+use Exception;
+
+class ClientException extends Exception
+{
+}
diff --git a/app/Core/Http/InvalidStatusException.php b/app/Core/Http/InvalidStatusException.php
new file mode 100644
index 00000000..2d4a8d36
--- /dev/null
+++ b/app/Core/Http/InvalidStatusException.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Kanboard\Core\Http;
+
+class InvalidStatusException extends ClientException
+{
+ protected $statusCode = 0;
+
+ public function __construct($message, $statusCode)
+ {
+ parent::__construct($message);
+ $this->statusCode = $statusCode;
+ }
+
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+}