Browse code

removed Telnyx Plivo Twilio Flowroute appinfo/info.xml appinfo/signature.json README.md lib/Controller/AuthorApiController.php

DoubleBastionAdmin authored on 20/08/2022 16:26:33
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,463 +0,0 @@
1
-<?php
2
-
3
-namespace Telnyx\HttpClient;
4
-
5
-use Telnyx\Telnyx;
6
-use Telnyx\Error;
7
-use Telnyx\Util;
8
-
9
-// cURL constants are not defined in PHP < 5.5
10
-
11
-// @codingStandardsIgnoreStart
12
-// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
-// constants do not abide by those rules.
14
-
15
-// Note the values 1 and 6 come from their position in the enum that
16
-// defines them in cURL's source code.
17
-if (!defined('CURL_SSLVERSION_TLSv1')) {
18
-    define('CURL_SSLVERSION_TLSv1', 1);
19
-}
20
-if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
-    define('CURL_SSLVERSION_TLSv1_2', 6);
22
-}
23
-// @codingStandardsIgnoreEnd
24
-
25
-if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
-    define('CURL_HTTP_VERSION_2TLS', 4);
27
-}
28
-
29
-class CurlClient implements ClientInterface
30
-{
31
-    private static $instance;
32
-
33
-    public static function instance()
34
-    {
35
-        if (!self::$instance) {
36
-            self::$instance = new self();
37
-        }
38
-        return self::$instance;
39
-    }
40
-
41
-    protected $defaultOptions;
42
-
43
-    protected $userAgentInfo;
44
-
45
-    protected $enablePersistentConnections = null;
46
-
47
-    protected $enableHttp2 = null;
48
-
49
-    protected $curlHandle = null;
50
-
51
-    /**
52
-     * CurlClient constructor.
53
-     *
54
-     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
-     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
-     * provide a static set of options. Note that many options are overridden later in the request
57
-     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
-     *
59
-     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
-     * throw an exception if $defaultOptions returns a non-array value.
61
-     *
62
-     * @param array|callable|null $defaultOptions
63
-     */
64
-    public function __construct($defaultOptions = null, $randomGenerator = null)
65
-    {
66
-        $this->defaultOptions = $defaultOptions;
67
-        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
-        $this->initUserAgentInfo();
69
-
70
-        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
-        // initialize this to true.
72
-        $this->enablePersistentConnections = function_exists('curl_reset');
73
-
74
-        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
-    }
76
-
77
-    public function __destruct()
78
-    {
79
-        $this->closeCurlHandle();
80
-    }
81
-
82
-    public function initUserAgentInfo()
83
-    {
84
-        $curlVersion = curl_version();
85
-        $this->userAgentInfo = [
86
-            'httplib' =>  'curl ' . $curlVersion['version'],
87
-            'ssllib' => $curlVersion['ssl_version'],
88
-        ];
89
-    }
90
-
91
-    public function getDefaultOptions()
92
-    {
93
-        return $this->defaultOptions;
94
-    }
95
-
96
-    public function getUserAgentInfo()
97
-    {
98
-        return $this->userAgentInfo;
99
-    }
100
-
101
-    /**
102
-     * @return boolean
103
-     */
104
-    public function getEnablePersistentConnections()
105
-    {
106
-        return $this->enablePersistentConnections;
107
-    }
108
-
109
-    /**
110
-     * @param boolean $enable
111
-     */
112
-    public function setEnablePersistentConnections($enable)
113
-    {
114
-        $this->enablePersistentConnections = $enable;
115
-    }
116
-
117
-    /**
118
-     * @return boolean
119
-     */
120
-    public function getEnableHttp2()
121
-    {
122
-        return $this->enableHttp2;
123
-    }
124
-
125
-    /**
126
-     * @param boolean $enable
127
-     */
128
-    public function setEnableHttp2($enable)
129
-    {
130
-        $this->enableHttp2 = $enable;
131
-    }
132
-
133
-    // USER DEFINED TIMEOUTS
134
-
135
-    const DEFAULT_TIMEOUT = 80;
136
-    const DEFAULT_CONNECT_TIMEOUT = 30;
137
-
138
-    private $timeout = self::DEFAULT_TIMEOUT;
139
-    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
-
141
-    public function setTimeout($seconds)
142
-    {
143
-        $this->timeout = (int) max($seconds, 0);
144
-        return $this;
145
-    }
146
-
147
-    public function setConnectTimeout($seconds)
148
-    {
149
-        $this->connectTimeout = (int) max($seconds, 0);
150
-        return $this;
151
-    }
152
-
153
-    public function getTimeout()
154
-    {
155
-        return $this->timeout;
156
-    }
157
-
158
-    public function getConnectTimeout()
159
-    {
160
-        return $this->connectTimeout;
161
-    }
162
-
163
-    // END OF USER DEFINED TIMEOUTS
164
-
165
-    public function request($method, $absUrl, $headers, $params, $hasFile)
166
-    {
167
-        $method = strtolower($method);
168
-
169
-        $opts = [];
170
-        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
-            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
-            if (!is_array($opts)) {
173
-                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
-            }
175
-        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
-            $opts = $this->defaultOptions;
177
-        }
178
-
179
-        $params = Util\Util::objectsToIds($params);
180
-
181
-        if ($method == 'get') {
182
-            if ($hasFile) {
183
-                throw new Error\Api(
184
-                    "Issuing a GET request with a file parameter"
185
-                );
186
-            }
187
-            $opts[CURLOPT_HTTPGET] = 1;
188
-            if (count($params) > 0) {
189
-                $encoded = Util\Util::encodeParameters($params);
190
-                $absUrl = "$absUrl?$encoded";
191
-            }
192
-        } elseif ($method == 'post') {
193
-            $opts[CURLOPT_POST] = 1;
194
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
-        } elseif ($method == 'patch') {
196
-            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
-        } elseif ($method == 'delete') {
199
-            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
-            if (count($params) > 0) {
201
-                $encoded = Util\Util::encodeParameters($params);
202
-                $absUrl = "$absUrl?$encoded";
203
-            }
204
-        } else {
205
-            throw new Error\Api("Unrecognized method $method");
206
-        }
207
-
208
-        // It is only safe to retry network failures on POST requests if we
209
-        // add an Idempotency-Key header
210
-        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
-            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
-                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
-            }
214
-        }
215
-
216
-        // Create a callback to capture HTTP headers for the response
217
-        $rheaders = new Util\CaseInsensitiveArray();
218
-        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
-            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
-            if (strpos($header_line, ":") === false) {
221
-                return strlen($header_line);
222
-            }
223
-            list($key, $value) = explode(":", trim($header_line), 2);
224
-            $rheaders[trim($key)] = trim($value);
225
-            return strlen($header_line);
226
-        };
227
-
228
-        // By default for large request body sizes (> 1024 bytes), cURL will
229
-        // send a request without a body and with a `Expect: 100-continue`
230
-        // header, which gives the server a chance to respond with an error
231
-        // status code in cases where one can be determined right away (say
232
-        // on an authentication problem for example), and saves the "large"
233
-        // request body from being ever sent.
234
-        //
235
-        // Unfortunately, the bindings don't currently correctly handle the
236
-        // success case (in which the server sends back a 100 CONTINUE), so
237
-        // we'll error under that condition. To compensate for that problem
238
-        // for the time being, override cURL's behavior by simply always
239
-        // sending an empty `Expect:` header.
240
-        array_push($headers, 'Expect: ');
241
-
242
-        $absUrl = Util\Util::utf8($absUrl);
243
-        $opts[CURLOPT_URL] = $absUrl;
244
-        $opts[CURLOPT_RETURNTRANSFER] = true;
245
-        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
-        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
-        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
-        $opts[CURLOPT_HTTPHEADER] = $headers;
249
-        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
-        if (!Telnyx::getVerifySslCerts()) {
251
-            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
-        }
253
-
254
-        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
-            // For HTTPS requests, enable HTTP/2, if supported
256
-            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
-        }
258
-
259
-        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
-
261
-        return [$rbody, $rcode, $rheaders];
262
-    }
263
-
264
-    /**
265
-     * @param array $opts cURL options
266
-     */
267
-    private function executeRequestWithRetries($opts, $absUrl)
268
-    {
269
-        $numRetries = 0;
270
-
271
-        while (true) {
272
-            $rcode = 0;
273
-            $errno = 0;
274
-
275
-            $this->resetCurlHandle();
276
-            curl_setopt_array($this->curlHandle, $opts);
277
-            $rbody = curl_exec($this->curlHandle);
278
-
279
-            if ($rbody === false) {
280
-                $errno = curl_errno($this->curlHandle);
281
-                $message = curl_error($this->curlHandle);
282
-            } else {
283
-                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
-            }
285
-            if (!$this->getEnablePersistentConnections()) {
286
-                $this->closeCurlHandle();
287
-            }
288
-
289
-            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
-                $numRetries += 1;
291
-                $sleepSeconds = $this->sleepTime($numRetries);
292
-                usleep(intval($sleepSeconds * 1000000));
293
-            } else {
294
-                break;
295
-            }
296
-        }
297
-
298
-        if ($rbody === false) {
299
-            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
-        }
301
-
302
-        return [$rbody, $rcode];
303
-    }
304
-
305
-    /**
306
-     * @param string $url
307
-     * @param int $errno
308
-     * @param string $message
309
-     * @param int $numRetries
310
-     * @throws Error\ApiConnection
311
-     */
312
-    private function handleCurlError($url, $errno, $message, $numRetries)
313
-    {
314
-        switch ($errno) {
315
-            case CURLE_COULDNT_CONNECT:
316
-            case CURLE_COULDNT_RESOLVE_HOST:
317
-            case CURLE_OPERATION_TIMEOUTED:
318
-                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
-                 . "internet connection and try again.  If this problem persists, "
320
-                 . "you should check Telnyx's service status at "
321
-                 . "http://status.telnyx.com/, or";
322
-                break;
323
-            case CURLE_SSL_CACERT:
324
-            case CURLE_SSL_PEER_CERTIFICATE:
325
-                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
-                 . "that your network is not intercepting certificates.  "
327
-                 . "(Try going to $url in your browser.)  "
328
-                 . "If this problem persists,";
329
-                break;
330
-            default:
331
-                $msg = "Unexpected error communicating with Telnyx.  "
332
-                 . "If this problem persists,";
333
-        }
334
-        $msg .= " let us know at support@telnyx.com.";
335
-
336
-        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
-
338
-        if ($numRetries > 0) {
339
-            $msg .= "\n\nRequest was retried $numRetries times.";
340
-        }
341
-
342
-        throw new Error\ApiConnection($msg);
343
-    }
344
-
345
-    /**
346
-     * Checks if an error is a problem that we should retry on. This includes both
347
-     * socket errors that may represent an intermittent problem and some special
348
-     * HTTP statuses.
349
-     * @param int $errno
350
-     * @param int $rcode
351
-     * @param int $numRetries
352
-     * @return bool
353
-     */
354
-    private function shouldRetry($errno, $rcode, $numRetries)
355
-    {
356
-        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
-            return false;
358
-        }
359
-
360
-        // Retry on timeout-related problems (either on open or read).
361
-        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
-            return true;
363
-        }
364
-
365
-        // Destination refused the connection, the connection was reset, or a
366
-        // variety of other connection failures. This could occur from a single
367
-        // saturated server, so retry in case it's intermittent.
368
-        if ($errno === CURLE_COULDNT_CONNECT) {
369
-            return true;
370
-        }
371
-
372
-        // 409 conflict
373
-        if ($rcode === 409) {
374
-            return true;
375
-        }
376
-
377
-        return false;
378
-    }
379
-
380
-    private function sleepTime($numRetries)
381
-    {
382
-        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
-        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
-        // $maxNetworkRetryDelay.
385
-        $sleepSeconds = min(
386
-            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
-            Telnyx::getMaxNetworkRetryDelay()
388
-        );
389
-
390
-        // Apply some jitter by randomizing the value in the range of
391
-        // ($sleepSeconds / 2) to ($sleepSeconds).
392
-        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
-
394
-        // But never sleep less than the base sleep seconds.
395
-        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
-
397
-        return $sleepSeconds;
398
-    }
399
-
400
-    /**
401
-     * Initializes the curl handle. If already initialized, the handle is closed first.
402
-     */
403
-    private function initCurlHandle()
404
-    {
405
-        $this->closeCurlHandle();
406
-        $this->curlHandle = curl_init();
407
-    }
408
-
409
-    /**
410
-     * Closes the curl handle if initialized. Do nothing if already closed.
411
-     */
412
-    private function closeCurlHandle()
413
-    {
414
-        if (!is_null($this->curlHandle)) {
415
-            curl_close($this->curlHandle);
416
-            $this->curlHandle = null;
417
-        }
418
-    }
419
-
420
-    /**
421
-     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
-     * connections are disabled, the handle is reinitialized instead.
423
-     */
424
-    private function resetCurlHandle()
425
-    {
426
-        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
-            curl_reset($this->curlHandle);
428
-        } else {
429
-            $this->initCurlHandle();
430
-        }
431
-    }
432
-
433
-    /**
434
-     * Indicates whether it is safe to use HTTP/2 or not.
435
-     *
436
-     * @return boolean
437
-     */
438
-    private function canSafelyUseHttp2()
439
-    {
440
-        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
-        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
-        $curlVersion = curl_version()['version'];
443
-        return (version_compare($curlVersion, '7.60.0') >= 0);
444
-    }
445
-
446
-    /**
447
-     * Checks if a list of headers contains a specific header name.
448
-     *
449
-     * @param string[] $headers
450
-     * @param string $name
451
-     * @return boolean
452
-     */
453
-    private function hasHeader($headers, $name)
454
-    {
455
-        foreach ($headers as $header) {
456
-            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
-                return true;
458
-            }
459
-        }
460
-
461
-        return false;
462
-    }
463
-}
Browse code

