diff options
author | Frédéric Guillot <fred@kanboard.net> | 2018-03-02 12:05:41 -0800 |
---|---|---|
committer | Frédéric Guillot <fred@kanboard.net> | 2018-03-02 12:05:41 -0800 |
commit | ebe04e672c7982a1560133ee87f417560a851a10 (patch) | |
tree | 459c4fd52f85e9f9032c9e3ec7e178e032a8c8c5 /app/Core | |
parent | 94ed32dedfeb32d8b89f457bae749fbec16fd9b0 (diff) |
Improve HTTP client to raise exceptions
Diffstat (limited to 'app/Core')
-rw-r--r-- | app/Core/Http/Client.php | 103 | ||||
-rw-r--r-- | app/Core/Http/ClientException.php | 9 | ||||
-rw-r--r-- | app/Core/Http/InvalidStatusException.php | 19 |
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; + } +} |