Skip to content

Commit 94ae7e2

Browse files
CopilotThavarshan
andcommitted
Add connection pooling and HTTP/2 support implementation
Co-authored-by: Thavarshan <10804999+Thavarshan@users.noreply.github.com>
1 parent 0cce693 commit 94ae7e2

12 files changed

Lines changed: 2165 additions & 1 deletion
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fetch\Concerns;
6+
7+
use Fetch\Interfaces\ClientHandler;
8+
use Fetch\Pool\ConnectionPool;
9+
use Fetch\Pool\DnsCache;
10+
use Fetch\Pool\Http2Configuration;
11+
use Fetch\Pool\PoolConfiguration;
12+
13+
/**
14+
* Trait for managing connection pooling and HTTP/2 support.
15+
*/
16+
trait ManagesConnectionPool
17+
{
18+
/**
19+
* The connection pool instance.
20+
*/
21+
protected static ?ConnectionPool $connectionPool = null;
22+
23+
/**
24+
* The DNS cache instance.
25+
*/
26+
protected static ?DnsCache $dnsCache = null;
27+
28+
/**
29+
* The HTTP/2 configuration.
30+
*/
31+
protected ?Http2Configuration $http2Config = null;
32+
33+
/**
34+
* Whether connection pooling is enabled for this handler.
35+
*/
36+
protected bool $poolingEnabled = false;
37+
38+
/**
39+
* Configure connection pooling for this handler.
40+
*
41+
* @param array<string, mixed>|bool $config Pool configuration or boolean to enable/disable
42+
* @return $this
43+
*/
44+
public function withConnectionPool(array|bool $config = true): ClientHandler
45+
{
46+
if (is_bool($config)) {
47+
$this->poolingEnabled = $config;
48+
49+
return $this;
50+
}
51+
52+
$this->poolingEnabled = true;
53+
54+
// Initialize or update the global pool
55+
self::$connectionPool = ConnectionPool::fromArray($config);
56+
57+
// Initialize DNS cache if TTL is specified
58+
if (isset($config['dns_cache_ttl'])) {
59+
self::$dnsCache = new DnsCache((int) $config['dns_cache_ttl']);
60+
}
61+
62+
return $this;
63+
}
64+
65+
/**
66+
* Configure HTTP/2 for this handler.
67+
*
68+
* @param array<string, mixed>|bool $config HTTP/2 configuration or boolean to enable/disable
69+
* @return $this
70+
*/
71+
public function withHttp2(array|bool $config = true): ClientHandler
72+
{
73+
if (is_bool($config)) {
74+
$this->http2Config = new Http2Configuration(enabled: $config);
75+
} else {
76+
$this->http2Config = Http2Configuration::fromArray($config);
77+
}
78+
79+
// Apply HTTP/2 curl options to the handler options
80+
if ($this->http2Config->isEnabled()) {
81+
$curlOptions = $this->http2Config->getCurlOptions();
82+
if (! empty($curlOptions)) {
83+
$existingCurl = $this->options['curl'] ?? [];
84+
// Use + operator to preserve integer keys
85+
$this->options['curl'] = $existingCurl + $curlOptions;
86+
}
87+
88+
// Set HTTP version in options
89+
$this->options['version'] = 2.0;
90+
}
91+
92+
return $this;
93+
}
94+
95+
/**
96+
* Get the connection pool instance.
97+
*
98+
* @return ConnectionPool|null The pool instance or null if not configured
99+
*/
100+
public function getConnectionPool(): ?ConnectionPool
101+
{
102+
return self::$connectionPool;
103+
}
104+
105+
/**
106+
* Get the DNS cache instance.
107+
*
108+
* @return DnsCache|null The DNS cache or null if not configured
109+
*/
110+
public function getDnsCache(): ?DnsCache
111+
{
112+
return self::$dnsCache;
113+
}
114+
115+
/**
116+
* Get the HTTP/2 configuration.
117+
*
118+
* @return Http2Configuration|null The HTTP/2 config or null if not configured
119+
*/
120+
public function getHttp2Config(): ?Http2Configuration
121+
{
122+
return $this->http2Config;
123+
}
124+
125+
/**
126+
* Check if connection pooling is enabled.
127+
*/
128+
public function isPoolingEnabled(): bool
129+
{
130+
return $this->poolingEnabled && self::$connectionPool !== null && self::$connectionPool->isEnabled();
131+
}
132+
133+
/**
134+
* Check if HTTP/2 is enabled.
135+
*/
136+
public function isHttp2Enabled(): bool
137+
{
138+
return $this->http2Config !== null && $this->http2Config->isEnabled();
139+
}
140+
141+
/**
142+
* Get connection pool statistics.
143+
*
144+
* @return array<string, mixed>
145+
*/
146+
public function getPoolStats(): array
147+
{
148+
if (self::$connectionPool === null) {
149+
return ['enabled' => false];
150+
}
151+
152+
return self::$connectionPool->getStats();
153+
}
154+
155+
/**
156+
* Get DNS cache statistics.
157+
*
158+
* @return array<string, mixed>
159+
*/
160+
public function getDnsCacheStats(): array
161+
{
162+
if (self::$dnsCache === null) {
163+
return ['enabled' => false];
164+
}
165+
166+
return array_merge(['enabled' => true], self::$dnsCache->getStats());
167+
}
168+
169+
/**
170+
* Clear the DNS cache.
171+
*
172+
* @param string|null $hostname Specific hostname to clear, or null for all
173+
* @return $this
174+
*/
175+
public function clearDnsCache(?string $hostname = null): ClientHandler
176+
{
177+
if (self::$dnsCache !== null) {
178+
self::$dnsCache->clear($hostname);
179+
}
180+
181+
return $this;
182+
}
183+
184+
/**
185+
* Close all pooled connections.
186+
*
187+
* @return $this
188+
*/
189+
public function closeAllConnections(): ClientHandler
190+
{
191+
if (self::$connectionPool !== null) {
192+
self::$connectionPool->closeAll();
193+
}
194+
195+
return $this;
196+
}
197+
198+
/**
199+
* Reset the global connection pool and DNS cache.
200+
*
201+
* @return $this
202+
*/
203+
public function resetPool(): ClientHandler
204+
{
205+
if (self::$connectionPool !== null) {
206+
self::$connectionPool->closeAll();
207+
}
208+
self::$connectionPool = null;
209+
self::$dnsCache = null;
210+
$this->poolingEnabled = false;
211+
212+
return $this;
213+
}
214+
215+
/**
216+
* Initialize connection pooling with default configuration.
217+
*
218+
* @param PoolConfiguration|null $config Optional pool configuration
219+
*/
220+
protected static function initializePool(?PoolConfiguration $config = null): void
221+
{
222+
if (self::$connectionPool === null) {
223+
$poolConfig = $config ?? new PoolConfiguration;
224+
self::$connectionPool = new ConnectionPool($poolConfig);
225+
self::$dnsCache = new DnsCache($poolConfig->getDnsCacheTtl());
226+
}
227+
}
228+
229+
/**
230+
* Get cURL options for HTTP/2 support.
231+
*
232+
* @return array<int, mixed>
233+
*/
234+
protected function getHttp2CurlOptions(): array
235+
{
236+
if ($this->http2Config === null) {
237+
return [];
238+
}
239+
240+
return $this->http2Config->getCurlOptions();
241+
}
242+
243+
/**
244+
* Resolve a hostname using the DNS cache.
245+
*
246+
* @param string $hostname The hostname to resolve
247+
* @return string|null The resolved IP address or null
248+
*/
249+
protected function resolveHostname(string $hostname): ?string
250+
{
251+
if (self::$dnsCache === null) {
252+
return null;
253+
}
254+
255+
try {
256+
return self::$dnsCache->resolveFirst($hostname);
257+
} catch (\Throwable) {
258+
return null;
259+
}
260+
}
261+
}