added appinfo/signature.json Telnyx Twilio Flowroute

DoubleBastionAdmin authored on 19/08/2022 13:10:24
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,463 @@
1
+<?php
2
+
3
+namespace Telnyx\HttpClient;
4
+
5
+use Telnyx\Telnyx;
6
+use Telnyx\Error;
7
+use Telnyx\Util;
8
+
9
+// cURL constants are not defined in PHP < 5.5
10
+
11
+// @codingStandardsIgnoreStart
12
+// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
+// constants do not abide by those rules.
14
+
15
+// Note the values 1 and 6 come from their position in the enum that
16
+// defines them in cURL's source code.
17
+if (!defined('CURL_SSLVERSION_TLSv1')) {
18
+    define('CURL_SSLVERSION_TLSv1', 1);
19
+}
20
+if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
+    define('CURL_SSLVERSION_TLSv1_2', 6);
22
+}
23
+// @codingStandardsIgnoreEnd
24
+
25
+if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
+    define('CURL_HTTP_VERSION_2TLS', 4);
27
+}
28
+
29
+class CurlClient implements ClientInterface
30
+{
31
+    private static $instance;
32
+
33
+    public static function instance()
34
+    {
35
+        if (!self::$instance) {
36
+            self::$instance = new self();
37
+        }
38
+        return self::$instance;
39
+    }
40
+
41
+    protected $defaultOptions;
42
+
43
+    protected $userAgentInfo;
44
+
45
+    protected $enablePersistentConnections = null;
46
+
47
+    protected $enableHttp2 = null;
48
+
49
+    protected $curlHandle = null;
50
+
51
+    /**
52
+     * CurlClient constructor.
53
+     *
54
+     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
+     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
+     * provide a static set of options. Note that many options are overridden later in the request
57
+     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
+     *
59
+     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
+     * throw an exception if $defaultOptions returns a non-array value.
61
+     *
62
+     * @param array|callable|null $defaultOptions
63
+     */
64
+    public function __construct($defaultOptions = null, $randomGenerator = null)
65
+    {
66
+        $this->defaultOptions = $defaultOptions;
67
+        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
+        $this->initUserAgentInfo();
69
+
70
+        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
+        // initialize this to true.
72
+        $this->enablePersistentConnections = function_exists('curl_reset');
73
+
74
+        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
+    }
76
+
77
+    public function __destruct()
78
+    {
79
+        $this->closeCurlHandle();
80
+    }
81
+
82
+    public function initUserAgentInfo()
83
+    {
84
+        $curlVersion = curl_version();
85
+        $this->userAgentInfo = [
86
+            'httplib' =>  'curl ' . $curlVersion['version'],
87
+            'ssllib' => $curlVersion['ssl_version'],
88
+        ];
89
+    }
90
+
91
+    public function getDefaultOptions()
92
+    {
93
+        return $this->defaultOptions;
94
+    }
95
+
96
+    public function getUserAgentInfo()
97
+    {
98
+        return $this->userAgentInfo;
99
+    }
100
+
101
+    /**
102
+     * @return boolean
103
+     */
104
+    public function getEnablePersistentConnections()
105
+    {
106
+        return $this->enablePersistentConnections;
107
+    }
108
+
109
+    /**
110
+     * @param boolean $enable
111
+     */
112
+    public function setEnablePersistentConnections($enable)
113
+    {
114
+        $this->enablePersistentConnections = $enable;
115
+    }
116
+
117
+    /**
118
+     * @return boolean
119
+     */
120
+    public function getEnableHttp2()
121
+    {
122
+        return $this->enableHttp2;
123
+    }
124
+
125
+    /**
126
+     * @param boolean $enable
127
+     */
128
+    public function setEnableHttp2($enable)
129
+    {
130
+        $this->enableHttp2 = $enable;
131
+    }
132
+
133
+    // USER DEFINED TIMEOUTS
134
+
135
+    const DEFAULT_TIMEOUT = 80;
136
+    const DEFAULT_CONNECT_TIMEOUT = 30;
137
+
138
+    private $timeout = self::DEFAULT_TIMEOUT;
139
+    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
+
141
+    public function setTimeout($seconds)
142
+    {
143
+        $this->timeout = (int) max($seconds, 0);
144
+        return $this;
145
+    }
146
+
147
+    public function setConnectTimeout($seconds)
148
+    {
149
+        $this->connectTimeout = (int) max($seconds, 0);
150
+        return $this;
151
+    }
152
+
153
+    public function getTimeout()
154
+    {
155
+        return $this->timeout;
156
+    }
157
+
158
+    public function getConnectTimeout()
159
+    {
160
+        return $this->connectTimeout;
161
+    }
162
+
163
+    // END OF USER DEFINED TIMEOUTS
164
+
165
+    public function request($method, $absUrl, $headers, $params, $hasFile)
166
+    {
167
+        $method = strtolower($method);
168
+
169
+        $opts = [];
170
+        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
+            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
+            if (!is_array($opts)) {
173
+                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
+            }
175
+        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
+            $opts = $this->defaultOptions;
177
+        }
178
+
179
+        $params = Util\Util::objectsToIds($params);
180
+
181
+        if ($method == 'get') {
182
+            if ($hasFile) {
183
+                throw new Error\Api(
184
+                    "Issuing a GET request with a file parameter"
185
+                );
186
+            }
187
+            $opts[CURLOPT_HTTPGET] = 1;
188
+            if (count($params) > 0) {
189
+                $encoded = Util\Util::encodeParameters($params);
190
+                $absUrl = "$absUrl?$encoded";
191
+            }
192
+        } elseif ($method == 'post') {
193
+            $opts[CURLOPT_POST] = 1;
194
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
+        } elseif ($method == 'patch') {
196
+            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
+        } elseif ($method == 'delete') {
199
+            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
+            if (count($params) > 0) {
201
+                $encoded = Util\Util::encodeParameters($params);
202
+                $absUrl = "$absUrl?$encoded";
203
+            }
204
+        } else {
205
+            throw new Error\Api("Unrecognized method $method");
206
+        }
207
+
208
+        // It is only safe to retry network failures on POST requests if we
209
+        // add an Idempotency-Key header
210
+        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
+            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
+                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
+            }
214
+        }
215
+
216
+        // Create a callback to capture HTTP headers for the response
217
+        $rheaders = new Util\CaseInsensitiveArray();
218
+        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
+            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
+            if (strpos($header_line, ":") === false) {
221
+                return strlen($header_line);
222
+            }
223
+            list($key, $value) = explode(":", trim($header_line), 2);
224
+            $rheaders[trim($key)] = trim($value);
225
+            return strlen($header_line);
226
+        };
227
+
228
+        // By default for large request body sizes (> 1024 bytes), cURL will
229
+        // send a request without a body and with a `Expect: 100-continue`
230
+        // header, which gives the server a chance to respond with an error
231
+        // status code in cases where one can be determined right away (say
232
+        // on an authentication problem for example), and saves the "large"
233
+        // request body from being ever sent.
234
+        //
235
+        // Unfortunately, the bindings don't currently correctly handle the
236
+        // success case (in which the server sends back a 100 CONTINUE), so
237
+        // we'll error under that condition. To compensate for that problem
238
+        // for the time being, override cURL's behavior by simply always
239
+        // sending an empty `Expect:` header.
240
+        array_push($headers, 'Expect: ');
241
+
242
+        $absUrl = Util\Util::utf8($absUrl);
243
+        $opts[CURLOPT_URL] = $absUrl;
244
+        $opts[CURLOPT_RETURNTRANSFER] = true;
245
+        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
+        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
+        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
+        $opts[CURLOPT_HTTPHEADER] = $headers;
249
+        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
+        if (!Telnyx::getVerifySslCerts()) {
251
+            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
+        }
253
+
254
+        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
+            // For HTTPS requests, enable HTTP/2, if supported
256
+            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
+        }
258
+
259
+        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
+
261
+        return [$rbody, $rcode, $rheaders];
262
+    }
263
+
264
+    /**
265
+     * @param array $opts cURL options
266
+     */
267
+    private function executeRequestWithRetries($opts, $absUrl)
268
+    {
269
+        $numRetries = 0;
270
+
271
+        while (true) {
272
+            $rcode = 0;
273
+            $errno = 0;
274
+
275
+            $this->resetCurlHandle();
276
+            curl_setopt_array($this->curlHandle, $opts);
277
+            $rbody = curl_exec($this->curlHandle);
278
+
279
+            if ($rbody === false) {
280
+                $errno = curl_errno($this->curlHandle);
281
+                $message = curl_error($this->curlHandle);
282
+            } else {
283
+                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
+            }
285
+            if (!$this->getEnablePersistentConnections()) {
286
+                $this->closeCurlHandle();
287
+            }
288
+
289
+            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
+                $numRetries += 1;
291
+                $sleepSeconds = $this->sleepTime($numRetries);
292
+                usleep(intval($sleepSeconds * 1000000));
293
+            } else {
294
+                break;
295
+            }
296
+        }
297
+
298
+        if ($rbody === false) {
299
+            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
+        }
301
+
302
+        return [$rbody, $rcode];
303
+    }
304
+
305
+    /**
306
+     * @param string $url
307
+     * @param int $errno
308
+     * @param string $message
309
+     * @param int $numRetries
310
+     * @throws Error\ApiConnection
311
+     */
312
+    private function handleCurlError($url, $errno, $message, $numRetries)
313
+    {
314
+        switch ($errno) {
315
+            case CURLE_COULDNT_CONNECT:
316
+            case CURLE_COULDNT_RESOLVE_HOST:
317
+            case CURLE_OPERATION_TIMEOUTED:
318
+                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
+                 . "internet connection and try again.  If this problem persists, "
320
+                 . "you should check Telnyx's service status at "
321
+                 . "http://status.telnyx.com/, or";
322
+                break;
323
+            case CURLE_SSL_CACERT:
324
+            case CURLE_SSL_PEER_CERTIFICATE:
325
+                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
+                 . "that your network is not intercepting certificates.  "
327
+                 . "(Try going to $url in your browser.)  "
328
+                 . "If this problem persists,";
329
+                break;
330
+            default:
331
+                $msg = "Unexpected error communicating with Telnyx.  "
332
+                 . "If this problem persists,";
333
+        }
334
+        $msg .= " let us know at support@telnyx.com.";
335
+
336
+        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
+
338
+        if ($numRetries > 0) {
339
+            $msg .= "\n\nRequest was retried $numRetries times.";
340
+        }
341
+
342
+        throw new Error\ApiConnection($msg);
343
+    }
344
+
345
+    /**
346
+     * Checks if an error is a problem that we should retry on. This includes both
347
+     * socket errors that may represent an intermittent problem and some special
348
+     * HTTP statuses.
349
+     * @param int $errno
350
+     * @param int $rcode
351
+     * @param int $numRetries
352
+     * @return bool
353
+     */
354
+    private function shouldRetry($errno, $rcode, $numRetries)
355
+    {
356
+        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
+            return false;
358
+        }
359
+
360
+        // Retry on timeout-related problems (either on open or read).
361
+        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
+            return true;
363
+        }
364
+
365
+        // Destination refused the connection, the connection was reset, or a
366
+        // variety of other connection failures. This could occur from a single
367
+        // saturated server, so retry in case it's intermittent.
368
+        if ($errno === CURLE_COULDNT_CONNECT) {
369
+            return true;
370
+        }
371
+
372
+        // 409 conflict
373
+        if ($rcode === 409) {
374
+            return true;
375
+        }
376
+
377
+        return false;
378
+    }
379
+
380
+    private function sleepTime($numRetries)
381
+    {
382
+        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
+        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
+        // $maxNetworkRetryDelay.
385
+        $sleepSeconds = min(
386
+            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
+            Telnyx::getMaxNetworkRetryDelay()
388
+        );
389
+
390
+        // Apply some jitter by randomizing the value in the range of
391
+        // ($sleepSeconds / 2) to ($sleepSeconds).
392
+        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
+
394
+        // But never sleep less than the base sleep seconds.
395
+        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
+
397
+        return $sleepSeconds;
398
+    }
399
+
400
+    /**
401
+     * Initializes the curl handle. If already initialized, the handle is closed first.
402
+     */
403
+    private function initCurlHandle()
404
+    {
405
+        $this->closeCurlHandle();
406
+        $this->curlHandle = curl_init();
407
+    }
408
+
409
+    /**
410
+     * Closes the curl handle if initialized. Do nothing if already closed.
411
+     */
412
+    private function closeCurlHandle()
413
+    {
414
+        if (!is_null($this->curlHandle)) {
415
+            curl_close($this->curlHandle);
416
+            $this->curlHandle = null;
417
+        }
418
+    }
419
+
420
+    /**
421
+     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
+     * connections are disabled, the handle is reinitialized instead.
423
+     */
424
+    private function resetCurlHandle()
425
+    {
426
+        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
+            curl_reset($this->curlHandle);
428
+        } else {
429
+            $this->initCurlHandle();
430
+        }
431
+    }
432
+
433
+    /**
434
+     * Indicates whether it is safe to use HTTP/2 or not.
435
+     *
436
+     * @return boolean
437
+     */
438
+    private function canSafelyUseHttp2()
439
+    {
440
+        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
+        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
+        $curlVersion = curl_version()['version'];
443
+        return (version_compare($curlVersion, '7.60.0') >= 0);
444
+    }
445
+
446
+    /**
447
+     * Checks if a list of headers contains a specific header name.
448
+     *
449
+     * @param string[] $headers
450
+     * @param string $name
451
+     * @return boolean
452
+     */
453
+    private function hasHeader($headers, $name)
454
+    {
455
+        foreach ($headers as $header) {
456
+            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
+                return true;
458
+            }
459
+        }
460
+
461
+        return false;
462
+    }
463
+}
Browse code

