Completed
Pull Request — master (#28)
by
unknown
10:54 queued 09:16
created

MiddlewareFactory::getRetryDelay()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 11
cts 11
cp 1
rs 9.2
cc 4
eloc 10
nc 1
nop 0
crap 4
1
<?php
2
3
namespace Mediawiki\Api\Guzzle;
4
5
use GuzzleHttp\Exception\ConnectException;
6
use GuzzleHttp\Exception\RequestException;
7
use GuzzleHttp\Middleware;
8
use GuzzleHttp\Psr7\Request;
9
use GuzzleHttp\Psr7\Response;
10
use Psr\Log\LoggerAwareInterface;
11
use Psr\Log\LoggerInterface;
12
use Psr\Log\NullLogger;
13
14
/**
15
 * @access private
16
 *
17
 * @author Addshore
18
 */
19
class MiddlewareFactory implements LoggerAwareInterface {
20
21
	/**
22
	 * @var LoggerInterface
23
	 */
24
	private $logger;
25
26 10
	public function __construct() {
27 10
		$this->logger = new NullLogger();
28 10
	}
29
30
	public function setLogger( LoggerInterface $logger ) {
31
		$this->logger = $logger;
32
	}
33
34
	/**
35
	 * @access private
36
	 *
37
	 * @param bool $delay default to true, can be false to speed up tests
38
	 *
39
	 * @return callable
40
	 */
41 10
	public function retry( $delay = true ) {
42 10
		if( $delay ) {
43 5
			return Middleware::retry( $this->newRetryDecider(), $this->getRetryDelay() );
44
		} else {
45 5
			return Middleware::retry( $this->newRetryDecider() );
46
		}
47
	}
48
49
	/**
50
	 * Returns a method that takes the number of retries and returns the number of miliseconds
51
	 * to wait
52
	 *
53
	 * @return callable
54
	 */
55 5
	private function getRetryDelay() {
56
		return function( $numberOfRetries, Response $response = null ) {
57
			// The $response argument is only passed as of Guzzle 6.2.2.
58 5
			if( $response ) {
59
				// Retry-After may be a number of seonds or an absolute date.
60 4
				$retryAfter = $response->getHeaderLine( 'Retry-After' );
61 4
				if( is_numeric( $retryAfter ) ) {
62 1
					return 1000 * $retryAfter;
63 3
				} elseif( $retryAfter ) {
64 2
					$seconds = strtotime( $retryAfter ) - time();
65 2
					return 1000 * max( 1, $seconds );
66
				}
67 1
			}
68
69 2
			return 1000 * $numberOfRetries;
70 5
		};
71
	}
72
73
	/**
74
	 * @return callable
75
	 */
76
	private function newRetryDecider() {
77 10
		return function (
78
			$retries,
79
			Request $request,
80
			Response $response = null,
81
			RequestException $exception = null
82
		) {
83
			// Don't retry if we have run out of retries
84 10
			if ( $retries >= 5 ) {
85 2
				return false;
86
			}
87
88 10
			$shouldRetry = false;
89
90
			// Retry connection exceptions
91 10
			if( $exception instanceof ConnectException ) {
92 3
				$shouldRetry = true;
93 3
			}
94
95 10
			if( $response ) {
96 9
				$data = json_decode( $response->getBody(), true );
97
98
				// Retry on server errors
99 9
				if( $response->getStatusCode() >= 500 ) {
100 2
					$shouldRetry = true;
101 2
				}
102
103 9
				foreach( $response->getHeader( 'Mediawiki-Api-Error' ) as $mediawikiApiErrorHeader ) {
104
					if (
105 5
						$response->getHeaderLine( 'Retry-After' )
106
						||
107
						// Retry if we have a response with an API error worth retrying
108 2
						in_array(
109 2
							$mediawikiApiErrorHeader,
110
							array(
111 2
								'ratelimited',
112 2
								'maxlag',
113 2
								'readonly',
114 2
								'internal_api_error_DBQueryError',
115
							)
116 2
						)
117 2
						||
118
						// Or if we have been stopped from saving as an 'anti-abuse measure'
119
						// Note: this tries to match "actionthrottledtext" i18n messagae for mediawiki
120
						(
121 2
							$mediawikiApiErrorHeader == 'failed-save' &&
122 1
							strstr( $data['error']['info'], 'anti-abuse measure' )
123 1
						)
124 5
					) {
125 5
						$shouldRetry = true;
126 5
					}
127
128 9
				}
129 9
			}
130
131
			// Log if we are retrying
132 10
			if( $shouldRetry ) {
133 10
				$this->logger->warning(
134 10
					sprintf(
135 10
						'Retrying %s %s %s/5, %s',
136 10
						$request->getMethod(),
137 10
						$request->getUri(),
138 10
						$retries + 1,
139 10
						$response ? 'status code: ' . $response->getStatusCode() :
140 3
							$exception->getMessage()
0 ignored issues
show
Bug introduced by
It seems like $exception is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
141 10
					)
142 10
				);
143 10
			}
144
145 10
			return $shouldRetry;
146 10
		};
147
	}
148
149
}
150