-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCurlHttpClient.php
More file actions
126 lines (102 loc) · 3.87 KB
/
Copy pathCurlHttpClient.php
File metadata and controls
126 lines (102 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
declare(strict_types=1);
namespace TestingBot\Http;
use TestingBot\Configuration\ClientConfig;
use TestingBot\Exception\NetworkException;
/**
* cURL-backed transport. This is the only class in the library that talks to
* cURL directly: it centralises authentication, timeouts, TLS verification,
* the User-Agent, body encoding and transport-level error handling.
*/
final class CurlHttpClient implements HttpClientInterface
{
public function __construct(private readonly ClientConfig $config)
{
}
public function send(Request $request): Response
{
$handle = curl_init();
/** @var array<string, string> $responseHeaders */
$responseHeaders = [];
$options = [
CURLOPT_URL => $this->buildUrl($request),
CURLOPT_CUSTOMREQUEST => $request->method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $this->config->connectTimeout,
CURLOPT_TIMEOUT => $this->config->timeout,
CURLOPT_SSL_VERIFYPEER => $this->config->sslVerify,
CURLOPT_SSL_VERIFYHOST => $this->config->sslVerify ? 2 : 0,
CURLOPT_USERAGENT => $this->config->userAgent,
CURLOPT_HTTPHEADER => $this->buildHeaders($request),
CURLOPT_HEADERFUNCTION => function ($_handle, string $header) use (&$responseHeaders): int {
$parts = explode(':', $header, 2);
if (count($parts) === 2) {
$responseHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
}
return strlen($header);
},
];
if ($request->requiresAuth) {
$options[CURLOPT_USERPWD] = $this->config->key . ':' . $this->config->secret;
}
$body = $this->resolveBody($request->body);
if ($body !== null) {
$options[CURLOPT_POSTFIELDS] = $body;
}
curl_setopt_array($handle, $options);
$rawBody = curl_exec($handle);
if ($rawBody === false || curl_errno($handle) !== 0) {
// The CurlHandle object is freed automatically when it goes out of
// scope; curl_close() has been a no-op since PHP 8.0.
throw new NetworkException(
sprintf('Request to %s failed: %s', $request->path, curl_error($handle)),
curl_errno($handle),
);
}
/** @var int $status */
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
return new Response($status, is_string($rawBody) ? $rawBody : '', $responseHeaders);
}
private function buildUrl(Request $request): string
{
$url = $this->config->baseUrl . ltrim($request->path, '/');
if ($request->query !== []) {
$url .= '?' . http_build_query($request->query);
}
return $url;
}
/**
* @return list<string>
*/
private function buildHeaders(Request $request): array
{
$headers = ['Accept: application/json'];
foreach ($request->headers as $name => $value) {
$headers[] = $name . ': ' . $value;
}
return $headers;
}
/**
* Encode the request body for cURL:
* - array -> form-urlencoded string (application/x-www-form-urlencoded)
* - Multipart -> field array (cURL switches to multipart/form-data)
* - string -> sent verbatim
* - null -> no body
*
* @param array<string, mixed>|string|Multipart|null $body
* @return array<string, \CURLFile|string>|string|null
*/
private function resolveBody(array|string|Multipart|null $body): array|string|null
{
if ($body === null) {
return null;
}
if ($body instanceof Multipart) {
return $body->getFields();
}
if (is_array($body)) {
return http_build_query($body);
}
return $body;
}
}