removed appinfo/signature.json and Telnyx

DoubleBastionAdmin authored on 19/08/2022 12:45:59
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,463 +0,0 @@
1
-<?php
2
-
3
-namespace Telnyx\HttpClient;
4
-
5
-use Telnyx\Telnyx;
6
-use Telnyx\Error;
7
-use Telnyx\Util;
8
-
9
-// cURL constants are not defined in PHP < 5.5
10
-
11
-// @codingStandardsIgnoreStart
12
-// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
-// constants do not abide by those rules.
14
-
15
-// Note the values 1 and 6 come from their position in the enum that
16
-// defines them in cURL's source code.
17
-if (!defined('CURL_SSLVERSION_TLSv1')) {
18
-    define('CURL_SSLVERSION_TLSv1', 1);
19
-}
20
-if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
-    define('CURL_SSLVERSION_TLSv1_2', 6);
22
-}
23
-// @codingStandardsIgnoreEnd
24
-
25
-if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
-    define('CURL_HTTP_VERSION_2TLS', 4);
27
-}
28
-
29
-class CurlClient implements ClientInterface
30
-{
31
-    private static $instance;
32
-
33
-    public static function instance()
34
-    {
35
-        if (!self::$instance) {
36
-            self::$instance = new self();
37
-        }
38
-        return self::$instance;
39
-    }
40
-
41
-    protected $defaultOptions;
42
-
43
-    protected $userAgentInfo;
44
-
45
-    protected $enablePersistentConnections = null;
46
-
47
-    protected $enableHttp2 = null;
48
-
49
-    protected $curlHandle = null;
50
-
51
-    /**
52
-     * CurlClient constructor.
53
-     *
54
-     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
-     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
-     * provide a static set of options. Note that many options are overridden later in the request
57
-     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
-     *
59
-     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
-     * throw an exception if $defaultOptions returns a non-array value.
61
-     *
62
-     * @param array|callable|null $defaultOptions
63
-     */
64
-    public function __construct($defaultOptions = null, $randomGenerator = null)
65
-    {
66
-        $this->defaultOptions = $defaultOptions;
67
-        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
-        $this->initUserAgentInfo();
69
-
70
-        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
-        // initialize this to true.
72
-        $this->enablePersistentConnections = function_exists('curl_reset');
73
-
74
-        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
-    }
76
-
77
-    public function __destruct()
78
-    {
79
-        $this->closeCurlHandle();
80
-    }
81
-
82
-    public function initUserAgentInfo()
83
-    {
84
-        $curlVersion = curl_version();
85
-        $this->userAgentInfo = [
86
-            'httplib' =>  'curl ' . $curlVersion['version'],
87
-            'ssllib' => $curlVersion['ssl_version'],
88
-        ];
89
-    }
90
-
91
-    public function getDefaultOptions()
92
-    {
93
-        return $this->defaultOptions;
94
-    }
95
-
96
-    public function getUserAgentInfo()
97
-    {
98
-        return $this->userAgentInfo;
99
-    }
100
-
101
-    /**
102
-     * @return boolean
103
-     */
104
-    public function getEnablePersistentConnections()
105
-    {
106
-        return $this->enablePersistentConnections;
107
-    }
108
-
109
-    /**
110
-     * @param boolean $enable
111
-     */
112
-    public function setEnablePersistentConnections($enable)
113
-    {
114
-        $this->enablePersistentConnections = $enable;
115
-    }
116
-
117
-    /**
118
-     * @return boolean
119
-     */
120
-    public function getEnableHttp2()
121
-    {
122
-        return $this->enableHttp2;
123
-    }
124
-
125
-    /**
126
-     * @param boolean $enable
127
-     */
128
-    public function setEnableHttp2($enable)
129
-    {
130
-        $this->enableHttp2 = $enable;
131
-    }
132
-
133
-    // USER DEFINED TIMEOUTS
134
-
135
-    const DEFAULT_TIMEOUT = 80;
136
-    const DEFAULT_CONNECT_TIMEOUT = 30;
137
-
138
-    private $timeout = self::DEFAULT_TIMEOUT;
139
-    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
-
141
-    public function setTimeout($seconds)
142
-    {
143
-        $this->timeout = (int) max($seconds, 0);
144
-        return $this;
145
-    }
146
-
147
-    public function setConnectTimeout($seconds)
148
-    {
149
-        $this->connectTimeout = (int) max($seconds, 0);
150
-        return $this;
151
-    }
152
-
153
-    public function getTimeout()
154
-    {
155
-        return $this->timeout;
156
-    }
157
-
158
-    public function getConnectTimeout()
159
-    {
160
-        return $this->connectTimeout;
161
-    }
162
-
163
-    // END OF USER DEFINED TIMEOUTS
164
-
165
-    public function request($method, $absUrl, $headers, $params, $hasFile)
166
-    {
167
-        $method = strtolower($method);
168
-
169
-        $opts = [];
170
-        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
-            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
-            if (!is_array($opts)) {
173
-                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
-            }
175
-        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
-            $opts = $this->defaultOptions;
177
-        }
178
-
179
-        $params = Util\Util::objectsToIds($params);
180
-
181
-        if ($method == 'get') {
182
-            if ($hasFile) {
183
-                throw new Error\Api(
184
-                    "Issuing a GET request with a file parameter"
185
-                );
186
-            }
187
-            $opts[CURLOPT_HTTPGET] = 1;
188
-            if (count($params) > 0) {
189
-                $encoded = Util\Util::encodeParameters($params);
190
-                $absUrl = "$absUrl?$encoded";
191
-            }
192
-        } elseif ($method == 'post') {
193
-            $opts[CURLOPT_POST] = 1;
194
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
-        } elseif ($method == 'patch') {
196
-            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
-        } elseif ($method == 'delete') {
199
-            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
-            if (count($params) > 0) {
201
-                $encoded = Util\Util::encodeParameters($params);
202
-                $absUrl = "$absUrl?$encoded";
203
-            }
204
-        } else {
205
-            throw new Error\Api("Unrecognized method $method");
206
-        }
207
-
208
-        // It is only safe to retry network failures on POST requests if we
209
-        // add an Idempotency-Key header
210
-        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
-            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
-                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
-            }
214
-        }
215
-
216
-        // Create a callback to capture HTTP headers for the response
217
-        $rheaders = new Util\CaseInsensitiveArray();
218
-        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
-            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
-            if (strpos($header_line, ":") === false) {
221
-                return strlen($header_line);
222
-            }
223
-            list($key, $value) = explode(":", trim($header_line), 2);
224
-            $rheaders[trim($key)] = trim($value);
225
-            return strlen($header_line);
226
-        };
227
-
228
-        // By default for large request body sizes (> 1024 bytes), cURL will
229
-        // send a request without a body and with a `Expect: 100-continue`
230
-        // header, which gives the server a chance to respond with an error
231
-        // status code in cases where one can be determined right away (say
232
-        // on an authentication problem for example), and saves the "large"
233
-        // request body from being ever sent.
234
-        //
235
-        // Unfortunately, the bindings don't currently correctly handle the
236
-        // success case (in which the server sends back a 100 CONTINUE), so
237
-        // we'll error under that condition. To compensate for that problem
238
-        // for the time being, override cURL's behavior by simply always
239
-        // sending an empty `Expect:` header.
240
-        array_push($headers, 'Expect: ');
241
-
242
-        $absUrl = Util\Util::utf8($absUrl);
243
-        $opts[CURLOPT_URL] = $absUrl;
244
-        $opts[CURLOPT_RETURNTRANSFER] = true;
245
-        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
-        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
-        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
-        $opts[CURLOPT_HTTPHEADER] = $headers;
249
-        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
-        if (!Telnyx::getVerifySslCerts()) {
251
-            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
-        }
253
-
254
-        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
-            // For HTTPS requests, enable HTTP/2, if supported
256
-            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
-        }
258
-
259
-        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
-
261
-        return [$rbody, $rcode, $rheaders];
262
-    }
263
-
264
-    /**
265
-     * @param array $opts cURL options
266
-     */
267
-    private function executeRequestWithRetries($opts, $absUrl)
268
-    {
269
-        $numRetries = 0;
270
-
271
-        while (true) {
272
-            $rcode = 0;
273
-            $errno = 0;
274
-
275
-            $this->resetCurlHandle();
276
-            curl_setopt_array($this->curlHandle, $opts);
277
-            $rbody = curl_exec($this->curlHandle);
278
-
279
-            if ($rbody === false) {
280
-                $errno = curl_errno($this->curlHandle);
281
-                $message = curl_error($this->curlHandle);
282
-            } else {
283
-                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
-            }
285
-            if (!$this->getEnablePersistentConnections()) {
286
-                $this->closeCurlHandle();
287
-            }
288
-
289
-            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
-                $numRetries += 1;
291
-                $sleepSeconds = $this->sleepTime($numRetries);
292
-                usleep(intval($sleepSeconds * 1000000));
293
-            } else {
294
-                break;
295
-            }
296
-        }
297
-
298
-        if ($rbody === false) {
299
-            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
-        }
301
-
302
-        return [$rbody, $rcode];
303
-    }
304
-
305
-    /**
306
-     * @param string $url
307
-     * @param int $errno
308
-     * @param string $message
309
-     * @param int $numRetries
310
-     * @throws Error\ApiConnection
311
-     */
312
-    private function handleCurlError($url, $errno, $message, $numRetries)
313
-    {
314
-        switch ($errno) {
315
-            case CURLE_COULDNT_CONNECT:
316
-            case CURLE_COULDNT_RESOLVE_HOST:
317
-            case CURLE_OPERATION_TIMEOUTED:
318
-                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
-                 . "internet connection and try again.  If this problem persists, "
320
-                 . "you should check Telnyx's service status at "
321
-                 . "http://status.telnyx.com/, or";
322
-                break;
323
-            case CURLE_SSL_CACERT:
324
-            case CURLE_SSL_PEER_CERTIFICATE:
325
-                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
-                 . "that your network is not intercepting certificates.  "
327
-                 . "(Try going to $url in your browser.)  "
328
-                 . "If this problem persists,";
329
-                break;
330
-            default:
331
-                $msg = "Unexpected error communicating with Telnyx.  "
332
-                 . "If this problem persists,";
333
-        }
334
-        $msg .= " let us know at support@telnyx.com.";
335
-
336
-        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
-
338
-        if ($numRetries > 0) {
339
-            $msg .= "\n\nRequest was retried $numRetries times.";
340
-        }
341
-
342
-        throw new Error\ApiConnection($msg);
343
-    }
344
-
345
-    /**
346
-     * Checks if an error is a problem that we should retry on. This includes both
347
-     * socket errors that may represent an intermittent problem and some special
348
-     * HTTP statuses.
349
-     * @param int $errno
350
-     * @param int $rcode
351
-     * @param int $numRetries
352
-     * @return bool
353
-     */
354
-    private function shouldRetry($errno, $rcode, $numRetries)
355
-    {
356
-        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
-            return false;
358
-        }
359
-
360
-        // Retry on timeout-related problems (either on open or read).
361
-        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
-            return true;
363
-        }
364
-
365
-        // Destination refused the connection, the connection was reset, or a
366
-        // variety of other connection failures. This could occur from a single
367
-        // saturated server, so retry in case it's intermittent.
368
-        if ($errno === CURLE_COULDNT_CONNECT) {
369
-            return true;
370
-        }
371
-
372
-        // 409 conflict
373
-        if ($rcode === 409) {
374
-            return true;
375
-        }
376
-
377
-        return false;
378
-    }
379
-
380
-    private function sleepTime($numRetries)
381
-    {
382
-        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
-        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
-        // $maxNetworkRetryDelay.
385
-        $sleepSeconds = min(
386
-            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
-            Telnyx::getMaxNetworkRetryDelay()
388
-        );
389
-
390
-        // Apply some jitter by randomizing the value in the range of
391
-        // ($sleepSeconds / 2) to ($sleepSeconds).
392
-        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
-
394
-        // But never sleep less than the base sleep seconds.
395
-        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
-
397
-        return $sleepSeconds;
398
-    }
399
-
400
-    /**
401
-     * Initializes the curl handle. If already initialized, the handle is closed first.
402
-     */
403
-    private function initCurlHandle()
404
-    {
405
-        $this->closeCurlHandle();
406
-        $this->curlHandle = curl_init();
407
-    }
408
-
409
-    /**
410
-     * Closes the curl handle if initialized. Do nothing if already closed.
411
-     */
412
-    private function closeCurlHandle()
413
-    {
414
-        if (!is_null($this->curlHandle)) {
415
-            curl_close($this->curlHandle);
416
-            $this->curlHandle = null;
417
-        }
418
-    }
419
-
420
-    /**
421
-     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
-     * connections are disabled, the handle is reinitialized instead.
423
-     */
424
-    private function resetCurlHandle()
425
-    {
426
-        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
-            curl_reset($this->curlHandle);
428
-        } else {
429
-            $this->initCurlHandle();
430
-        }
431
-    }
432
-
433
-    /**
434
-     * Indicates whether it is safe to use HTTP/2 or not.
435
-     *
436
-     * @return boolean
437
-     */
438
-    private function canSafelyUseHttp2()
439
-    {
440
-        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
-        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
-        $curlVersion = curl_version()['version'];
443
-        return (version_compare($curlVersion, '7.60.0') >= 0);
444
-    }
445
-
446
-    /**
447
-     * Checks if a list of headers contains a specific header name.
448
-     *
449
-     * @param string[] $headers
450
-     * @param string $name
451
-     * @return boolean
452
-     */
453
-    private function hasHeader($headers, $name)
454
-    {
455
-        foreach ($headers as $header) {
456
-            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
-                return true;
458
-            }
459
-        }
460
-
461
-        return false;
462
-    }
463
-}
Browse code

added appinfo/signature.json and Telnyx directory

DoubleBastionAdmin authored on 19/08/2022 11:38:54
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,463 @@
1
+<?php
2
+
3
+namespace Telnyx\HttpClient;
4
+
5
+use Telnyx\Telnyx;
6
+use Telnyx\Error;
7
+use Telnyx\Util;
8
+
9
+// cURL constants are not defined in PHP < 5.5
10
+
11
+// @codingStandardsIgnoreStart
12
+// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
+// constants do not abide by those rules.
14
+
15
+// Note the values 1 and 6 come from their position in the enum that
16
+// defines them in cURL's source code.
17
+if (!defined('CURL_SSLVERSION_TLSv1')) {
18
+    define('CURL_SSLVERSION_TLSv1', 1);
19
+}
20
+if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
+    define('CURL_SSLVERSION_TLSv1_2', 6);
22
+}
23
+// @codingStandardsIgnoreEnd
24
+
25
+if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
+    define('CURL_HTTP_VERSION_2TLS', 4);
27
+}
28
+
29
+class CurlClient implements ClientInterface
30
+{
31
+    private static $instance;
32
+
33
+    public static function instance()
34
+    {
35
+        if (!self::$instance) {
36
+            self::$instance = new self();
37
+        }
38
+        return self::$instance;
39
+    }
40
+
41
+    protected $defaultOptions;
42
+
43
+    protected $userAgentInfo;
44
+
45
+    protected $enablePersistentConnections = null;
46
+
47
+    protected $enableHttp2 = null;
48
+
49
+    protected $curlHandle = null;
50
+
51
+    /**
52
+     * CurlClient constructor.
53
+     *
54
+     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
+     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
+     * provide a static set of options. Note that many options are overridden later in the request
57
+     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
+     *
59
+     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
+     * throw an exception if $defaultOptions returns a non-array value.
61
+     *
62
+     * @param array|callable|null $defaultOptions
63
+     */
64
+    public function __construct($defaultOptions = null, $randomGenerator = null)
65
+    {
66
+        $this->defaultOptions = $defaultOptions;
67
+        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
+        $this->initUserAgentInfo();
69
+
70
+        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
+        // initialize this to true.
72
+        $this->enablePersistentConnections = function_exists('curl_reset');
73
+
74
+        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
+    }
76
+
77
+    public function __destruct()
78
+    {
79
+        $this->closeCurlHandle();
80
+    }
81
+
82
+    public function initUserAgentInfo()
83
+    {
84
+        $curlVersion = curl_version();
85
+        $this->userAgentInfo = [
86
+            'httplib' =>  'curl ' . $curlVersion['version'],
87
+            'ssllib' => $curlVersion['ssl_version'],
88
+        ];
89
+    }
90
+
91
+    public function getDefaultOptions()
92
+    {
93
+        return $this->defaultOptions;
94
+    }
95
+
96
+    public function getUserAgentInfo()
97
+    {
98
+        return $this->userAgentInfo;
99
+    }
100
+
101
+    /**
102
+     * @return boolean
103
+     */
104
+    public function getEnablePersistentConnections()
105
+    {
106
+        return $this->enablePersistentConnections;
107
+    }
108
+
109
+    /**
110
+     * @param boolean $enable
111
+     */
112
+    public function setEnablePersistentConnections($enable)
113
+    {
114
+        $this->enablePersistentConnections = $enable;
115
+    }
116
+
117
+    /**
118
+     * @return boolean
119
+     */
120
+    public function getEnableHttp2()
121
+    {
122
+        return $this->enableHttp2;
123
+    }
124
+
125
+    /**
126
+     * @param boolean $enable
127
+     */
128
+    public function setEnableHttp2($enable)
129
+    {
130
+        $this->enableHttp2 = $enable;
131
+    }
132
+
133
+    // USER DEFINED TIMEOUTS
134
+
135
+    const DEFAULT_TIMEOUT = 80;
136
+    const DEFAULT_CONNECT_TIMEOUT = 30;
137
+
138
+    private $timeout = self::DEFAULT_TIMEOUT;
139
+    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
+
141
+    public function setTimeout($seconds)
142
+    {
143
+        $this->timeout = (int) max($seconds, 0);
144
+        return $this;
145
+    }
146
+
147
+    public function setConnectTimeout($seconds)
148
+    {
149
+        $this->connectTimeout = (int) max($seconds, 0);
150
+        return $this;
151
+    }
152
+
153
+    public function getTimeout()
154
+    {
155
+        return $this->timeout;
156
+    }
157
+
158
+    public function getConnectTimeout()
159
+    {
160
+        return $this->connectTimeout;
161
+    }
162
+
163
+    // END OF USER DEFINED TIMEOUTS
164
+
165
+    public function request($method, $absUrl, $headers, $params, $hasFile)
166
+    {
167
+        $method = strtolower($method);
168
+
169
+        $opts = [];
170
+        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
+            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
+            if (!is_array($opts)) {
173
+                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
+            }
175
+        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
+            $opts = $this->defaultOptions;
177
+        }
178
+
179
+        $params = Util\Util::objectsToIds($params);
180
+
181
+        if ($method == 'get') {
182
+            if ($hasFile) {
183
+                throw new Error\Api(
184
+                    "Issuing a GET request with a file parameter"
185
+                );
186
+            }
187
+            $opts[CURLOPT_HTTPGET] = 1;
188
+            if (count($params) > 0) {
189
+                $encoded = Util\Util::encodeParameters($params);
190
+                $absUrl = "$absUrl?$encoded";
191
+            }
192
+        } elseif ($method == 'post') {
193
+            $opts[CURLOPT_POST] = 1;
194
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
+        } elseif ($method == 'patch') {
196
+            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
+        } elseif ($method == 'delete') {
199
+            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
+            if (count($params) > 0) {
201
+                $encoded = Util\Util::encodeParameters($params);
202
+                $absUrl = "$absUrl?$encoded";
203
+            }
204
+        } else {
205
+            throw new Error\Api("Unrecognized method $method");
206
+        }
207
+
208
+        // It is only safe to retry network failures on POST requests if we
209
+        // add an Idempotency-Key header
210
+        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
+            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
+                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
+            }
214
+        }
215
+
216
+        // Create a callback to capture HTTP headers for the response
217
+        $rheaders = new Util\CaseInsensitiveArray();
218
+        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
+            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
+            if (strpos($header_line, ":") === false) {
221
+                return strlen($header_line);
222
+            }
223
+            list($key, $value) = explode(":", trim($header_line), 2);
224
+            $rheaders[trim($key)] = trim($value);
225
+            return strlen($header_line);
226
+        };
227
+
228
+        // By default for large request body sizes (> 1024 bytes), cURL will
229
+        // send a request without a body and with a `Expect: 100-continue`
230
+        // header, which gives the server a chance to respond with an error
231
+        // status code in cases where one can be determined right away (say
232
+        // on an authentication problem for example), and saves the "large"
233
+        // request body from being ever sent.
234
+        //
235
+        // Unfortunately, the bindings don't currently correctly handle the
236
+        // success case (in which the server sends back a 100 CONTINUE), so
237
+        // we'll error under that condition. To compensate for that problem
238
+        // for the time being, override cURL's behavior by simply always
239
+        // sending an empty `Expect:` header.
240
+        array_push($headers, 'Expect: ');
241
+
242
+        $absUrl = Util\Util::utf8($absUrl);
243
+        $opts[CURLOPT_URL] = $absUrl;
244
+        $opts[CURLOPT_RETURNTRANSFER] = true;
245
+        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
+        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
+        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
+        $opts[CURLOPT_HTTPHEADER] = $headers;
249
+        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
+        if (!Telnyx::getVerifySslCerts()) {
251
+            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
+        }
253
+
254
+        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
+            // For HTTPS requests, enable HTTP/2, if supported
256
+            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
+        }
258
+
259
+        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
+
261
+        return [$rbody, $rcode, $rheaders];
262
+    }
263
+
264
+    /**
265
+     * @param array $opts cURL options
266
+     */
267
+    private function executeRequestWithRetries($opts, $absUrl)
268
+    {
269
+        $numRetries = 0;
270
+
271
+        while (true) {
272
+            $rcode = 0;
273
+            $errno = 0;
274
+
275
+            $this->resetCurlHandle();
276
+            curl_setopt_array($this->curlHandle, $opts);
277
+            $rbody = curl_exec($this->curlHandle);
278
+
279
+            if ($rbody === false) {
280
+                $errno = curl_errno($this->curlHandle);
281
+                $message = curl_error($this->curlHandle);
282
+            } else {
283
+                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
+            }
285
+            if (!$this->getEnablePersistentConnections()) {
286
+                $this->closeCurlHandle();
287
+            }
288
+
289
+            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
+                $numRetries += 1;
291
+                $sleepSeconds = $this->sleepTime($numRetries);
292
+                usleep(intval($sleepSeconds * 1000000));
293
+            } else {
294
+                break;
295
+            }
296
+        }
297
+
298
+        if ($rbody === false) {
299
+            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
+        }
301
+
302
+        return [$rbody, $rcode];
303
+    }
304
+
305
+    /**
306
+     * @param string $url
307
+     * @param int $errno
308
+     * @param string $message
309
+     * @param int $numRetries
310
+     * @throws Error\ApiConnection
311
+     */
312
+    private function handleCurlError($url, $errno, $message, $numRetries)
313
+    {
314
+        switch ($errno) {
315
+            case CURLE_COULDNT_CONNECT:
316
+            case CURLE_COULDNT_RESOLVE_HOST:
317
+            case CURLE_OPERATION_TIMEOUTED:
318
+                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
+                 . "internet connection and try again.  If this problem persists, "
320
+                 . "you should check Telnyx's service status at "
321
+                 . "http://status.telnyx.com/, or";
322
+                break;
323
+            case CURLE_SSL_CACERT:
324
+            case CURLE_SSL_PEER_CERTIFICATE:
325
+                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
+                 . "that your network is not intercepting certificates.  "
327
+                 . "(Try going to $url in your browser.)  "
328
+                 . "If this problem persists,";
329
+                break;
330
+            default:
331
+                $msg = "Unexpected error communicating with Telnyx.  "
332
+                 . "If this problem persists,";
333
+        }
334
+        $msg .= " let us know at support@telnyx.com.";
335
+
336
+        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
+
338
+        if ($numRetries > 0) {
339
+            $msg .= "\n\nRequest was retried $numRetries times.";
340
+        }
341
+
342
+        throw new Error\ApiConnection($msg);
343
+    }
344
+
345
+    /**
346
+     * Checks if an error is a problem that we should retry on. This includes both
347
+     * socket errors that may represent an intermittent problem and some special
348
+     * HTTP statuses.
349
+     * @param int $errno
350
+     * @param int $rcode
351
+     * @param int $numRetries
352
+     * @return bool
353
+     */
354
+    private function shouldRetry($errno, $rcode, $numRetries)
355
+    {
356
+        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
+            return false;
358
+        }
359
+
360
+        // Retry on timeout-related problems (either on open or read).
361
+        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
+            return true;
363
+        }
364
+
365
+        // Destination refused the connection, the connection was reset, or a
366
+        // variety of other connection failures. This could occur from a single
367
+        // saturated server, so retry in case it's intermittent.
368
+        if ($errno === CURLE_COULDNT_CONNECT) {
369
+            return true;
370
+        }
371
+
372
+        // 409 conflict
373
+        if ($rcode === 409) {
374
+            return true;
375
+        }
376
+
377
+        return false;
378
+    }
379
+
380
+    private function sleepTime($numRetries)
381
+    {
382
+        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
+        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
+        // $maxNetworkRetryDelay.
385
+        $sleepSeconds = min(
386
+            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
+            Telnyx::getMaxNetworkRetryDelay()
388
+        );
389
+
390
+        // Apply some jitter by randomizing the value in the range of
391
+        // ($sleepSeconds / 2) to ($sleepSeconds).
392
+        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
+
394
+        // But never sleep less than the base sleep seconds.
395
+        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
+
397
+        return $sleepSeconds;
398
+    }
399
+
400
+    /**
401
+     * Initializes the curl handle. If already initialized, the handle is closed first.
402
+     */
403
+    private function initCurlHandle()
404
+    {
405
+        $this->closeCurlHandle();
406
+        $this->curlHandle = curl_init();
407
+    }
408
+
409
+    /**
410
+     * Closes the curl handle if initialized. Do nothing if already closed.
411
+     */
412
+    private function closeCurlHandle()
413
+    {
414
+        if (!is_null($this->curlHandle)) {
415
+            curl_close($this->curlHandle);
416
+            $this->curlHandle = null;
417
+        }
418
+    }
419
+
420
+    /**
421
+     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
+     * connections are disabled, the handle is reinitialized instead.
423
+     */
424
+    private function resetCurlHandle()
425
+    {
426
+        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
+            curl_reset($this->curlHandle);
428
+        } else {
429
+            $this->initCurlHandle();
430
+        }
431
+    }
432
+
433
+    /**
434
+     * Indicates whether it is safe to use HTTP/2 or not.
435
+     *
436
+     * @return boolean
437
+     */
438
+    private function canSafelyUseHttp2()
439
+    {
440
+        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
+        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
+        $curlVersion = curl_version()['version'];
443
+        return (version_compare($curlVersion, '7.60.0') >= 0);
444
+    }
445
+
446
+    /**
447
+     * Checks if a list of headers contains a specific header name.
448
+     *
449
+     * @param string[] $headers
450
+     * @param string $name
451
+     * @return boolean
452
+     */
453
+    private function hasHeader($headers, $name)
454
+    {
455
+        foreach ($headers as $header) {
456
+            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
+                return true;
458
+            }
459
+        }
460
+
461
+        return false;
462
+    }
463
+}
Browse code

