-
Notifications
You must be signed in to change notification settings - Fork 25
/
CurlRemoteGetRequest.php
160 lines (135 loc) · 5.02 KB
/
CurlRemoteGetRequest.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<?php
namespace AmpProject\RemoteRequest;
use AmpProject\Exception\FailedRemoteRequest;
use AmpProject\Exception\FailedToGetFromRemoteUrl;
use AmpProject\RemoteGetRequest;
use AmpProject\Response;
use AmpProject\Exception\FailedToParseUrl;
/**
* Remote request transport using cURL.
*
* @package ampproject/amp-toolbox
*/
final class CurlRemoteGetRequest implements RemoteGetRequest
{
/**
* Default timeout value to use in seconds.
*
* @var int
*/
const DEFAULT_TIMEOUT = 10;
/**
* Default number of retry attempts to do.
*
* @var int
*/
const DEFAULT_RETRIES = 2;
/**
* List of cURL error codes that are worth retrying for.
*
* @var int[]
*/
const RETRYABLE_ERROR_CODES = [
CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT,
CURLE_READ_ERROR,
CURLE_OPERATION_TIMEOUTED,
CURLE_HTTP_POST_ERROR,
CURLE_SSL_CONNECT_ERROR,
];
/**
* Whether to verify SSL certificates or not.
*
* @var boolean
*/
private $sslVerify;
/**
* Timeout value to use in seconds.
*
* @var int
*/
private $timeout;
/**
* Number of retry attempts to do for an error that is worth retrying.
*
* @var int
*/
private $retries;
/**
* Instantiate a CurlRemoteGetRequest object.
*
* @param bool $sslVerify Optional. Whether to verify SSL certificates. Defaults to true.
* @param int $timeout Optional. Timeout value to use in seconds. Defaults to 10.
* @param int $retries Optional. Number of retry attempts to do if an error code was thrown that is worth
* retrying. Defaults to 2.
*/
public function __construct($sslVerify = true, $timeout = self::DEFAULT_TIMEOUT, $retries = self::DEFAULT_RETRIES)
{
if (! is_int($timeout) || $timeout < 0) {
$timeout = self::DEFAULT_TIMEOUT;
}
if (! is_int($retries) || $retries < 0) {
$retries = self::DEFAULT_RETRIES;
}
$this->sslVerify = $sslVerify;
$this->timeout = $timeout;
$this->retries = $retries;
}
/**
* Do a GET request to retrieve the contents of a remote URL.
*
* @param string $url URL to get.
* @param array $headers Optional. Associative array of headers to send with the request. Defaults to empty array.
* @return Response Response for the executed request.
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
*/
public function get($url, $headers = [])
{
$retriesLeft = $this->retries;
if (! is_string($url) || empty($url)) {
throw FailedToGetFromRemoteUrl::withException($url, FailedToParseUrl::forUrl($url));
}
do {
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_URL, $url);
curl_setopt($curlHandle, CURLOPT_HEADER, false);
curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, $this->sslVerify);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, $this->sslVerify ? 2 : 0);
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_FAILONERROR, true);
curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, $this->timeout);
curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt(
$curlHandle,
CURLOPT_HEADERFUNCTION,
static function ($curl, $header) use (&$headers) {
$length = strlen($header);
$header = array_map('trim', explode(':', $header, 2));
// Only store valid headers, discard invalid ones that choke on the explode.
if (count($header) === 2) {
$headers[$header[0]][] = $header[1];
}
return $length;
}
);
$body = curl_exec($curlHandle);
$status = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
$curlErrno = curl_errno($curlHandle);
curl_close($curlHandle);
if ($body === false || $status < 200 || $status >= 300) {
if (! $retriesLeft || in_array($curlErrno, self::RETRYABLE_ERROR_CODES, true) === false) {
if (! empty($status) && is_numeric($status)) {
throw FailedToGetFromRemoteUrl::withHttpStatus($url, (int) $status);
}
throw FailedToGetFromRemoteUrl::withoutHttpStatus($url);
}
continue;
}
return new RemoteGetRequestResponse($body, $headers, (int) $status);
} while ($retriesLeft--);
// This should never be triggered, but we want to ensure we always have a typed return value,
// to make PHPStan happy.
return new RemoteGetRequestResponse('', [], 500);
}
}