mirror of
https://github.com/guzzle/guzzle.git
synced 2025-02-24 10:03:27 +01:00
Adding retry tests
This commit is contained in:
parent
1f08b4f91f
commit
45e2733bd1
@ -27,6 +27,9 @@ class RetrySubscriber implements EventSubscriberInterface
|
||||
/** @var int */
|
||||
private $maxRetries;
|
||||
|
||||
/** @var callable */
|
||||
private $sleepFn;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
@ -36,45 +39,48 @@ class RetrySubscriber implements EventSubscriberInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $filter Filter used to determine whether or not to retry a request. The filter must be a
|
||||
* callable that accepts the current number of retries and an AbstractTransferStatsEvent
|
||||
* object. The filter must return true or false to denote if the request must be retried.
|
||||
* @param callable $delayFunc Callable that accepts the number of retries and an AbstractTransferStatsEvent and
|
||||
* returns the amount of of time in seconds to delay.
|
||||
* @param int $maxRetries Maximum number of retries
|
||||
* @param callable $filter Filter used to determine whether or not to retry a request. The filter must be a
|
||||
* callable that accepts the current number of retries and an AbstractTransferStatsEvent
|
||||
* object. The filter must return true or false to denote if the request must be retried
|
||||
* @param callable $delayFunc Callable that accepts the number of retries and an AbstractTransferStatsEvent and
|
||||
* returns the amount of of time in seconds to delay.
|
||||
* @param int $maxRetries Maximum number of retries
|
||||
* @param callable $sleepFn Function invoked when the subscriber needs to sleep. Accepts a float containing the
|
||||
* amount of time in seconds to sleep and an AbstractTransferStatsEvent.
|
||||
*/
|
||||
public function __construct(
|
||||
callable $filter,
|
||||
callable $delayFunc,
|
||||
$maxRetries = 3
|
||||
$maxRetries = 5,
|
||||
callable $sleepFn = null
|
||||
) {
|
||||
$this->filter = $filter;
|
||||
$this->delayFunc = $delayFunc;
|
||||
$this->maxRetries = $maxRetries;
|
||||
$this->sleepFn = $sleepFn ?: function($time) { usleep($time * 1000); };
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a basic truncated exponential backoff plugin that will retry HTTP errors and cURL errors
|
||||
*
|
||||
* @param int $maxRetries Maximum number of retries
|
||||
* @param array $httpCodes HTTP response codes to retry
|
||||
* @param array $curlCodes cURL error codes to retry
|
||||
* @param LoggerInterface|bool $logger Pass a logger instance to log each retry or pass true for STDOUT
|
||||
* @param string|MessageFormatter $formatter Pass a message formatter if logging to customize log messages
|
||||
* @param array $config Exponential backoff configuration
|
||||
* - max_retries: Maximum number of retries (overiddes the default of 5)
|
||||
* - http_codes: HTTP response codes to retry (overrides the default)
|
||||
* - curl_codes: cURL error codes to retry (overrides the default)
|
||||
* - logger: Pass a logger instance to log each retry or pass true for STDOUT
|
||||
* - formatter: Pass a message formatter if logging to customize log messages
|
||||
* - sleep_fn: Pass a callable to override how to sleep.
|
||||
*
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException if logger is not a boolean or LoggerInterface
|
||||
*/
|
||||
public static function getExponentialBackoff(
|
||||
$maxRetries = 3,
|
||||
array $httpCodes = null,
|
||||
array $curlCodes = null,
|
||||
$logger = null,
|
||||
$formatter = null
|
||||
) {
|
||||
public static function getExponentialBackoff(array $config = [])
|
||||
{
|
||||
$httpCodes = isset($config['http_codes']) ? $config['http_codes'] : null;
|
||||
if (!extension_loaded('curl')) {
|
||||
$filter = self::createStatusFilter($httpCodes);
|
||||
} else {
|
||||
$curlCodes = isset($config['curl_codes']) ? $config['curl_codes'] : null;
|
||||
$filter = self::createChainFilter([
|
||||
self::createStatusFilter($httpCodes),
|
||||
self::createCurlFilter($curlCodes)
|
||||
@ -83,16 +89,18 @@ class RetrySubscriber implements EventSubscriberInterface
|
||||
|
||||
$delay = [__CLASS__, 'exponentialDelay'];
|
||||
|
||||
if ($logger) {
|
||||
if ($logger === true) {
|
||||
$logger = new SimpleLogger(STDOUT);
|
||||
} elseif (!($logger instanceof LoggerInterface)) {
|
||||
if (isset($config['logger'])) {
|
||||
$logger = $config['logger'];
|
||||
if (!($logger instanceof LoggerInterface)) {
|
||||
throw new \InvalidArgumentException('$logger must be true, false, or a LoggerInterface');
|
||||
}
|
||||
$formatter = isset($config['formatter']) ? $config['formatter'] : null;
|
||||
$delay = self::createLoggingDelay($delay, $logger, $formatter);
|
||||
}
|
||||
|
||||
return new self($filter, $delay, $maxRetries);
|
||||
$maxRetries = isset($config['max_retries']) ? $config['max_retries'] : 5;
|
||||
|
||||
return new self($filter, $delay, $maxRetries, isset($config['sleep_fn']) ? $config['sleep_fn'] : null);
|
||||
}
|
||||
|
||||
public function onRequestSent(AbstractTransferStatsEvent $event)
|
||||
@ -104,7 +112,8 @@ class RetrySubscriber implements EventSubscriberInterface
|
||||
$filterFn = $this->filter;
|
||||
if ($filterFn($retries, $event)) {
|
||||
$delayFn = $this->delayFunc;
|
||||
sleep($delayFn($retries, $event));
|
||||
$sleepFn = $this->sleepFn;
|
||||
$sleepFn($delayFn($retries, $event), $event);
|
||||
$request->getConfig()->set('retries', ++$retries);
|
||||
$event->intercept($event->getClient()->send($request));
|
||||
}
|
||||
|
173
tests/Guzzle/Tests/Subscriber/Retry/RetrySubscriberTest.php
Normal file
173
tests/Guzzle/Tests/Subscriber/Retry/RetrySubscriberTest.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Guzzle\Tests\Subscriber\RetrySubscriber;
|
||||
|
||||
use Guzzle\Http\Adapter\Transaction;
|
||||
use Guzzle\Http\Client;
|
||||
use Guzzle\Http\Event\RequestAfterSendEvent;
|
||||
use Guzzle\Http\Message\Request;
|
||||
use Guzzle\Http\Message\RequestInterface;
|
||||
use Guzzle\Http\Message\Response;
|
||||
use Guzzle\Http\Message\ResponseInterface;
|
||||
use Guzzle\Http\Subscriber\History;
|
||||
use Guzzle\Http\Subscriber\Mock;
|
||||
use Guzzle\Subscriber\Log\SimpleLogger;
|
||||
use Guzzle\Subscriber\Retry\RetrySubscriber;
|
||||
|
||||
class RetrySubscriberTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCreatesDefaultStatusFilter()
|
||||
{
|
||||
$f = RetrySubscriber::createStatusFilter();
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$this->assertTrue($f(1, $e));
|
||||
$e = $this->createEvent(new Response(503));
|
||||
$this->assertTrue($f(0, $e));
|
||||
$e = $this->createEvent(new Response(200));
|
||||
$this->assertFalse($f(1, $e));
|
||||
}
|
||||
|
||||
public function testCreatesCustomStatusFilter()
|
||||
{
|
||||
$f = RetrySubscriber::createStatusFilter([202, 304]);
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$this->assertFalse($f(1, $e));
|
||||
$e = $this->createEvent(new Response(503));
|
||||
$this->assertFalse($f(0, $e));
|
||||
$e = $this->createEvent(new Response(202));
|
||||
$this->assertTrue($f(1, $e));
|
||||
$e = $this->createEvent();
|
||||
$this->assertFalse($f(1, $e));
|
||||
}
|
||||
|
||||
public function testCreatesDefaultCurlFilter()
|
||||
{
|
||||
$f = RetrySubscriber::createCurlFilter();
|
||||
$e = $this->createEvent(null, null, null, ['curl_result' => CURLE_RECV_ERROR]);
|
||||
$this->assertTrue($f(1, $e));
|
||||
$e = $this->createEvent(null, null, null, ['curl_result' => CURLE_OK]);
|
||||
$this->assertFalse($f(0, $e));
|
||||
}
|
||||
|
||||
public function testCreatesCustomCurlFilter()
|
||||
{
|
||||
$f = RetrySubscriber::createCurlFilter([CURLE_OK]);
|
||||
$e = $this->createEvent(null, null, null, ['curl_result' => CURLE_RECV_ERROR]);
|
||||
$this->assertFalse($f(1, $e));
|
||||
$e = $this->createEvent(null, null, null, ['curl_result' => CURLE_OK]);
|
||||
$this->assertTrue($f(0, $e));
|
||||
}
|
||||
|
||||
public function testCreatesChainFilter()
|
||||
{
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$f = RetrySubscriber::createChainFilter([
|
||||
function () { return false; },
|
||||
function () { return true; },
|
||||
]);
|
||||
$this->assertTrue($f(1, $e));
|
||||
$f = RetrySubscriber::createChainFilter([function () { return false; }]);
|
||||
$this->assertFalse($f(1, $e));
|
||||
$f = RetrySubscriber::createChainFilter([function () { return true; }]);
|
||||
$this->assertTrue($f(1, $e));
|
||||
}
|
||||
|
||||
public function testCreateLoggingDelayFilter()
|
||||
{
|
||||
$str = fopen('php://temp', 'r+');
|
||||
$l = new SimpleLogger($str);
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$f = RetrySubscriber::createLoggingDelay(function () {
|
||||
return true;
|
||||
}, $l);
|
||||
$this->assertTrue($f(2, $e));
|
||||
rewind($str);
|
||||
$this->assertContains('500 Internal Server Error - Retries: 3, Delay: 1', stream_get_contents($str));
|
||||
}
|
||||
|
||||
public function testCreateLoggingDelayFilterWithCustomFormat()
|
||||
{
|
||||
$str = fopen('php://temp', 'r+');
|
||||
$l = new SimpleLogger($str);
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$f = RetrySubscriber::createLoggingDelay(function () {
|
||||
return true;
|
||||
}, $l, 'Foo');
|
||||
$this->assertTrue($f(2, $e));
|
||||
rewind($str);
|
||||
$this->assertContains('Foo', stream_get_contents($str));
|
||||
}
|
||||
|
||||
public function testCalculatesExponentialDelay()
|
||||
{
|
||||
$e = $this->createEvent(new Response(500));
|
||||
$this->assertEquals(0, RetrySubscriber::exponentialDelay(0, $e));
|
||||
$this->assertEquals(1, RetrySubscriber::exponentialDelay(1, $e));
|
||||
$this->assertEquals(2, RetrySubscriber::exponentialDelay(2, $e));
|
||||
$this->assertEquals(4, RetrySubscriber::exponentialDelay(3, $e));
|
||||
$this->assertEquals(8, RetrySubscriber::exponentialDelay(4, $e));
|
||||
}
|
||||
|
||||
public function testCreatesExponentialPlugin()
|
||||
{
|
||||
$t = null;
|
||||
$str = fopen('php://temp', 'r+');
|
||||
$l = new SimpleLogger($str);
|
||||
$s = RetrySubscriber::getExponentialBackoff([
|
||||
'max_retries' => 3,
|
||||
'http_codes' => [500, 503],
|
||||
'curl_codes' => [CURLE_OPERATION_TIMEOUTED],
|
||||
'logger' => $l,
|
||||
'sleep_fn' => function ($time) use (&$t) {
|
||||
$t += $time;
|
||||
}
|
||||
]);
|
||||
$c = new Client();
|
||||
$c->getEmitter()->addSubscriber($s);
|
||||
$h = new History();
|
||||
$c->getEmitter()->addSubscriber($h);
|
||||
$c->getEmitter()->addSubscriber(new Mock([
|
||||
new Response(500),
|
||||
new Response(503),
|
||||
new Response(200)
|
||||
]));
|
||||
$this->assertEquals(200, $c->get('/')->getStatusCode());
|
||||
rewind($str);
|
||||
$this->assertNotEmpty(stream_get_contents($str));
|
||||
$this->assertEquals(3, count($h));
|
||||
$this->assertEquals(1, $t);
|
||||
}
|
||||
|
||||
private function createEvent(
|
||||
ResponseInterface $response = null,
|
||||
RequestInterface $request = null,
|
||||
\Exception $exception = null,
|
||||
array $transferInfo = [],
|
||||
$type = 'Guzzle\Http\Event\AbstractTransferStatsEvent'
|
||||
) {
|
||||
if (!$request) {
|
||||
$request = new Request('GET', 'http://www.foo.com');
|
||||
}
|
||||
|
||||
$e = $this->getMockBuilder($type)
|
||||
->setMethods(['getResponse', 'getTransferInfo', 'getRequest', 'getException'])
|
||||
->disableOriginalConstructor()
|
||||
->getMockForAbstractClass();
|
||||
$e->expects($this->any())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue($request));
|
||||
$e->expects($this->any())
|
||||
->method('getResponse')
|
||||
->will($this->returnValue($response));
|
||||
$e->expects($this->any())
|
||||
->method('getException')
|
||||
->will($this->returnValue($exception));
|
||||
$e->expects($this->any())
|
||||
->method('getTransferInfo')
|
||||
->will($this->returnCallback(function ($arg) use ($transferInfo) {
|
||||
return $arg ? (isset($transferInfo[$arg]) ? $transferInfo[$arg] : null) : $transferInfo;
|
||||
}));
|
||||
|
||||
return $e;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user