2013-07-08 13:11:07 +04:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Guzzle\Plugin\Backoff;
|
|
|
|
|
|
|
|
use Guzzle\Common\Event;
|
|
|
|
use Guzzle\Common\AbstractHasDispatcher;
|
|
|
|
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
|
|
|
|
use Guzzle\Http\Message\RequestInterface;
|
|
|
|
use Guzzle\Http\Curl\CurlMultiInterface;
|
2014-08-28 02:10:31 +04:00
|
|
|
use Guzzle\Http\Exception\CurlException;
|
2013-07-08 13:11:07 +04:00
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plugin to automatically retry failed HTTP requests using a backoff strategy
|
|
|
|
*/
|
|
|
|
class BackoffPlugin extends AbstractHasDispatcher implements EventSubscriberInterface
|
|
|
|
{
|
|
|
|
const DELAY_PARAM = CurlMultiInterface::BLOCKING;
|
|
|
|
const RETRY_PARAM = 'plugins.backoff.retry_count';
|
|
|
|
const RETRY_EVENT = 'plugins.backoff.retry';
|
|
|
|
|
|
|
|
/** @var BackoffStrategyInterface Backoff strategy */
|
|
|
|
protected $strategy;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param BackoffStrategyInterface $strategy The backoff strategy used to determine whether or not to retry and
|
|
|
|
* the amount of delay between retries.
|
|
|
|
*/
|
|
|
|
public function __construct(BackoffStrategyInterface $strategy = null)
|
|
|
|
{
|
|
|
|
$this->strategy = $strategy;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
public static function getExponentialBackoff(
|
|
|
|
$maxRetries = 3,
|
|
|
|
array $httpCodes = null,
|
|
|
|
array $curlCodes = null
|
|
|
|
) {
|
|
|
|
return new self(new TruncatedBackoffStrategy($maxRetries,
|
|
|
|
new HttpBackoffStrategy($httpCodes,
|
|
|
|
new CurlBackoffStrategy($curlCodes,
|
|
|
|
new ExponentialBackoffStrategy()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getAllEvents()
|
|
|
|
{
|
|
|
|
return array(self::RETRY_EVENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getSubscribedEvents()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
'request.sent' => 'onRequestSent',
|
|
|
|
'request.exception' => 'onRequestSent',
|
|
|
|
CurlMultiInterface::POLLING_REQUEST => 'onRequestPoll'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a request has been sent and isn't finished processing
|
|
|
|
*
|
|
|
|
* @param Event $event
|
|
|
|
*/
|
|
|
|
public function onRequestSent(Event $event)
|
|
|
|
{
|
|
|
|
$request = $event['request'];
|
|
|
|
$response = $event['response'];
|
|
|
|
$exception = $event['exception'];
|
|
|
|
|
|
|
|
$params = $request->getParams();
|
|
|
|
$retries = (int) $params->get(self::RETRY_PARAM);
|
|
|
|
$delay = $this->strategy->getBackoffPeriod($retries, $request, $response, $exception);
|
|
|
|
|
|
|
|
if ($delay !== false) {
|
|
|
|
// Calculate how long to wait until the request should be retried
|
|
|
|
$params->set(self::RETRY_PARAM, ++$retries)
|
|
|
|
->set(self::DELAY_PARAM, microtime(true) + $delay);
|
|
|
|
// Send the request again
|
|
|
|
$request->setState(RequestInterface::STATE_TRANSFER);
|
|
|
|
$this->dispatch(self::RETRY_EVENT, array(
|
|
|
|
'request' => $request,
|
|
|
|
'response' => $response,
|
2014-08-28 02:10:31 +04:00
|
|
|
'handle' => ($exception && $exception instanceof CurlException) ? $exception->getCurlHandle() : null,
|
2013-07-08 13:11:07 +04:00
|
|
|
'retries' => $retries,
|
|
|
|
'delay' => $delay
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when a request is polling in the curl multi object
|
|
|
|
*
|
|
|
|
* @param Event $event
|
|
|
|
*/
|
|
|
|
public function onRequestPoll(Event $event)
|
|
|
|
{
|
|
|
|
$request = $event['request'];
|
|
|
|
$delay = $request->getParams()->get(self::DELAY_PARAM);
|
|
|
|
|
|
|
|
// If the duration of the delay has passed, retry the request using the pool
|
|
|
|
if (null !== $delay && microtime(true) >= $delay) {
|
|
|
|
// Remove the request from the pool and then add it back again. This is required for cURL to know that we
|
|
|
|
// want to retry sending the easy handle.
|
|
|
|
$request->getParams()->remove(self::DELAY_PARAM);
|
|
|
|
// Rewind the request body if possible
|
|
|
|
if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()) {
|
|
|
|
$request->getBody()->seek(0);
|
|
|
|
}
|
|
|
|
$multi = $event['curl_multi'];
|
|
|
|
$multi->remove($request);
|
|
|
|
$multi->add($request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|