Created
January 16, 2020 11:19
-
-
Save devster/a1823eac5cd721fa3b9b25f04157a344 to your computer and use it in GitHub Desktop.
Retry decorator for symfony HttpClientInterface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\HttpClient; | |
use App\HttpClient\Response\RetryResponse; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Contracts\HttpClient\HttpClientInterface; | |
use Symfony\Contracts\HttpClient\ResponseInterface; | |
use Symfony\Contracts\HttpClient\ResponseStreamInterface; | |
final class RetryHttpClient implements HttpClientInterface | |
{ | |
private HttpClientInterface $client; | |
private LoggerInterface $logger; | |
public function __construct(HttpClientInterface $client, LoggerInterface $logger) | |
{ | |
$this->client = $client; | |
$this->logger = $logger; | |
} | |
/** | |
* @param array<mixed> $options | |
*/ | |
public function request(string $method, string $url, array $options = []): ResponseInterface | |
{ | |
$retries = $options['retries'] ?? 0; | |
unset($options['retries']); | |
return new RetryResponse( | |
$this->client->request($method, $url, $options), | |
$this->client, | |
$this->logger, | |
[$method, $url, $options], | |
$retries | |
); | |
} | |
public function stream($responses, float $timeout = null): ResponseStreamInterface | |
{ | |
return $this->client->stream($responses, $timeout); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\HttpClient\Response; | |
use App\Exception\RetryTransportException; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; | |
use Symfony\Contracts\HttpClient\HttpClientInterface; | |
use Symfony\Contracts\HttpClient\ResponseInterface; | |
final class RetryResponse implements ResponseInterface | |
{ | |
private ResponseInterface $response; | |
private HttpClientInterface $client; | |
private LoggerInterface $logger; | |
/** | |
* $request = [$method, $url, $options] | |
* | |
* @var array<mixed> | |
*/ | |
private array $request; | |
private int $retries; | |
/** | |
* @param array<mixed> $request | |
*/ | |
public function __construct( | |
ResponseInterface $response, | |
HttpClientInterface $client, | |
LoggerInterface $logger, | |
array $request, | |
int $retries | |
) { | |
$this->response = $response; | |
$this->client = $client; | |
$this->logger = $logger; | |
$this->request = $request; | |
$this->retries = $retries; | |
} | |
/** | |
* @return mixed | |
*/ | |
private function retry(\Closure $closure) | |
{ | |
$retries = $this->retries + 1; | |
while ($retries) { | |
$retries--; | |
try { | |
return $closure(); | |
} catch (TransportExceptionInterface $e) { | |
if (0 === $retries) { | |
throw new RetryTransportException($e, $this->retries); | |
} | |
$this->logger->notice('TransportException: {message}. Handled by retry. Remaining retries: {remaining}', [ | |
'message' => $e->getMessage(), | |
'remaining' => $retries, | |
'exception' => $e, | |
]); | |
sleep(1); | |
$this->response = $this->client->request(...$this->request); | |
} | |
} | |
return; | |
} | |
public function __destruct() | |
{ | |
$this->retry(function (): void { | |
if (!method_exists($this->response, '__destruct')) { | |
return; | |
} | |
$this->response->__destruct(); | |
}); | |
} | |
public function getStatusCode(): int | |
{ | |
return $this->retry(function (): int { | |
return $this->response->getStatusCode(); | |
}); | |
} | |
public function getHeaders(bool $throw = true): array | |
{ | |
return $this->retry(function () use ($throw): array { | |
return $this->response->getHeaders($throw); | |
}); | |
} | |
public function getContent(bool $throw = true): string | |
{ | |
return $this->retry(function () use ($throw): string { | |
return $this->response->getContent($throw); | |
}); | |
} | |
/** | |
* @return array<mixed> | |
*/ | |
public function toArray(bool $throw = true): array | |
{ | |
return $this->retry(function () use ($throw): array { | |
return $this->response->toArray($throw); | |
}); | |
} | |
public function cancel(): void | |
{ | |
$this->response->cancel(); | |
} | |
public function getInfo(string $type = null) | |
{ | |
return $this->response->getInfo($type); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment