1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
<?php
declare(strict_types=1);
namespace Cepis;
/**
* The default {@see Transport}, built on ext-curl.
*
* curl is used directly rather than through an HTTP abstraction: this library
* makes one GET, and a wrapper for that would cost dependencies without buying
* anything. Hosts that need their own HTTP layer implement {@see Transport}
* instead.
*
* Each call opens its own handle, so one instance is safe to share.
*/
final class CurlTransport implements Transport
{
/**
* @param bool $verifyTls Whether to verify the peer's certificate. Leave
* it alone. It exists for a lab against a self-signed local instance,
* and turning it off on the public API removes the only thing making
* the answer trustworthy.
* @param int $maxRedirects How many redirects to follow.
*/
public function __construct(
public readonly bool $verifyTls = true,
public readonly int $maxRedirects = 3,
) {
}
public function get(string $url, array $headers, float $timeout): Response
{
$handle = curl_init();
if ($handle === false) {
throw new NetworkException('could not initialise a curl handle');
}
$lines = [];
foreach ($headers as $name => $value) {
$lines[] = $name . ': ' . $value;
}
$milliseconds = max(1, (int) round($timeout * 1000));
$received = [];
curl_setopt_array($handle, [
CURLOPT_URL => $url,
CURLOPT_HTTPGET => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $lines,
CURLOPT_CONNECTTIMEOUT_MS => $milliseconds,
CURLOPT_TIMEOUT_MS => $milliseconds,
CURLOPT_FOLLOWLOCATION => $this->maxRedirects > 0,
CURLOPT_MAXREDIRS => $this->maxRedirects,
CURLOPT_SSL_VERIFYPEER => $this->verifyTls,
CURLOPT_SSL_VERIFYHOST => $this->verifyTls ? 2 : 0,
CURLOPT_NOSIGNAL => true,
CURLOPT_HEADERFUNCTION => static function ($_handle, string $line) use (&$received): int {
$length = strlen($line);
$parts = explode(':', $line, 2);
if (count($parts) === 2) {
$received[trim($parts[0])][] = trim($parts[1]);
}
return $length;
},
]);
$body = curl_exec($handle);
$error = curl_error($handle);
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
unset($handle);
if ($body === false || $status === 0) {
throw new NetworkException(sprintf(
'could not reach %s: %s',
$url,
$error !== '' ? $error : 'the request did not complete',
));
}
return new Response($status, $received, (string) $body);
}
}
|