icingaweb2-module-graphite/library/Graphite/Graphing/GraphiteWebClient.php
Adam James 5e0d57cce0 Add Graphite Web HTTP request timeout option
If the Graphite Web server is unreachable, all requests for frontend
pages containing graphs hang until the backend HTTP request times out,
resulting in a very poor UX.

The Guzzle documentation states that the default behaviour is to wait
indefinitely, however in our testing the cURL handler has an internal
default of 30 seconds:

https://docs.guzzlephp.org/en/stable/request-options.html#timeout

This commit makes the HTTP request timeout configurable and sets a
reasonable default of 10 seconds.
2024-02-20 15:43:34 +01:00

232 lines
4.6 KiB
PHP

<?php
namespace Icinga\Module\Graphite\Graphing;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Request;
use Icinga\Web\Url;
/**
* HTTP interface to Graphite Web
*/
class GraphiteWebClient
{
/**
* Base URL of every Graphite Web HTTP request
*
* @var Url
*/
protected $baseUrl;
/**
* HTTP basic auth user for every Graphite Web HTTP request
*
* @var string|null
*/
protected $user;
/**
* The above user's password
*
* @var string|null
*/
protected $password;
/**
* Don't verify the remote's TLS certificate
*
* @var bool
*/
protected $insecure = false;
/**
* Timeout for every Graphite Web HTTP request
*
* @var ?int
*/
protected $timeout;
/**
* HTTP client
*
* @var ClientInterface
*/
protected $httpClient;
/**
* Constructor
*
* @param Url $baseUrl Base URL of every Graphite Web HTTP request
*/
public function __construct(Url $baseUrl)
{
$this->httpClient = new Client();
$this->setBaseUrl($baseUrl);
}
/**
* Send an HTTP request to the configured Graphite Web and return the response's body
*
* @param Url $url
* @param string $method
* @param string[] $headers
* @param string $body
*
* @return string
*/
public function request(Url $url, $method = 'GET', array $headers = [], $body = null)
{
$headers['User-Agent'] = 'icingaweb2-module-graphite';
if ($this->user !== null) {
$headers['Authorization'] = 'Basic ' . base64_encode("{$this->user}:{$this->password}");
}
// TODO(ak): keep connections alive (TCP handshakes are a bit expensive and TLS handshakes are very expensive)
return (string) $this->httpClient->send(
new Request($method, $this->completeUrl($url)->getAbsoluteUrl(), $headers, $body),
[
'curl' => [
CURLOPT_SSL_VERIFYPEER => ! $this->insecure
],
'timeout' => $this->timeout ?? 10
]
)->getBody();
}
/**
* Complete the given relative URL according to the base URL
*
* @param Url $url
*
* @return Url
*/
public function completeUrl(Url $url)
{
$completeUrl = clone $this->baseUrl;
return $completeUrl
->setPath(ltrim(rtrim($completeUrl->getPath(), '/') . '/' . ltrim($url->getPath(), '/'), '/'))
->setParams($url->getParams());
}
/**
* Get the base URL of every Graphite Web HTTP request
*
* @return Url
*/
public function getBaseUrl()
{
return $this->baseUrl;
}
/**
* Set the base URL of every Graphite Web HTTP request
*
* @param Url $baseUrl
*
* @return $this
*/
public function setBaseUrl(Url $baseUrl)
{
$this->baseUrl = $baseUrl;
return $this;
}
/**
* Get the HTTP basic auth user
*
* @return null|string
*/
public function getUser()
{
return $this->user;
}
/**
* Set the HTTP basic auth user
*
* @param null|string $user
*
* @return $this
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* Get the HTTP basic auth password
*
* @return null|string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set the HTTP basic auth password
*
* @param null|string $password
*
* @return $this
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get whether not to verify the remote's TLS certificate
*
* @return bool
*/
public function getInsecure()
{
return $this->insecure;
}
/**
* Set whether not to verify the remote's TLS certificate
*
* @param bool $insecure
*
* @return $this
*/
public function setInsecure($insecure = true)
{
$this->insecure = $insecure;
return $this;
}
/**
* Get the HTTP request timeout
*
* @return ?int
*/
public function getTimeout(): ?int
{
return $this->timeout;
}
/**
* Set the HTTP request timeout
*
* @param ?int $timeout
*
* @return $this
*/
public function setTimeout(?int $timeout): self
{
$this->timeout = $timeout;
return $this;
}
}