PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / vendor / symfony / http-client / RetryableHttpClient.php

RetryableHttpClient.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at vendor/symfony/http-client/RetryableHttpClient.php

172 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\HttpClient;
13
14 use Psr\Log\LoggerInterface;
15 use Psr\Log\NullLogger;
16 use Symfony\Component\HttpClient\Response\AsyncContext;
17 use Symfony\Component\HttpClient\Response\AsyncResponse;
18 use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
19 use Symfony\Component\HttpClient\Retry\RetryStrategyInterface;
20 use Symfony\Contracts\HttpClient\ChunkInterface;
21 use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
22 use Symfony\Contracts\HttpClient\HttpClientInterface;
23 use Symfony\Contracts\HttpClient\ResponseInterface;
24 use Symfony\Contracts\Service\ResetInterface;
25
26 /**
27 * Automatically retries failing HTTP requests.
28 *
29 * @author Jérémy Derussé <[email protected]>
30 */
31 class RetryableHttpClient implements HttpClientInterface, ResetInterface
32 {
33 use AsyncDecoratorTrait;
34
35 private $strategy;
36 private $maxRetries;
37 private $logger;
38
39 /**
40 * @param int $maxRetries The maximum number of times to retry
41 */
42 public function __construct(HttpClientInterface $client, RetryStrategyInterface $strategy = null, int $maxRetries = 3, LoggerInterface $logger = null)
43 {
44 $this->client = $client;
45 $this->strategy = $strategy ?? new GenericRetryStrategy();
46 $this->maxRetries = $maxRetries;
47 $this->logger = $logger ?? new NullLogger();
48 }
49
50 public function request(string $method, string $url, array $options = []): ResponseInterface
51 {
52 if ($this->maxRetries <= 0) {
53 return new AsyncResponse($this->client, $method, $url, $options);
54 }
55
56 $retryCount = 0;
57 $content = '';
58 $firstChunk = null;
59
60 return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) {
61 $exception = null;
62 try {
63 if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) {
64 yield $chunk;
65
66 return;
67 }
68 } catch (TransportExceptionInterface $exception) {
69 // catch TransportExceptionInterface to send it to the strategy
70 }
71 if (null !== $exception) {
72 // always retry request that fail to resolve DNS
73 if ('' !== $context->getInfo('primary_ip')) {
74 $shouldRetry = $this->strategy->shouldRetry($context, null, $exception);
75 if (null === $shouldRetry) {
76 throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy)));
77 }
78
79 if (false === $shouldRetry) {
80 yield from $this->passthru($context, $firstChunk, $content, $chunk);
81
82 return;
83 }
84 }
85 } elseif ($chunk->isFirst()) {
86 if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) {
87 yield from $this->passthru($context, $firstChunk, $content, $chunk);
88
89 return;
90 }
91
92 // Body is needed to decide
93 if (null === $shouldRetry) {
94 $firstChunk = $chunk;
95 $content = '';
96
97 return;
98 }
99 } else {
100 if (!$chunk->isLast()) {
101 $content .= $chunk->getContent();
102
103 return;
104 }
105
106 if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) {
107 throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy)));
108 }
109
110 if (false === $shouldRetry) {
111 yield from $this->passthru($context, $firstChunk, $content, $chunk);
112
113 return;
114 }
115 }
116
117 $context->getResponse()->cancel();
118
119 $delay = $this->getDelayFromHeader($context->getHeaders()) ?? $this->strategy->getDelay($context, !$exception && $chunk->isLast() ? $content : null, $exception);
120 ++$retryCount;
121 $content = '';
122 $firstChunk = null;
123
124 $this->logger->info('Try #{count} after {delay}ms'.($exception ? ': '.$exception->getMessage() : ', status code: '.$context->getStatusCode()), [
125 'count' => $retryCount,
126 'delay' => $delay,
127 ]);
128
129 $context->setInfo('retry_count', $retryCount);
130 $context->replaceRequest($method, $url, $options);
131 $context->pause($delay / 1000);
132
133 if ($retryCount >= $this->maxRetries) {
134 $context->passthru();
135 }
136 });
137 }
138
139 private function getDelayFromHeader(array $headers): ?int
140 {
141 if (null !== $after = $headers['retry-after'][0] ?? null) {
142 if (is_numeric($after)) {
143 return (int) ($after * 1000);
144 }
145
146 if (false !== $time = strtotime($after)) {
147 return max(0, $time - time()) * 1000;
148 }
149 }
150
151 return null;
152 }
153
154 private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator
155 {
156 $context->passthru();
157
158 if (null !== $firstChunk) {
159 yield $firstChunk;
160 }
161
162 if ('' !== $content) {
163 $chunk = $context->createChunk($content);
164 $content = '';
165
166 yield $chunk;
167 }
168
169 yield $lastChunk;
170 }
171 }
172