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