Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a default timeout & connect_timeout to Guzzle #616

Merged
merged 5 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Bugsnag\Callbacks\RequestMetaData;
use Bugsnag\Callbacks\RequestSession;
use Bugsnag\Callbacks\RequestUser;
use Bugsnag\Internal\GuzzleCompat;
use Bugsnag\Middleware\BreadcrumbData;
use Bugsnag\Middleware\CallbackBridge;
use Bugsnag\Middleware\NotificationSkipper;
Expand Down Expand Up @@ -73,6 +74,15 @@ class Client
*/
protected $sessionTracker;

/**
* Default HTTP timeout, in seconds.
*
* @internal
*
* @var float
*/
const DEFAULT_TIMEOUT_S = 15.0;

/**
* Make a new client instance.
*
Expand Down Expand Up @@ -145,39 +155,35 @@ public function __construct(
*/
public static function makeGuzzle($base = null, array $options = [])
{
$key = self::getGuzzleBaseUriOptionName();

$options[$key] = $base ?: Configuration::NOTIFY_ENDPOINT;

if ($path = static::getCaBundlePath()) {
$options['verify'] = $path;
}
$options = self::resolveGuzzleOptions($base, $options);

return new GuzzleHttp\Client($options);
}

/**
* Get the base URL/URI option name, which depends on the Guzzle version.
* @param string|null $base
* @param array $options
*
* @return string
* @return array
*/
private static function getGuzzleBaseUriOptionName()
private static function resolveGuzzleOptions($base, array $options)
{
return method_exists(GuzzleHttp\ClientInterface::class, 'request')
? 'base_uri'
: 'base_url';
}
$key = GuzzleCompat::getBaseUriOptionName();
$options[$key] = $base ?: Configuration::NOTIFY_ENDPOINT;

/**
* Get the base URL/URI, which depends on the Guzzle version.
*
* @return mixed
*/
private function getGuzzleBaseUri(GuzzleHttp\ClientInterface $guzzle)
{
return method_exists(GuzzleHttp\ClientInterface::class, 'getBaseUrl')
? $guzzle->getBaseUrl()
: $guzzle->getConfig(self::getGuzzleBaseUriOptionName());
$path = static::getCaBundlePath();

if ($path) {
$options['verify'] = $path;
}

return GuzzleCompat::applyRequestOptions(
$options,
[
'timeout' => self::DEFAULT_TIMEOUT_S,
'connect_timeout' => self::DEFAULT_TIMEOUT_S,
]
);
}