removed Telnyx directory

DoubleBastionAdmin authored on 19/08/2022 11:12:03
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,463 +0,0 @@
1
-<?php
2
-
3
-namespace Telnyx\HttpClient;
4
-
5
-use Telnyx\Telnyx;
6
-use Telnyx\Error;
7
-use Telnyx\Util;
8
-
9
-// cURL constants are not defined in PHP < 5.5
10
-
11
-// @codingStandardsIgnoreStart
12
-// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
-// constants do not abide by those rules.
14
-
15
-// Note the values 1 and 6 come from their position in the enum that
16
-// defines them in cURL's source code.
17
-if (!defined('CURL_SSLVERSION_TLSv1')) {
18
-    define('CURL_SSLVERSION_TLSv1', 1);
19
-}
20
-if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
-    define('CURL_SSLVERSION_TLSv1_2', 6);
22
-}
23
-// @codingStandardsIgnoreEnd
24
-
25
-if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
-    define('CURL_HTTP_VERSION_2TLS', 4);
27
-}
28
-
29
-class CurlClient implements ClientInterface
30
-{
31
-    private static $instance;
32
-
33
-    public static function instance()
34
-    {
35
-        if (!self::$instance) {
36
-            self::$instance = new self();
37
-        }
38
-        return self::$instance;
39
-    }
40
-
41
-    protected $defaultOptions;
42
-
43
-    protected $userAgentInfo;
44
-
45
-    protected $enablePersistentConnections = null;
46
-
47
-    protected $enableHttp2 = null;
48
-
49
-    protected $curlHandle = null;
50
-
51
-    /**
52
-     * CurlClient constructor.
53
-     *
54
-     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
-     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
-     * provide a static set of options. Note that many options are overridden later in the request
57
-     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
-     *
59
-     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
-     * throw an exception if $defaultOptions returns a non-array value.
61
-     *
62
-     * @param array|callable|null $defaultOptions
63
-     */
64
-    public function __construct($defaultOptions = null, $randomGenerator = null)
65
-    {
66
-        $this->defaultOptions = $defaultOptions;
67
-        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
-        $this->initUserAgentInfo();
69
-
70
-        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
-        // initialize this to true.
72
-        $this->enablePersistentConnections = function_exists('curl_reset');
73
-
74
-        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
-    }
76
-
77
-    public function __destruct()
78
-    {
79
-        $this->closeCurlHandle();
80
-    }
81
-
82
-    public function initUserAgentInfo()
83
-    {
84
-        $curlVersion = curl_version();
85
-        $this->userAgentInfo = [
86
-            'httplib' =>  'curl ' . $curlVersion['version'],
87
-            'ssllib' => $curlVersion['ssl_version'],
88
-        ];
89
-    }
90
-
91
-    public function getDefaultOptions()
92
-    {
93
-        return $this->defaultOptions;
94
-    }
95
-
96
-    public function getUserAgentInfo()
97
-    {
98
-        return $this->userAgentInfo;
99
-    }
100
-
101
-    /**
102
-     * @return boolean
103
-     */
104
-    public function getEnablePersistentConnections()
105
-    {
106
-        return $this->enablePersistentConnections;
107
-    }
108
-
109
-    /**
110
-     * @param boolean $enable
111
-     */
112
-    public function setEnablePersistentConnections($enable)
113
-    {
114
-        $this->enablePersistentConnections = $enable;
115
-    }
116
-
117
-    /**
118
-     * @return boolean
119
-     */
120
-    public function getEnableHttp2()
121
-    {
122
-        return $this->enableHttp2;
123
-    }
124
-
125
-    /**
126
-     * @param boolean $enable
127
-     */
128
-    public function setEnableHttp2($enable)
129
-    {
130
-        $this->enableHttp2 = $enable;
131
-    }
132
-
133
-    // USER DEFINED TIMEOUTS
134
-
135
-    const DEFAULT_TIMEOUT = 80;
136
-    const DEFAULT_CONNECT_TIMEOUT = 30;
137
-
138
-    private $timeout = self::DEFAULT_TIMEOUT;
139
-    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
-
141
-    public function setTimeout($seconds)
142
-    {
143
-        $this->timeout = (int) max($seconds, 0);
144
-        return $this;
145
-    }
146
-
147
-    public function setConnectTimeout($seconds)
148
-    {
149
-        $this->connectTimeout = (int) max($seconds, 0);
150
-        return $this;
151
-    }
152
-
153
-    public function getTimeout()
154
-    {
155
-        return $this->timeout;
156
-    }
157
-
158
-    public function getConnectTimeout()
159
-    {
160
-        return $this->connectTimeout;
161
-    }
162
-
163
-    // END OF USER DEFINED TIMEOUTS
164
-
165
-    public function request($method, $absUrl, $headers, $params, $hasFile)
166
-    {
167
-        $method = strtolower($method);
168
-
169
-        $opts = [];
170
-        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
-            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
-            if (!is_array($opts)) {
173
-                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
-            }
175
-        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
-            $opts = $this->defaultOptions;
177
-        }
178
-
179
-        $params = Util\Util::objectsToIds($params);
180
-
181
-        if ($method == 'get') {
182
-            if ($hasFile) {
183
-                throw new Error\Api(
184
-                    "Issuing a GET request with a file parameter"
185
-                );
186
-            }
187
-            $opts[CURLOPT_HTTPGET] = 1;
188
-            if (count($params) > 0) {
189
-                $encoded = Util\Util::encodeParameters($params);
190
-                $absUrl = "$absUrl?$encoded";
191
-            }
192
-        } elseif ($method == 'post') {
193
-            $opts[CURLOPT_POST] = 1;
194
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
-        } elseif ($method == 'patch') {
196
-            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
-            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
-        } elseif ($method == 'delete') {
199
-            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
-            if (count($params) > 0) {
201
-                $encoded = Util\Util::encodeParameters($params);
202
-                $absUrl = "$absUrl?$encoded";
203
-            }
204
-        } else {
205
-            throw new Error\Api("Unrecognized method $method");
206
-        }
207
-
208
-        // It is only safe to retry network failures on POST requests if we
209
-        // add an Idempotency-Key header
210
-        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
-            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
-                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
-            }
214
-        }
215
-
216
-        // Create a callback to capture HTTP headers for the response
217
-        $rheaders = new Util\CaseInsensitiveArray();
218
-        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
-            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
-            if (strpos($header_line, ":") === false) {
221
-                return strlen($header_line);
222
-            }
223
-            list($key, $value) = explode(":", trim($header_line), 2);
224
-            $rheaders[trim($key)] = trim($value);
225
-            return strlen($header_line);
226
-        };
227
-
228
-        // By default for large request body sizes (> 1024 bytes), cURL will
229
-        // send a request without a body and with a `Expect: 100-continue`
230
-        // header, which gives the server a chance to respond with an error
231
-        // status code in cases where one can be determined right away (say
232
-        // on an authentication problem for example), and saves the "large"
233
-        // request body from being ever sent.
234
-        //
235
-        // Unfortunately, the bindings don't currently correctly handle the
236
-        // success case (in which the server sends back a 100 CONTINUE), so
237
-        // we'll error under that condition. To compensate for that problem
238
-        // for the time being, override cURL's behavior by simply always
239
-        // sending an empty `Expect:` header.
240
-        array_push($headers, 'Expect: ');
241
-
242
-        $absUrl = Util\Util::utf8($absUrl);
243
-        $opts[CURLOPT_URL] = $absUrl;
244
-        $opts[CURLOPT_RETURNTRANSFER] = true;
245
-        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
-        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
-        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
-        $opts[CURLOPT_HTTPHEADER] = $headers;
249
-        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
-        if (!Telnyx::getVerifySslCerts()) {
251
-            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
-        }
253
-
254
-        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
-            // For HTTPS requests, enable HTTP/2, if supported
256
-            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
-        }
258
-
259
-        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
-
261
-        return [$rbody, $rcode, $rheaders];
262
-    }
263
-
264
-    /**
265
-     * @param array $opts cURL options
266
-     */
267
-    private function executeRequestWithRetries($opts, $absUrl)
268
-    {
269
-        $numRetries = 0;
270
-
271
-        while (true) {
272
-            $rcode = 0;
273
-            $errno = 0;
274
-
275
-            $this->resetCurlHandle();
276
-            curl_setopt_array($this->curlHandle, $opts);
277
-            $rbody = curl_exec($this->curlHandle);
278
-
279
-            if ($rbody === false) {
280
-                $errno = curl_errno($this->curlHandle);
281
-                $message = curl_error($this->curlHandle);
282
-            } else {
283
-                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
-            }
285
-            if (!$this->getEnablePersistentConnections()) {
286
-                $this->closeCurlHandle();
287
-            }
288
-
289
-            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
-                $numRetries += 1;
291
-                $sleepSeconds = $this->sleepTime($numRetries);
292
-                usleep(intval($sleepSeconds * 1000000));
293
-            } else {
294
-                break;
295
-            }
296
-        }
297
-
298
-        if ($rbody === false) {
299
-            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
-        }
301
-
302
-        return [$rbody, $rcode];
303
-    }
304
-
305
-    /**
306
-     * @param string $url
307
-     * @param int $errno
308
-     * @param string $message
309
-     * @param int $numRetries
310
-     * @throws Error\ApiConnection
311
-     */
312
-    private function handleCurlError($url, $errno, $message, $numRetries)
313
-    {
314
-        switch ($errno) {
315
-            case CURLE_COULDNT_CONNECT:
316
-            case CURLE_COULDNT_RESOLVE_HOST:
317
-            case CURLE_OPERATION_TIMEOUTED:
318
-                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
-                 . "internet connection and try again.  If this problem persists, "
320
-                 . "you should check Telnyx's service status at "
321
-                 . "http://status.telnyx.com/, or";
322
-                break;
323
-            case CURLE_SSL_CACERT:
324
-            case CURLE_SSL_PEER_CERTIFICATE:
325
-                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
-                 . "that your network is not intercepting certificates.  "
327
-                 . "(Try going to $url in your browser.)  "
328
-                 . "If this problem persists,";
329
-                break;
330
-            default:
331
-                $msg = "Unexpected error communicating with Telnyx.  "
332
-                 . "If this problem persists,";
333
-        }
334
-        $msg .= " let us know at support@telnyx.com.";
335
-
336
-        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
-
338
-        if ($numRetries > 0) {
339
-            $msg .= "\n\nRequest was retried $numRetries times.";
340
-        }
341
-
342
-        throw new Error\ApiConnection($msg);
343
-    }
344
-
345
-    /**
346
-     * Checks if an error is a problem that we should retry on. This includes both
347
-     * socket errors that may represent an intermittent problem and some special
348
-     * HTTP statuses.
349
-     * @param int $errno
350
-     * @param int $rcode
351
-     * @param int $numRetries
352
-     * @return bool
353
-     */
354
-    private function shouldRetry($errno, $rcode, $numRetries)
355
-    {
356
-        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
-            return false;
358
-        }
359
-
360
-        // Retry on timeout-related problems (either on open or read).
361
-        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
-            return true;
363
-        }
364
-
365
-        // Destination refused the connection, the connection was reset, or a
366
-        // variety of other connection failures. This could occur from a single
367
-        // saturated server, so retry in case it's intermittent.
368
-        if ($errno === CURLE_COULDNT_CONNECT) {
369
-            return true;
370
-        }
371
-
372
-        // 409 conflict
373
-        if ($rcode === 409) {
374
-            return true;
375
-        }
376
-
377
-        return false;
378
-    }
379
-
380
-    private function sleepTime($numRetries)
381
-    {
382
-        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
-        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
-        // $maxNetworkRetryDelay.
385
-        $sleepSeconds = min(
386
-            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
-            Telnyx::getMaxNetworkRetryDelay()
388
-        );
389
-
390
-        // Apply some jitter by randomizing the value in the range of
391
-        // ($sleepSeconds / 2) to ($sleepSeconds).
392
-        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
-
394
-        // But never sleep less than the base sleep seconds.
395
-        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
-
397
-        return $sleepSeconds;
398
-    }
399
-
400
-    /**
401
-     * Initializes the curl handle. If already initialized, the handle is closed first.
402
-     */
403
-    private function initCurlHandle()
404
-    {
405
-        $this->closeCurlHandle();
406
-        $this->curlHandle = curl_init();
407
-    }
408
-
409
-    /**
410
-     * Closes the curl handle if initialized. Do nothing if already closed.
411
-     */
412
-    private function closeCurlHandle()
413
-    {
414
-        if (!is_null($this->curlHandle)) {
415
-            curl_close($this->curlHandle);
416
-            $this->curlHandle = null;
417
-        }
418
-    }
419
-
420
-    /**
421
-     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
-     * connections are disabled, the handle is reinitialized instead.
423
-     */
424
-    private function resetCurlHandle()
425
-    {
426
-        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
-            curl_reset($this->curlHandle);
428
-        } else {
429
-            $this->initCurlHandle();
430
-        }
431
-    }
432
-
433
-    /**
434
-     * Indicates whether it is safe to use HTTP/2 or not.
435
-     *
436
-     * @return boolean
437
-     */
438
-    private function canSafelyUseHttp2()
439
-    {
440
-        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
-        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
-        $curlVersion = curl_version()['version'];
443
-        return (version_compare($curlVersion, '7.60.0') >= 0);
444
-    }
445
-
446
-    /**
447
-     * Checks if a list of headers contains a specific header name.
448
-     *
449
-     * @param string[] $headers
450
-     * @param string $name
451
-     * @return boolean
452
-     */
453
-    private function hasHeader($headers, $name)
454
-    {
455
-        foreach ($headers as $header) {
456
-            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
-                return true;
458
-            }
459
-        }
460
-
461
-        return false;
462
-    }
463
-}
Browse code

