1313
1414namespace RoachPHP \Downloader \Middleware ;
1515
16+ use InvalidArgumentException ;
1617use Psr \Log \LoggerInterface ;
1718use RoachPHP \Http \Response ;
1819use RoachPHP \Scheduling \RequestSchedulerInterface ;
@@ -31,54 +32,80 @@ public function __construct(
3132 public function handleResponse (Response $ response ): Response
3233 {
3334 $ request = $ response ->getRequest ();
34-
35+
3536 /** @var int $retryCount */
3637 $ retryCount = $ request ->getMeta ('retry_count ' , 0 );
3738
3839 /** @var list<int> $retryOnStatus */
3940 $ retryOnStatus = $ this ->option ('retryOnStatus ' );
40-
41+
4142 /** @var int $maxRetries */
4243 $ maxRetries = $ this ->option ('maxRetries ' );
4344
44- if (\in_array ($ response ->getStatus (), $ retryOnStatus , true ) && $ retryCount < $ maxRetries ) {
45- /** @var int $initialDelay */
46- $ initialDelay = $ this ->option ('initialDelay ' );
47-
48- /** @var float $delayMultiplier */
49- $ delayMultiplier = $ this ->option ('delayMultiplier ' );
50-
51- $ delay = (int ) ($ initialDelay * ($ delayMultiplier ** $ retryCount ));
52-
53- $ this ->logger ->info (
54- 'Retrying request ' ,
55- [
56- 'uri ' => $ request ->getUri (),
57- 'status ' => $ response ->getStatus (),
58- 'retry_count ' => $ retryCount + 1 ,
59- 'delay_ms ' => $ delay ,
60- ],
45+ if (!\in_array ($ response ->getStatus (), $ retryOnStatus , true ) || $ retryCount >= $ maxRetries ) {
46+ return $ response ;
47+ }
48+
49+ $ delay = $ this ->getDelay ($ retryCount );
50+
51+ $ this ->logger ->info (
52+ 'Retrying request ' ,
53+ [
54+ 'uri ' => $ request ->getUri (),
55+ 'status ' => $ response ->getStatus (),
56+ 'retry_count ' => $ retryCount + 1 ,
57+ 'delay_ms ' => $ delay ,
58+ ],
59+ );
60+
61+ $ retryRequest = $ request
62+ ->withMeta ('retry_count ' , $ retryCount + 1 )
63+ ->addOption ('delay ' , $ delay );
64+
65+ $ this ->scheduler ->schedule ($ retryRequest );
66+
67+ return $ response ->drop ('Request being retried ' );
68+ }
69+
70+ private function getDelay (int $ retryCount ): int
71+ {
72+ /** @var int|list<int> $backoff */
73+ $ backoff = $ this ->option ('backoff ' );
74+
75+ if (\is_int ($ backoff )) {
76+ return $ backoff * 1000 ;
77+ }
78+
79+ if (!\is_array ($ backoff )) {
80+ throw new InvalidArgumentException (
81+ 'backoff must be an integer or array, ' . \gettype ($ backoff ) . ' given. ' ,
6182 );
83+ }
6284
63- $ retryRequest = $ request
64- -> withMeta ( ' retry_count ' , $ retryCount + 1 )
65- -> addOption ( ' delay ' , $ delay );
85+ if ([] === $ backoff ) {
86+ throw new InvalidArgumentException ( ' backoff array cannot be empty. ' );
87+ }
6688
67- $ this -> scheduler -> schedule ( $ retryRequest );
89+ $ nonIntegerValues = \array_filter ( $ backoff , static fn ( $ value ) => ! \is_int ( $ value ) );
6890
69- return $ response ->drop ('Request being retried ' );
91+ if ([] !== $ nonIntegerValues ) {
92+ throw new InvalidArgumentException (
93+ 'backoff array must contain only integers. Found: ' .
94+ \implode (', ' , \array_map ('gettype ' , $ nonIntegerValues )),
95+ );
7096 }
7197
72- return $ response ;
98+ $ delay = $ backoff [$ retryCount ] ?? $ backoff [\array_key_last ($ backoff )];
99+
100+ return $ delay * 1000 ;
73101 }
74102
75103 private static function defaultOptions (): array
76104 {
77105 return [
78106 'retryOnStatus ' => [500 , 502 , 503 , 504 ],
79107 'maxRetries ' => 3 ,
80- 'initialDelay ' => 1000 ,
81- 'delayMultiplier ' => 2.0 ,
108+ 'backoff ' => [1 , 5 , 10 ],
82109 ];
83110 }
84111}
0 commit comments