/**
Expand All @@ -199,7 +205,7 @@ private function syncNotifyEndpointWithGuzzleBaseUri(
return;
}

$base = $this->getGuzzleBaseUri($guzzle);
$base = GuzzleCompat::getBaseUri($guzzle);

if (is_string($base) || (is_object($base) && method_exists($base, '__toString'))) {
$configuration->setNotifyEndpoint((string) $base);
Expand Down
7 changes: 4 additions & 3 deletions src/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Bugsnag;

use Bugsnag\DateTime\Date;
use Bugsnag\Internal\GuzzleCompat;
use Exception;
use GuzzleHttp\ClientInterface;
use RuntimeException;
Expand Down Expand Up @@ -267,10 +268,10 @@ protected function getHeaders($version = self::NOTIFY_PAYLOAD_VERSION)
*/
protected function post($uri, array $options = [])
{
if (method_exists(ClientInterface::class, 'request')) {
$this->guzzle->request('POST', $uri, $options);
} else {
if (GuzzleCompat::isUsingGuzzle5()) {
$this->guzzle->post($uri, $options);
} else {
$this->guzzle->request('POST', $uri, $options);
}
}

Expand Down
88 changes: 88 additions & 0 deletions src/Internal/GuzzleCompat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Bugsnag\Internal;

use GuzzleHttp;

/**
* @internal
*/
final class GuzzleCompat
{
/**
* @return bool
*/
public static function isUsingGuzzle5()
{
if (defined(GuzzleHttp\ClientInterface::class.'::VERSION')) {
$version = constant(GuzzleHttp\ClientInterface::class.'::VERSION');

return version_compare($version, '5.0.0', '>=')
&& version_compare($version, '6.0.0', '<');
}

return false;
}

/**
* Get the base URL/URI option name, which depends on the Guzzle version.
*
* @return string
*/
public static function getBaseUriOptionName()
{
return self::isUsingGuzzle5() ? 'base_url' : 'base_uri';
}

/**
* Get the base URL/URI, which depends on the Guzzle version.
*
* @param GuzzleHttp\ClientInterface $guzzle
*
* @return mixed
*/
public static function getBaseUri(GuzzleHttp\ClientInterface $guzzle)
{
return self::isUsingGuzzle5()
? $guzzle->getBaseUrl()
: $guzzle->getConfig(self::getBaseUriOptionName());
}

/**
* Apply the given $requestOptions to the Guzzle $options array, if they are
* not already set.
*
* The layout of request options differs in Guzzle 5 to 6/7; in Guzzle 5
* request options live in a 'defaults' array, but in 6/7 they are in the
* top level
*
* @param array $options
* @param array $requestOptions
*
* @return array
*/
public static function applyRequestOptions(array $options, array $requestOptions)
{
if (self::isUsingGuzzle5()) {
if (!isset($options['defaults'])) {
$options['defaults'] = [];
}

foreach ($requestOptions as $key => $value) {
if (!isset($options['defaults'][$key])) {
$options['defaults'][$key] = $value;
}
}

return $options;
}

foreach ($requestOptions as $key => $value) {
if (!isset($options[$key])) {
$options[$key] = $value;
}
}

return $options;
}
}
48 changes: 43 additions & 5 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Bugsnag\Client;
use Bugsnag\Configuration;
use Bugsnag\Internal\GuzzleCompat;
use Bugsnag\Report;
use Bugsnag\Tests\Fakes\FakeShutdownStrategy;
use Exception;
Expand Down Expand Up @@ -149,7 +150,7 @@ public function testTheApiKeyAndNotifyEndpointCanBeSetViaEnvSuperglobal()
public function testTheNotifyEndpointCanBeSetBySettingItOnAGuzzleInstance()
{
$guzzle = new Guzzle([
$this->getGuzzleBaseOptionName() => 'https://example.com',
GuzzleCompat::getBaseUriOptionName() => 'https://example.com',
]);

$client = new Client(new Configuration('abc'), null, $guzzle);
Expand All @@ -173,7 +174,7 @@ public function testTheNotifyEndpointWontBeOverwrittenByGivenGuzzleInstanceWhenO
$config->setNotifyEndpoint('https://foo.com');

$guzzle = new Guzzle([
$this->getGuzzleBaseOptionName() => 'https://example.com',
GuzzleCompat::getBaseUriOptionName() => 'https://example.com',
]);

$client = new Client($config, null, $guzzle);
Expand All @@ -195,14 +196,14 @@ public function testTheNotifyEndpointWontBeOverwrittenByMakeGuzzleWhenOneIsAlrea

public function testTheNotifyEndpointCanBeSetBySettingItOnAGuzzleInstanceWithAnArray()
{
if (!$this->isUsingGuzzle5()) {
if (!GuzzleCompat::isUsingGuzzle5()) {
$this->markTestSkipped(
'This test is not relevant on Guzzle >= 6 as arrays are not allowed'
);
}

$guzzle = new Guzzle([
$this->getGuzzleBaseOptionName() => [
GuzzleCompat::getBaseUriOptionName() => [
'https://example.com/{version}', ['version' => '1.2'],
],
]);
Expand All @@ -215,7 +216,7 @@ public function testTheNotifyEndpointCanBeSetBySettingItOnAGuzzleInstanceWithAnA
public function testTheNotifyEndpointCanBeSetBySettingItOnAGuzzleInstanceWithAUriInstance()
{
$guzzle = new Guzzle([
$this->getGuzzleBaseOptionName() => new Uri('https://example.com:8080/hello/world'),
GuzzleCompat::getBaseUriOptionName() => new Uri('https://example.com:8080/hello/world'),
]);

$client = new Client(new Configuration('abc'), null, $guzzle);
Expand Down Expand Up @@ -1098,6 +1099,43 @@ public function testShutdownStrategyIsCalledWithinConstructor()
$this->assertTrue($shutdownStrategy->wasRegistered());
}

public function testMakeGuzzleCreatesAGuzzleInstanceWithATimeout()
{
$guzzle = Client::makeGuzzle();

$timeout = $this->getGuzzleOption($guzzle, 'timeout');
$connectTimeout = $this->getGuzzleOption($guzzle, 'connect_timeout');

$this->assertSame(Client::DEFAULT_TIMEOUT_S, $timeout);
$this->assertSame(Client::DEFAULT_TIMEOUT_S, $connectTimeout);
}

public function testMakeGuzzleCreatesTimeoutCanBeSpecified()
{
$options = ['timeout' => 1, 'connect_timeout' => 2];

if (GuzzleCompat::isUsingGuzzle5()) {
$options = ['defaults' => $options];
}

$guzzle = Client::makeGuzzle(null, $options);

$timeout = $this->getGuzzleOption($guzzle, 'timeout');
$connectTimeout = $this->getGuzzleOption($guzzle, 'connect_timeout');

$this->assertSame(1, $timeout);
$this->assertSame(2, $connectTimeout);
}

private function getGuzzleOption($guzzle, $name)
{
if (GuzzleCompat::isUsingGuzzle5()) {
return $guzzle->getDefaultOption($name);
}

return $guzzle->getConfig($name);
}

private function expectGuzzlePostWith($uri, array $options = [])
{
$method = self::getGuzzleMethod();
Expand Down
3 changes: 2 additions & 1 deletion tests/HttpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Bugsnag\Configuration;
use Bugsnag\HttpClient;
use Bugsnag\Internal\GuzzleCompat;
use Bugsnag\Report;
use Exception;
use GuzzleHttp\Client;
Expand Down Expand Up @@ -41,7 +42,7 @@ protected function beforeEach()

private function setExpectedGuzzleParameters($expectation, $callback)
{
if ($this->isUsingGuzzle5()) {
if (GuzzleCompat::isUsingGuzzle5()) {
$expectation->with(
$this->config->getNotifyEndpoint(),
$this->callback($callback)
Expand Down
20 changes: 2 additions & 18 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Bugsnag\Tests;

use GuzzleHttp\ClientInterface;
use Bugsnag\Internal\GuzzleCompat;
use phpmock\phpunit\PHPMock;
use PHPUnit\Framework\TestCase as BaseTestCase;
use PHPUnit\Runner\Version as PhpUnitVersion;
Expand Down Expand Up @@ -43,23 +43,7 @@ protected function isPhpUnit4()
*/
protected static function getGuzzleMethod()
{
return method_exists(ClientInterface::class, 'request') ? 'request' : 'post';
}

/**
* @return string
*/
protected function getGuzzleBaseOptionName()
{
return $this->isUsingGuzzle5() ? 'base_url' : 'base_uri';
}

/**
* @return bool
*/
protected function isUsingGuzzle5()
{
return method_exists(ClientInterface::class, 'getBaseUrl');
return GuzzleCompat::isUsingGuzzle5() ? 'post' : 'request';
}

private function phpUnitVersion()
Expand Down