src/Fetch/Http/ClientHandler.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Fetch\Concerns\ConfiguresRequests;
88
use Fetch\Concerns\HandlesMocking;
99
use Fetch\Concerns\HandlesUris;
10+
use Fetch\Concerns\ManagesConnectionPool;
1011
use Fetch\Concerns\ManagesDebugAndProfiling;
1112
use Fetch\Concerns\ManagesPromises;
1213
use Fetch\Concerns\ManagesRetries;
@@ -29,6 +30,7 @@ class ClientHandler implements ClientHandlerInterface
2930
use ConfiguresRequests,
3031
HandlesMocking,
3132
HandlesUris,
33+
ManagesConnectionPool,
3234
ManagesDebugAndProfiling,
3335
ManagesPromises,
3436
ManagesRetries,

src/Fetch/Interfaces/ClientHandler.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,87 @@ public function getDebugOptions(): array;
573573
* Get the last debug info from the most recent request.
574574
*/
575575
public function getLastDebugInfo(): ?\Fetch\Support\DebugInfo;
576+
577+
/**
578+
* Configure connection pooling for this handler.
579+
*
580+
* @param array<string, mixed>|bool $config Pool configuration or boolean to enable/disable
581+
* @return $this
582+
*/
583+
public function withConnectionPool(array|bool $config = true): self;
584+
585+
/**
586+
* Configure HTTP/2 for this handler.
587+
*
588+
* @param array<string, mixed>|bool $config HTTP/2 configuration or boolean to enable/disable
589+
* @return $this
590+
*/
591+
public function withHttp2(array|bool $config = true): self;
592+
593+
/**
594+
* Get the connection pool instance.
595+
*
596+
* @return \Fetch\Pool\ConnectionPool|null The pool instance or null if not configured
597+
*/
598+
public function getConnectionPool(): ?\Fetch\Pool\ConnectionPool;
599+
600+
/**
601+
* Get the DNS cache instance.
602+
*
603+
* @return \Fetch\Pool\DnsCache|null The DNS cache or null if not configured
604+
*/
605+
public function getDnsCache(): ?\Fetch\Pool\DnsCache;
606+
607+
/**
608+
* Get the HTTP/2 configuration.
609+
*
610+
* @return \Fetch\Pool\Http2Configuration|null The HTTP/2 config or null if not configured
611+
*/
612+
public function getHttp2Config(): ?\Fetch\Pool\Http2Configuration;
613+
614+
/**
615+
* Check if connection pooling is enabled.
616+
*/
617+
public function isPoolingEnabled(): bool;
618+
619+
/**
620+
* Check if HTTP/2 is enabled.
621+
*/
622+
public function isHttp2Enabled(): bool;
623+
624+
/**
625+
* Get connection pool statistics.
626+
*
627+
* @return array<string, mixed>
628+
*/
629+
public function getPoolStats(): array;
630+
631+
/**
632+
* Get DNS cache statistics.
633+
*
634+
* @return array<string, mixed>
635+
*/
636+
public function getDnsCacheStats(): array;
637+
638+
/**
639+
* Clear the DNS cache.
640+
*
641+
* @param string|null $hostname Specific hostname to clear, or null for all
642+
* @return $this
643+
*/
644+
public function clearDnsCache(?string $hostname = null): self;
645+
646+
/**
647+
* Close all pooled connections.
648+
*
649+
* @return $this
650+
*/
651+
public function closeAllConnections(): self;
652+
653+
/**
654+
* Reset the global connection pool and DNS cache.
655+
*
656+
* @return $this
657+
*/
658+
public function resetPool(): self;
576659
}

0 commit comments

Comments
 (0)