summaryrefslogtreecommitdiffstats
path: root/src/CurlTransport.php
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 14:58:55 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 14:58:55 -0300
commit64764fb9fac0227f8e0443e7c7519d73aec275b9 (patch)
treecb4fc56eeb6ecce53854f9c530609cf01ab3349f /src/CurlTransport.php
downloadphp-0.1.0.tar.gz
Initial importv0.1.0
Diffstat (limited to 'src/CurlTransport.php')
-rw-r--r--src/CurlTransport.php85
1 files changed, 85 insertions, 0 deletions
diff --git a/src/CurlTransport.php b/src/CurlTransport.php
new file mode 100644
index 0000000..d22949b
--- /dev/null
+++ b/src/CurlTransport.php
@@ -0,0 +1,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);
+ }
+}