diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad67f984..e2237e6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Bump minimum version of `guzzlehttp/psr7` package to avoid [`CVE-2022-24775`](https://github.com/guzzle/psr7/security/advisories/GHSA-q7rv-6hp3-vh96) (#1305) +- Add `http_connect_timeout` and `http_timeout` client options (#1282) ## 3.4.0 (2022-03-14) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6361feee5..947091ce3 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -125,11 +125,21 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpConnectTimeout\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getHttpProxy\\(\\) should return string\\|null but returns mixed\\.$#" count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getHttpTimeout\\(\\) should return float but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/HttpClient/HttpClientFactory.php b/src/HttpClient/HttpClientFactory.php index ce6a88c75..0a2d3f172 100644 --- a/src/HttpClient/HttpClientFactory.php +++ b/src/HttpClient/HttpClientFactory.php @@ -31,17 +31,6 @@ */ final class HttpClientFactory implements HttpClientFactoryInterface { - /** - * @var int The timeout of the request in seconds - */ - private const DEFAULT_HTTP_TIMEOUT = 5; - - /** - * @var int The default number of seconds to wait while trying to connect - * to a server - */ - private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; - /** * @var StreamFactoryInterface The PSR-17 stream factory */ @@ -123,7 +112,8 @@ private function resolveClient(Options $options) { if (class_exists(SymfonyHttplugClient::class)) { $symfonyConfig = [ - 'max_duration' => self::DEFAULT_HTTP_TIMEOUT, + 'timeout' => $options->getHttpConnectTimeout(), + 'max_duration' => $options->getHttpTimeout(), ]; if (null !== $options->getHttpProxy()) { @@ -135,8 +125,8 @@ private function resolveClient(Options $options) if (class_exists(GuzzleHttpClient::class)) { $guzzleConfig = [ - GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + GuzzleHttpClientOptions::TIMEOUT => $options->getHttpTimeout(), + GuzzleHttpClientOptions::CONNECT_TIMEOUT => $options->getHttpConnectTimeout(), ]; if (null !== $options->getHttpProxy()) { @@ -148,8 +138,8 @@ private function resolveClient(Options $options) if (class_exists(CurlHttpClient::class)) { $curlConfig = [ - \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, - \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + \CURLOPT_TIMEOUT => $options->getHttpTimeout(), + \CURLOPT_CONNECTTIMEOUT => $options->getHttpConnectTimeout(), ]; if (null !== $options->getHttpProxy()) { diff --git a/src/Options.php b/src/Options.php index 486b4fcaf..6229e2cdf 100644 --- a/src/Options.php +++ b/src/Options.php @@ -22,10 +22,22 @@ final class Options */ public const DEFAULT_MAX_BREADCRUMBS = 100; + /** + * The default maximum execution time in seconds for the request+response + * as a whole. + */ + public const DEFAULT_HTTP_TIMEOUT = 5; + + /** + * The default maximum number of seconds to wait while trying to connect to a + * server. + */ + public const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; + /** * @var array The configuration options */ - private $options = []; + private $options; /** * @var OptionsResolver The options resolver @@ -578,6 +590,48 @@ public function setHttpProxy(?string $httpProxy): void $this->options = $this->resolver->resolve($options); } + /** + * Gets the maximum number of seconds to wait while trying to connect to a server. + */ + public function getHttpConnectTimeout(): float + { + return $this->options['http_connect_timeout']; + } + + /** + * Sets the maximum number of seconds to wait while trying to connect to a server. + * + * @param float $httpConnectTimeout The amount of time in seconds + */ + public function setHttpConnectTimeout(float $httpConnectTimeout): void + { + $options = array_merge($this->options, ['http_connect_timeout' => $httpConnectTimeout]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets the maximum execution time for the request+response as a whole. + */ + public function getHttpTimeout(): float + { + return $this->options['http_timeout']; + } + + /** + * Sets the maximum execution time for the request+response as a whole. The + * value should also include the time for the connect phase, so it should be + * greater than the value set for the `http_connect_timeout` option. + * + * @param float $httpTimeout The amount of time in seconds + */ + public function setHttpTimeout(float $httpTimeout): void + { + $options = array_merge($this->options, ['http_timeout' => $httpTimeout]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets whether the silenced errors should be captured or not. * @@ -726,6 +780,8 @@ private function configureOptions(OptionsResolver $resolver): void 'send_default_pii' => false, 'max_value_length' => 1024, 'http_proxy' => null, + 'http_connect_timeout' => self::DEFAULT_HTTP_CONNECT_TIMEOUT, + 'http_timeout' => self::DEFAULT_HTTP_TIMEOUT, 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], @@ -756,6 +812,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('default_integrations', 'bool'); $resolver->setAllowedTypes('max_value_length', 'int'); $resolver->setAllowedTypes('http_proxy', ['null', 'string']); + $resolver->setAllowedTypes('http_connect_timeout', ['int', 'float']); + $resolver->setAllowedTypes('http_timeout', ['int', 'float']); $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 7adf45485..6b6d96284 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -51,7 +51,7 @@ public function testConstructor( $options = new Options([$option => $value]); - $this->assertSame($value, $options->$getterMethod()); + $this->assertEquals($value, $options->$getterMethod()); } /** @@ -81,7 +81,7 @@ public function testGettersAndSetters( $options->$setterMethod($value); } - $this->assertSame($value, $options->$getterMethod()); + $this->assertEquals($value, $options->$getterMethod()); } public function optionsDataProvider(): \Generator @@ -293,6 +293,42 @@ static function (): void {}, null, ]; + yield [ + 'http_timeout', + 1, + 'getHttpTimeout', + 'setHttpTimeout', + null, + null, + ]; + + yield [ + 'http_timeout', + 1.2, + 'getHttpTimeout', + 'setHttpTimeout', + null, + null, + ]; + + yield [ + 'http_connect_timeout', + 1, + 'getHttpConnectTimeout', + 'setHttpConnectTimeout', + null, + null, + ]; + + yield [ + 'http_connect_timeout', + 1.2, + 'getHttpConnectTimeout', + 'setHttpConnectTimeout', + null, + null, + ]; + yield [ 'capture_silenced_errors', true,