Created repository.

DoubleBastionAdmin authored on 01/03/2022 23:47:00
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,463 @@
1
+<?php
2
+
3
+namespace Telnyx\HttpClient;
4
+
5
+use Telnyx\Telnyx;
6
+use Telnyx\Error;
7
+use Telnyx\Util;
8
+
9
+// cURL constants are not defined in PHP < 5.5
10
+
11
+// @codingStandardsIgnoreStart
12
+// PSR2 requires all constants be upper case. Sadly, the CURL_SSLVERSION
13
+// constants do not abide by those rules.
14
+
15
+// Note the values 1 and 6 come from their position in the enum that
16
+// defines them in cURL's source code.
17
+if (!defined('CURL_SSLVERSION_TLSv1')) {
18
+    define('CURL_SSLVERSION_TLSv1', 1);
19
+}
20
+if (!defined('CURL_SSLVERSION_TLSv1_2')) {
21
+    define('CURL_SSLVERSION_TLSv1_2', 6);
22
+}
23
+// @codingStandardsIgnoreEnd
24
+
25
+if (!defined('CURL_HTTP_VERSION_2TLS')) {
26
+    define('CURL_HTTP_VERSION_2TLS', 4);
27
+}
28
+
29
+class CurlClient implements ClientInterface
30
+{
31
+    private static $instance;
32
+
33
+    public static function instance()
34
+    {
35
+        if (!self::$instance) {
36
+            self::$instance = new self();
37
+        }
38
+        return self::$instance;
39
+    }
40
+
41
+    protected $defaultOptions;
42
+
43
+    protected $userAgentInfo;
44
+
45
+    protected $enablePersistentConnections = null;
46
+
47
+    protected $enableHttp2 = null;
48
+
49
+    protected $curlHandle = null;
50
+
51
+    /**
52
+     * CurlClient constructor.
53
+     *
54
+     * Pass in a callable to $defaultOptions that returns an array of CURLOPT_* values to start
55
+     * off a request with, or an flat array with the same format used by curl_setopt_array() to
56
+     * provide a static set of options. Note that many options are overridden later in the request
57
+     * call, including timeouts, which can be set via setTimeout() and setConnectTimeout().
58
+     *
59
+     * Note that request() will silently ignore a non-callable, non-array $defaultOptions, and will
60
+     * throw an exception if $defaultOptions returns a non-array value.
61
+     *
62
+     * @param array|callable|null $defaultOptions
63
+     */
64
+    public function __construct($defaultOptions = null, $randomGenerator = null)
65
+    {
66
+        $this->defaultOptions = $defaultOptions;
67
+        $this->randomGenerator = $randomGenerator ?: new Util\RandomGenerator();
68
+        $this->initUserAgentInfo();
69
+
70
+        // TODO: curl_reset requires PHP >= 5.5.0. Once we drop support for PHP 5.4, we can simply
71
+        // initialize this to true.
72
+        $this->enablePersistentConnections = function_exists('curl_reset');
73
+
74
+        $this->enableHttp2 = $this->canSafelyUseHttp2();
75
+    }
76
+
77
+    public function __destruct()
78
+    {
79
+        $this->closeCurlHandle();
80
+    }
81
+
82
+    public function initUserAgentInfo()
83
+    {
84
+        $curlVersion = curl_version();
85
+        $this->userAgentInfo = [
86
+            'httplib' =>  'curl ' . $curlVersion['version'],
87
+            'ssllib' => $curlVersion['ssl_version'],
88
+        ];
89
+    }
90
+
91
+    public function getDefaultOptions()
92
+    {
93
+        return $this->defaultOptions;
94
+    }
95
+
96
+    public function getUserAgentInfo()
97
+    {
98
+        return $this->userAgentInfo;
99
+    }
100
+
101
+    /**
102
+     * @return boolean
103
+     */
104
+    public function getEnablePersistentConnections()
105
+    {
106
+        return $this->enablePersistentConnections;
107
+    }
108
+
109
+    /**
110
+     * @param boolean $enable
111
+     */
112
+    public function setEnablePersistentConnections($enable)
113
+    {
114
+        $this->enablePersistentConnections = $enable;
115
+    }
116
+
117
+    /**
118
+     * @return boolean
119
+     */
120
+    public function getEnableHttp2()
121
+    {
122
+        return $this->enableHttp2;
123
+    }
124
+
125
+    /**
126
+     * @param boolean $enable
127
+     */
128
+    public function setEnableHttp2($enable)
129
+    {
130
+        $this->enableHttp2 = $enable;
131
+    }
132
+
133
+    // USER DEFINED TIMEOUTS
134
+
135
+    const DEFAULT_TIMEOUT = 80;
136
+    const DEFAULT_CONNECT_TIMEOUT = 30;
137
+
138
+    private $timeout = self::DEFAULT_TIMEOUT;
139
+    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
140
+
141
+    public function setTimeout($seconds)
142
+    {
143
+        $this->timeout = (int) max($seconds, 0);
144
+        return $this;
145
+    }
146
+
147
+    public function setConnectTimeout($seconds)
148
+    {
149
+        $this->connectTimeout = (int) max($seconds, 0);
150
+        return $this;
151
+    }
152
+
153
+    public function getTimeout()
154
+    {
155
+        return $this->timeout;
156
+    }
157
+
158
+    public function getConnectTimeout()
159
+    {
160
+        return $this->connectTimeout;
161
+    }
162
+
163
+    // END OF USER DEFINED TIMEOUTS
164
+
165
+    public function request($method, $absUrl, $headers, $params, $hasFile)
166
+    {
167
+        $method = strtolower($method);
168
+
169
+        $opts = [];
170
+        if (is_callable($this->defaultOptions)) { // call defaultOptions callback, set options to return value
171
+            $opts = call_user_func_array($this->defaultOptions, func_get_args());
172
+            if (!is_array($opts)) {
173
+                throw new Error\Api("Non-array value returned by defaultOptions CurlClient callback");
174
+            }
175
+        } elseif (is_array($this->defaultOptions)) { // set default curlopts from array
176
+            $opts = $this->defaultOptions;
177
+        }
178
+
179
+        $params = Util\Util::objectsToIds($params);
180
+
181
+        if ($method == 'get') {
182
+            if ($hasFile) {
183
+                throw new Error\Api(
184
+                    "Issuing a GET request with a file parameter"
185
+                );
186
+            }
187
+            $opts[CURLOPT_HTTPGET] = 1;
188
+            if (count($params) > 0) {
189
+                $encoded = Util\Util::encodeParameters($params);
190
+                $absUrl = "$absUrl?$encoded";
191
+            }
192
+        } elseif ($method == 'post') {
193
+            $opts[CURLOPT_POST] = 1;
194
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
195
+        } elseif ($method == 'patch') {
196
+            $opts[CURLOPT_CUSTOMREQUEST] = 'PATCH';
197
+            $opts[CURLOPT_POSTFIELDS] = $hasFile ? $params : json_encode($params);
198
+        } elseif ($method == 'delete') {
199
+            $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
200
+            if (count($params) > 0) {
201
+                $encoded = Util\Util::encodeParameters($params);
202
+                $absUrl = "$absUrl?$encoded";
203
+            }
204
+        } else {
205
+            throw new Error\Api("Unrecognized method $method");
206
+        }
207
+
208
+        // It is only safe to retry network failures on POST requests if we
209
+        // add an Idempotency-Key header
210
+        if (($method == 'post') && (Telnyx::$maxNetworkRetries > 0)) {
211
+            if (!$this->hasHeader($headers, "Idempotency-Key")) {
212
+                array_push($headers, 'Idempotency-Key: ' . $this->randomGenerator->uuid());
213
+            }
214
+        }
215
+
216
+        // Create a callback to capture HTTP headers for the response
217
+        $rheaders = new Util\CaseInsensitiveArray();
218
+        $headerCallback = function ($curl, $header_line) use (&$rheaders) {
219
+            // Ignore the HTTP request line (HTTP/1.1 200 OK)
220
+            if (strpos($header_line, ":") === false) {
221
+                return strlen($header_line);
222
+            }
223
+            list($key, $value) = explode(":", trim($header_line), 2);
224
+            $rheaders[trim($key)] = trim($value);
225
+            return strlen($header_line);
226
+        };
227
+
228
+        // By default for large request body sizes (> 1024 bytes), cURL will
229
+        // send a request without a body and with a `Expect: 100-continue`
230
+        // header, which gives the server a chance to respond with an error
231
+        // status code in cases where one can be determined right away (say
232
+        // on an authentication problem for example), and saves the "large"
233
+        // request body from being ever sent.
234
+        //
235
+        // Unfortunately, the bindings don't currently correctly handle the
236
+        // success case (in which the server sends back a 100 CONTINUE), so
237
+        // we'll error under that condition. To compensate for that problem
238
+        // for the time being, override cURL's behavior by simply always
239
+        // sending an empty `Expect:` header.
240
+        array_push($headers, 'Expect: ');
241
+
242
+        $absUrl = Util\Util::utf8($absUrl);
243
+        $opts[CURLOPT_URL] = $absUrl;
244
+        $opts[CURLOPT_RETURNTRANSFER] = true;
245
+        $opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
246
+        $opts[CURLOPT_TIMEOUT] = $this->timeout;
247
+        $opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
248
+        $opts[CURLOPT_HTTPHEADER] = $headers;
249
+        $opts[CURLOPT_CAINFO] = Telnyx::getCABundlePath();
250
+        if (!Telnyx::getVerifySslCerts()) {
251
+            $opts[CURLOPT_SSL_VERIFYPEER] = false;
252
+        }
253
+
254
+        if (!isset($opts[CURLOPT_HTTP_VERSION]) && $this->getEnableHttp2()) {
255
+            // For HTTPS requests, enable HTTP/2, if supported
256
+            $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2TLS;
257
+        }
258
+
259
+        list($rbody, $rcode) = $this->executeRequestWithRetries($opts, $absUrl);
260
+
261
+        return [$rbody, $rcode, $rheaders];
262
+    }
263
+
264
+    /**
265
+     * @param array $opts cURL options
266
+     */
267
+    private function executeRequestWithRetries($opts, $absUrl)
268
+    {
269
+        $numRetries = 0;
270
+
271
+        while (true) {
272
+            $rcode = 0;
273
+            $errno = 0;
274
+
275
+            $this->resetCurlHandle();
276
+            curl_setopt_array($this->curlHandle, $opts);
277
+            $rbody = curl_exec($this->curlHandle);
278
+
279
+            if ($rbody === false) {
280
+                $errno = curl_errno($this->curlHandle);
281
+                $message = curl_error($this->curlHandle);
282
+            } else {
283
+                $rcode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
284
+            }
285
+            if (!$this->getEnablePersistentConnections()) {
286
+                $this->closeCurlHandle();
287
+            }
288
+
289
+            if ($this->shouldRetry($errno, $rcode, $numRetries)) {
290
+                $numRetries += 1;
291
+                $sleepSeconds = $this->sleepTime($numRetries);
292
+                usleep(intval($sleepSeconds * 1000000));
293
+            } else {
294
+                break;
295
+            }
296
+        }
297
+
298
+        if ($rbody === false) {
299
+            $this->handleCurlError($absUrl, $errno, $message, $numRetries);
300
+        }
301
+
302
+        return [$rbody, $rcode];
303
+    }
304
+
305
+    /**
306
+     * @param string $url
307
+     * @param int $errno
308
+     * @param string $message
309
+     * @param int $numRetries
310
+     * @throws Error\ApiConnection
311
+     */
312
+    private function handleCurlError($url, $errno, $message, $numRetries)
313
+    {
314
+        switch ($errno) {
315
+            case CURLE_COULDNT_CONNECT:
316
+            case CURLE_COULDNT_RESOLVE_HOST:
317
+            case CURLE_OPERATION_TIMEOUTED:
318
+                $msg = "Could not connect to Telnyx ($url).  Please check your "
319
+                 . "internet connection and try again.  If this problem persists, "
320
+                 . "you should check Telnyx's service status at "
321
+                 . "http://status.telnyx.com/, or";
322
+                break;
323
+            case CURLE_SSL_CACERT:
324
+            case CURLE_SSL_PEER_CERTIFICATE:
325
+                $msg = "Could not verify Telnyx's SSL certificate.  Please make sure "
326
+                 . "that your network is not intercepting certificates.  "
327
+                 . "(Try going to $url in your browser.)  "
328
+                 . "If this problem persists,";
329
+                break;
330
+            default:
331
+                $msg = "Unexpected error communicating with Telnyx.  "
332
+                 . "If this problem persists,";
333
+        }
334
+        $msg .= " let us know at support@telnyx.com.";
335
+
336
+        $msg .= "\n\n(Network error [errno $errno]: $message)";
337
+
338
+        if ($numRetries > 0) {
339
+            $msg .= "\n\nRequest was retried $numRetries times.";
340
+        }
341
+
342
+        throw new Error\ApiConnection($msg);
343
+    }
344
+
345
+    /**
346
+     * Checks if an error is a problem that we should retry on. This includes both
347
+     * socket errors that may represent an intermittent problem and some special
348
+     * HTTP statuses.
349
+     * @param int $errno
350
+     * @param int $rcode
351
+     * @param int $numRetries
352
+     * @return bool
353
+     */
354
+    private function shouldRetry($errno, $rcode, $numRetries)
355
+    {
356
+        if ($numRetries >= Telnyx::getMaxNetworkRetries()) {
357
+            return false;
358
+        }
359
+
360
+        // Retry on timeout-related problems (either on open or read).
361
+        if ($errno === CURLE_OPERATION_TIMEOUTED) {
362
+            return true;
363
+        }
364
+
365
+        // Destination refused the connection, the connection was reset, or a
366
+        // variety of other connection failures. This could occur from a single
367
+        // saturated server, so retry in case it's intermittent.
368
+        if ($errno === CURLE_COULDNT_CONNECT) {
369
+            return true;
370
+        }
371
+
372
+        // 409 conflict
373
+        if ($rcode === 409) {
374
+            return true;
375
+        }
376
+
377
+        return false;
378
+    }
379
+
380
+    private function sleepTime($numRetries)
381
+    {
382
+        // Apply exponential backoff with $initialNetworkRetryDelay on the
383
+        // number of $numRetries so far as inputs. Do not allow the number to exceed
384
+        // $maxNetworkRetryDelay.
385
+        $sleepSeconds = min(
386
+            Telnyx::getInitialNetworkRetryDelay() * 1.0 * pow(2, $numRetries - 1),
387
+            Telnyx::getMaxNetworkRetryDelay()
388
+        );
389
+
390
+        // Apply some jitter by randomizing the value in the range of
391
+        // ($sleepSeconds / 2) to ($sleepSeconds).
392
+        $sleepSeconds *= 0.5 * (1 + $this->randomGenerator->randFloat());
393
+
394
+        // But never sleep less than the base sleep seconds.
395
+        $sleepSeconds = max(Telnyx::getInitialNetworkRetryDelay(), $sleepSeconds);
396
+
397
+        return $sleepSeconds;
398
+    }
399
+
400
+    /**
401
+     * Initializes the curl handle. If already initialized, the handle is closed first.
402
+     */
403
+    private function initCurlHandle()
404
+    {
405
+        $this->closeCurlHandle();
406
+        $this->curlHandle = curl_init();
407
+    }
408
+
409
+    /**
410
+     * Closes the curl handle if initialized. Do nothing if already closed.
411
+     */
412
+    private function closeCurlHandle()
413
+    {
414
+        if (!is_null($this->curlHandle)) {
415
+            curl_close($this->curlHandle);
416
+            $this->curlHandle = null;
417
+        }
418
+    }
419
+
420
+    /**
421
+     * Resets the curl handle. If the handle is not already initialized, or if persistent
422
+     * connections are disabled, the handle is reinitialized instead.
423
+     */
424
+    private function resetCurlHandle()
425
+    {
426
+        if (!is_null($this->curlHandle) && $this->getEnablePersistentConnections()) {
427
+            curl_reset($this->curlHandle);
428
+        } else {
429
+            $this->initCurlHandle();
430
+        }
431
+    }
432
+
433
+    /**
434
+     * Indicates whether it is safe to use HTTP/2 or not.
435
+     *
436
+     * @return boolean
437
+     */
438
+    private function canSafelyUseHttp2()
439
+    {
440
+        // Versions of curl older than 7.60.0 don't respect GOAWAY frames
441
+        // (cf. https://github.com/curl/curl/issues/2416), which Telnyx use.
442
+        $curlVersion = curl_version()['version'];
443
+        return (version_compare($curlVersion, '7.60.0') >= 0);
444
+    }
445
+
446
+    /**
447
+     * Checks if a list of headers contains a specific header name.
448
+     *
449
+     * @param string[] $headers
450
+     * @param string $name
451
+     * @return boolean
452
+     */
453
+    private function hasHeader($headers, $name)
454
+    {
455
+        foreach ($headers as $header) {
456
+            if (strncasecmp($header, "{$name}: ", strlen($name) + 2) === 0) {
457
+                return true;
458
+            }
459
+        }
460
+
461
+        return false;
462
+    }
463
+}