1212namespace Symfony \Component \HttpClient ;
1313
1414use Psr \Log \LoggerAwareInterface ;
15- use Psr \Log \LoggerAwareTrait ;
15+ use Psr \Log \LoggerInterface ;
1616use Symfony \Component \HttpClient \Exception \InvalidArgumentException ;
1717use Symfony \Component \HttpClient \Exception \TransportException ;
1818use Symfony \Component \HttpClient \Internal \CurlClientState ;
3535final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
3636{
3737 use HttpClientTrait;
38- use LoggerAwareTrait;
3938
4039 private $ defaultOptions = self ::OPTIONS_DEFAULTS + [
4140 'auth_ntlm ' => null , // array|string - an array containing the username as first value, and optionally the
4241 // password as the second one; or string like username:password - enabling NTLM auth
4342 ];
4443
44+ /**
45+ * @var LoggerInterface|null
46+ */
47+ private $ logger ;
48+
4549 /**
4650 * An internal object to share state between the client and its responses.
4751 *
4852 * @var CurlClientState
4953 */
5054 private $ multi ;
5155
52- private static $ curlVersion ;
53-
5456 /**
5557 * @param array $defaultOptions Default request's options
5658 * @param int $maxHostConnections The maximum number of connections to a single host
@@ -70,33 +72,12 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections
7072 [, $ this ->defaultOptions ] = self ::prepareRequest (null , null , $ defaultOptions , $ this ->defaultOptions );
7173 }
7274
73- $ this ->multi = new CurlClientState ();
74- self ::$ curlVersion = self ::$ curlVersion ?? curl_version ();
75-
76- // Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
77- if (\defined ('CURLPIPE_MULTIPLEX ' )) {
78- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_PIPELINING , \CURLPIPE_MULTIPLEX );
79- }
80- if (\defined ('CURLMOPT_MAX_HOST_CONNECTIONS ' )) {
81- $ maxHostConnections = curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAX_HOST_CONNECTIONS , 0 < $ maxHostConnections ? $ maxHostConnections : \PHP_INT_MAX ) ? 0 : $ maxHostConnections ;
82- }
83- if (\defined ('CURLMOPT_MAXCONNECTS ' ) && 0 < $ maxHostConnections ) {
84- curl_multi_setopt ($ this ->multi ->handle , \CURLMOPT_MAXCONNECTS , $ maxHostConnections );
85- }
86-
87- // Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
88- if (0 >= $ maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304 )) {
89- return ;
90- }
91-
92- // HTTP/2 push crashes before curl 7.61
93- if (!\defined ('CURLMOPT_PUSHFUNCTION ' ) || 0x073D00 > self ::$ curlVersion ['version_number ' ] || !(\CURL_VERSION_HTTP2 & self ::$ curlVersion ['features ' ])) {
94- return ;
95- }
75+ $ this ->multi = new CurlClientState ($ maxHostConnections , $ maxPendingPushes );
76+ }
9677
97- curl_multi_setopt ( $ this -> multi -> handle , \ CURLMOPT_PUSHFUNCTION , function ( $ parent , $ pushed , array $ requestHeaders ) use ( $ maxPendingPushes ) {
98- return $ this -> handlePush ( $ parent , $ pushed , $ requestHeaders , $ maxPendingPushes );
99- }) ;
78+ public function setLogger ( LoggerInterface $ logger ): void
79+ {
80+ $ this -> logger = $ this -> multi -> logger = $ logger ;
10081 }
10182
10283 /**
@@ -142,7 +123,7 @@ public function request(string $method, string $url, array $options = []): Respo
142123 $ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_0 ;
143124 } elseif (1.1 === (float ) $ options ['http_version ' ]) {
144125 $ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_1_1 ;
145- } elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 & self ::$ curlVersion ['features ' ]) && ('https: ' === $ scheme || 2.0 === (float ) $ options ['http_version ' ])) {
126+ } elseif (\defined ('CURL_VERSION_HTTP2 ' ) && (\CURL_VERSION_HTTP2 & CurlClientState ::$ curlVersion ['features ' ]) && ('https: ' === $ scheme || 2.0 === (float ) $ options ['http_version ' ])) {
146127 $ curlopts [\CURLOPT_HTTP_VERSION ] = \CURL_HTTP_VERSION_2_0 ;
147128 }
148129
@@ -185,11 +166,10 @@ public function request(string $method, string $url, array $options = []): Respo
185166 $ this ->multi ->dnsCache ->evictions = [];
186167 $ port = parse_url ($ authority , \PHP_URL_PORT ) ?: ('http: ' === $ scheme ? 80 : 443 );
187168
188- if ($ resolve && 0x072A00 > self ::$ curlVersion ['version_number ' ]) {
169+ if ($ resolve && 0x072A00 > CurlClientState ::$ curlVersion ['version_number ' ]) {
189170 // DNS cache removals require curl 7.42 or higher
190171 // On lower versions, we have to create a new multi handle
191- curl_multi_close ($ this ->multi ->handle );
192- $ this ->multi ->handle = (new self ())->multi ->handle ;
172+ $ this ->multi ->reset ();
193173 }
194174
195175 foreach ($ options ['resolve ' ] as $ host => $ ip ) {
@@ -312,7 +292,7 @@ public function request(string $method, string $url, array $options = []): Respo
312292 }
313293 }
314294
315- 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 ' ]);
316296 }
317297
318298 /**
@@ -328,78 +308,18 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
328308
329309 if (\is_resource ($ this ->multi ->handle ) || $ this ->multi ->handle instanceof \CurlMultiHandle) {
330310 $ active = 0 ;
331- while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ this ->multi ->handle , $ active ));
311+ while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ this ->multi ->handle , $ active )) {
312+ }
332313 }
333314
334315 return new ResponseStream (CurlResponse::stream ($ responses , $ timeout ));
335316 }
336317
337318 public function reset ()
338319 {
339- $ this ->multi ->logger = $ this ->logger ;
340320 $ this ->multi ->reset ();
341321 }
342322
343- /**
344- * @return array
345- */
346- public function __sleep ()
347- {
348- throw new \BadMethodCallException ('Cannot serialize ' .__CLASS__ );
349- }
350-
351- public function __wakeup ()
352- {
353- throw new \BadMethodCallException ('Cannot unserialize ' .__CLASS__ );
354- }
355-
356- public function __destruct ()
357- {
358- $ this ->multi ->logger = $ this ->logger ;
359- }
360-
361- private function handlePush ($ parent , $ pushed , array $ requestHeaders , int $ maxPendingPushes ): int
362- {
363- $ headers = [];
364- $ origin = curl_getinfo ($ parent , \CURLINFO_EFFECTIVE_URL );
365-
366- foreach ($ requestHeaders as $ h ) {
367- if (false !== $ i = strpos ($ h , ': ' , 1 )) {
368- $ headers [substr ($ h , 0 , $ i )][] = substr ($ h , 1 + $ i );
369- }
370- }
371-
372- if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ])) {
373- $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": pushed headers are invalid ' , $ origin ));
374-
375- return \CURL_PUSH_DENY ;
376- }
377-
378- $ url = $ headers [':scheme ' ][0 ].':// ' .$ headers [':authority ' ][0 ];
379-
380- // curl before 7.65 doesn't validate the pushed ":authority" header,
381- // but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
382- // ignoring domains mentioned as alt-name in the certificate for now (same as curl).
383- if (!str_starts_with ($ origin , $ url .'/ ' )) {
384- $ this ->logger && $ this ->logger ->debug (sprintf ('Rejecting pushed response from "%s": server is not authoritative for "%s" ' , $ origin , $ url ));
385-
386- return \CURL_PUSH_DENY ;
387- }
388-
389- if ($ maxPendingPushes <= \count ($ this ->multi ->pushedResponses )) {
390- $ fifoUrl = key ($ this ->multi ->pushedResponses );
391- unset($ this ->multi ->pushedResponses [$ fifoUrl ]);
392- $ this ->logger && $ this ->logger ->debug (sprintf ('Evicting oldest pushed response: "%s" ' , $ fifoUrl ));
393- }
394-
395- $ url .= $ headers [':path ' ][0 ];
396- $ this ->logger && $ this ->logger ->debug (sprintf ('Queueing pushed response: "%s" ' , $ url ));
397-
398- $ this ->multi ->pushedResponses [$ url ] = new PushedResponse (new CurlResponse ($ this ->multi , $ pushed ), $ headers , $ this ->multi ->openHandles [(int ) $ parent ][1 ] ?? [], $ pushed );
399-
400- return \CURL_PUSH_OK ;
401- }
402-
403323 /**
404324 * Accepts pushed responses only if their headers related to authentication match the request.
405325 */
0 commit comments