12
12
namespace Symfony \Component \HttpClient ;
13
13
14
14
use Psr \Log \LoggerAwareInterface ;
15
- use Psr \Log \LoggerAwareTrait ;
15
+ use Psr \Log \LoggerInterface ;
16
16
use Symfony \Component \HttpClient \Exception \InvalidArgumentException ;
17
17
use Symfony \Component \HttpClient \Exception \TransportException ;
18
18
use Symfony \Component \HttpClient \Internal \CurlClientState ;
35
35
final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
36
36
{
37
37
use HttpClientTrait;
38
- use LoggerAwareTrait;
39
38
40
39
private array $ defaultOptions = self ::OPTIONS_DEFAULTS + [
41
40
'auth_ntlm ' => null , // array|string - an array containing the username as first value, and optionally the
@@ -45,13 +44,13 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
45
44
],
46
45
];
47
46
47
+ private ?LoggerInterface $ logger = null ;
48
+
48
49
/**
49
50
* An internal object to share state between the client and its responses.
50
51
*/
51
52
private $ multi ;
52
53
53
- private static array $ curlVersion ;
54
-
55
54
/**
56
55
* @param array $defaultOptions Default request's options
57
56
* @param int $maxHostConnections The maximum number of connections to a single host
@@ -71,33 +70,12 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
71
70
[, $ this ->defaultOptions ] = self ::prepareRequest (null , null , $ defaultOptions , $ this ->defaultOptions );
72
71
}
73
72
74
- $ this ->multi = new CurlClientState ();
75
- self ::$ curlVersion = self ::$ curlVersion ?? curl_version ();
76
-
77
- // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
78
- if (\defined ('CURLPIPE_MULTIPLEX ' )) {
79
- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_PIPELINING , \CURLPIPE_MULTIPLEX );
80
- }
81
- if (\defined ('CURLMOPT_MAX_HOST_CONNECTIONS ' )) {
82
- $ maxHostConnections = curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAX_HOST_CONNECTIONS , 0 < $ maxHostConnections ? $ maxHostConnections : \PHP_INT_MAX ) ? 0 : $ maxHostConnections ;
83
- }
84
- if (\defined ('CURLMOPT_MAXCONNECTS ' ) && 0 < $ maxHostConnections ) {
85
- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAXCONNECTS , $ maxHostConnections );
86
- }
87
-
88
- // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
89
- if (0 >= $ maxPendingPushes ) {
90
- return ;
91
- }
92
-
93
- // HTTP/2 push crashes before curl 7.61
94
- if (!\defined ('CURLMOPT_PUSHFUNCTION ' ) || 0x073D00 > self ::$ curlVersion ['version_number ' ] || !(\CURL_VERSION_HTTP2 & self ::$ curlVersion ['features ' ])) {
95
- return ;
96
- }
73
+ $ this ->multi = new CurlClientState ($ maxHostConnections , $ maxPendingPushes );
74
+ }
97
75
98
- curl_multi_setopt ( $ this -> multi -> handle , \ CURLMOPT_PUSHFUNCTION , function ( $ parent , $ pushed , array $ requestHeaders ) use ( $ maxPendingPushes ) {
99
- return $ this -> handlePush ( $ parent , $ pushed , $ requestHeaders , $ maxPendingPushes );
100
- }) ;
76
+ public function setLogger ( LoggerInterface $ logger ): void
77
+ {
78
+ $ this -> logger = $ this -> multi -> logger = $ logger ;
101
79
}
102
80
103
81
/**
@@ -143,7 +121,7 @@ public function request(string $method, string $url, array $options = []): Respo
143
121
$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_0 ;
144
122
} elseif (1.1 === (float ) $ options ['http_version ' ]) {
145
123
$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_1 ;
146
- } elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 & self ::$ curlVersion ['features ' ]) && ('https: ' === $ scheme || 2.0 === (float ) $ options ['http_version ' ])) {
124
+ } elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 & CurlClientState ::$ curlVersion ['features ' ]) && ('https: ' === $ scheme || 2.0 === (float ) $ options ['http_version ' ])) {
147
125
$ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_2_0 ;
148
126
}
149
127
@@ -186,11 +164,10 @@ public function request(string $method, string $url, array $options = []): Respo
186
164
$ this ->multi ->dnsCache ->evictions = [];
187
165
$ port = parse_url ($ authority , \PHP_URL_PORT ) ?: ('http: ' === $ scheme ? 80 : 443 );
188
166
189
- if ($ resolve && 0x072A00 > self ::$ curlVersion ['version_number ' ]) {
167
+ if ($ resolve && 0x072A00 > CurlClientState ::$ curlVersion ['version_number ' ]) {
190
168
// DNS cache removals require curl 7.42 or higher
191
169
// On lower versions, we have to create a new multi handle
192
- curl_multi_close ($ this ->multi ->handle );
193
- $ this ->multi ->handle = (new self ())->multi ->handle ;
170
+ $ this ->multi ->reset ();
194
171
}
195
172
196
173
foreach ($ options ['resolve ' ] as $ host => $ ip ) {
@@ -315,7 +292,7 @@ public function request(string $method, string $url, array $options = []): Respo
315
292
}
316
293
}
317
294
318
- return $ pushedResponse ?? new CurlResponse ($ this ->multi , $ ch , $ options , $ this ->logger , $ method , self ::createRedirectResolver ($ options , $ host ), self ::$ curlVersion ['version_number ' ]);
295
+ return $ pushedResponse ?? new CurlResponse ($ this ->multi , $ ch , $ options , $ this ->logger , $ method , self ::createRedirectResolver ($ options , $ host ), CurlClientState ::$ curlVersion ['version_number ' ]);
319
296
}
320
297
321
298
/**
@@ -329,75 +306,18 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n
329
306
330
307
if ($ this ->multi ->handle instanceof \CurlMultiHandle) {
331
308
$ active = 0 ;
332
- while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ this ->multi ->handle , $ active ));
309
+ while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ this ->multi ->handle , $ active )) {
310
+ }
333
311
}
334
312
335
313
return new ResponseStream (CurlResponse::stream ($ responses , $ timeout ));
336
314
}
337
315
338
316
public function reset ()
339
317
{
340
- $ this ->multi ->logger = $ this ->logger ;
341
318
$ this ->multi ->reset ();
342
319
}
343
320
344
- public function __sleep (): array
345
- {
346
- throw new \BadMethodCallException ('Cannot serialize ' .__CLASS__ );
347
- }
348
-
349
- public function __wakeup ()
350
- {
351
- throw new \BadMethodCallException ('Cannot unserialize ' .__CLASS__ );
352
- }
353
-
354
- public function __destruct ()
355
- {
356
- $ this ->multi ->logger = $ this ->logger ;
357
- }
358
-
359
- private function handlePush ($ parent , $ pushed , array $ requestHeaders , int $ maxPendingPushes ): int
360
- {
361
- $ headers = [];
362
- $ origin = curl_getinfo ($ parent , \CURLINFO_EFFECTIVE_URL );
363
-
364
- foreach ($ requestHeaders as $ h ) {
365
- if (false !== $ i = strpos ($ h , ': ' , 1 )) {
366
- $ headers [substr ($ h , 0 , $ i )][] = substr ($ h , 1 + $ i );
367
- }
368
- }
369
-
370
- if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ])) {
371
- $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": pushed headers are invalid ' , $ origin ));
372
-
373
- return \CURL_PUSH_DENY ;
374
- }
375
-
376
- $ url = $ headers [':scheme ' ][0 ].':// ' .$ headers [':authority ' ][0 ];
377
-
378
- // curl before 7.65 doesn't validate the pushed ":authority" header,
379
- // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
380
- // ignoring domains mentioned as alt-name in the certificate for now (same as curl).
381
- if (!str_starts_with ($ origin , $ url .'/ ' )) {
382
- $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": server is not authoritative for "%s" ' , $ origin , $ url ));
383
-
384
- return \CURL_PUSH_DENY ;
385
- }
386
-
387
- if ($ maxPendingPushes <= \count ($ this ->multi ->pushedResponses )) {
388
- $ fifoUrl = key ($ this ->multi ->pushedResponses );
389
- unset($ this ->multi ->pushedResponses [$ fifoUrl ]);
390
- $ this ->logger && $ this ->logger ->debug (sprintf ('Evicting oldest pushed response: "%s" ' , $ fifoUrl ));
391
- }
392
-
393
- $ url .= $ headers [':path ' ][0 ];
394
- $ this ->logger && $ this ->logger ->debug (sprintf ('Queueing pushed response: "%s" ' , $ url ));
395
-
396
- $ this ->multi ->pushedResponses [$ url ] = new PushedResponse (new CurlResponse ($ this ->multi , $ pushed ), $ headers , $ this ->multi ->openHandles [(int ) $ parent ][1 ] ?? [], $ pushed );
397
-
398
- return \CURL_PUSH_OK ;
399
- }
400
-
401
321
/**
402
322
* Accepts pushed responses only if their headers related to authentication match the request.
403
323
*/
0 commit comments