Skip to content

Commit ae38891

Browse files
Merge branch '5.4' into 6.0
* 5.4: [HttpClient] fix resetting DNS/etc when calling CurlHttpClient::reset() Fix invalid guess with enumType [HttpClient] Remove deprecated usage of GuzzleHttp\Promise\promise_for
2 parents c806cb4 + 1a407eb commit ae38891

File tree

5 files changed

+56
-81
lines changed

5 files changed

+56
-81
lines changed

CurlHttpClient.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ public function request(string $method, string $url, array $options = []): Respo
166166

167167
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
168168
// DNS cache removals require curl 7.42 or higher
169-
// On lower versions, we have to create a new multi handle
170169
$this->multi->reset();
171170
}
172171

@@ -283,6 +282,7 @@ public function request(string $method, string $url, array $options = []): Respo
283282
if (!$pushedResponse) {
284283
$ch = curl_init();
285284
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
285+
$curlopts += [\CURLOPT_SHARE => $this->multi->share];
286286
}
287287

288288
foreach ($curlopts as $opt => $value) {
@@ -304,9 +304,9 @@ public function stream(ResponseInterface|iterable $responses, float $timeout = n
304304
$responses = [$responses];
305305
}
306306

307-
if (($mh = $this->multi->handles[0] ?? null) instanceof \CurlMultiHandle) {
307+
if ($this->multi->handle instanceof \CurlMultiHandle) {
308308
$active = 0;
309-
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $active)) {
309+
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)) {
310310
}
311311
}
312312

Internal/CurlClientState.php

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,39 +23,35 @@
2323
*/
2424
final class CurlClientState extends ClientState
2525
{
26-
/** @var array<\CurlMultiHandle> */
27-
public array $handles = [];
26+
public \CurlMultiHandle $handle;
27+
public \CurlShareHandle $share;
2828
/** @var PushedResponse[] */
2929
public array $pushedResponses = [];
30-
public $dnsCache;
30+
public DnsCache $dnsCache;
3131
/** @var float[] */
3232
public array $pauseExpiries = [];
3333
public int $execCounter = \PHP_INT_MIN;
3434
public ?LoggerInterface $logger = null;
3535

3636
public static array $curlVersion;
3737

38-
private int $maxHostConnections;
39-
private int $maxPendingPushes;
40-
4138
public function __construct(int $maxHostConnections, int $maxPendingPushes)
4239
{
4340
self::$curlVersion = self::$curlVersion ?? curl_version();
4441

45-
array_unshift($this->handles, $mh = curl_multi_init());
42+
$this->handle = curl_multi_init();
4643
$this->dnsCache = new DnsCache();
47-
$this->maxHostConnections = $maxHostConnections;
48-
$this->maxPendingPushes = $maxPendingPushes;
44+
$this->reset();
4945

5046
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
5147
if (\defined('CURLPIPE_MULTIPLEX')) {
52-
curl_multi_setopt($mh, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
48+
curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
5349
}
5450
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
55-
$maxHostConnections = curl_multi_setopt($mh, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
51+
$maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections;
5652
}
5753
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
58-
curl_multi_setopt($mh, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
54+
curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
5955
}
6056

6157
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
@@ -68,40 +64,31 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes)
6864
return;
6965
}
7066

71-
// Clone to prevent a circular reference
72-
$multi = clone $this;
73-
$multi->handles = [$mh];
74-
$multi->pushedResponses = &$this->pushedResponses;
75-
$multi->logger = &$this->logger;
76-
$multi->handlesActivity = &$this->handlesActivity;
77-
$multi->openHandles = &$this->openHandles;
78-
$multi->lastTimeout = &$this->lastTimeout;
79-
80-
curl_multi_setopt($mh, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) {
81-
return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
67+
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, function ($parent, $pushed, array $requestHeaders) use ($maxPendingPushes) {
68+
return $this->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
8269
});
8370
}
8471

8572
public function reset()
8673
{
8774
foreach ($this->pushedResponses as $url => $response) {
8875
$this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
89-
90-
foreach ($this->handles as $mh) {
91-
curl_multi_remove_handle($mh, $response->handle);
92-
}
76+
curl_multi_remove_handle($this->handle, $response->handle);
9377
curl_close($response->handle);
9478
}
9579

9680
$this->pushedResponses = [];
9781
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
9882
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
9983

100-
if (\defined('CURLMOPT_PUSHFUNCTION')) {
101-
curl_multi_setopt($this->handles[0], \CURLMOPT_PUSHFUNCTION, null);
102-
}
84+
$this->share = curl_share_init();
85+
86+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
87+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
10388

104-
$this->__construct($this->maxHostConnections, $this->maxPendingPushes);
89+
if (\defined('CURL_LOCK_DATA_CONNECT')) {
90+
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT);
91+
}
10592
}
10693

10794
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int

Response/CurlResponse.php

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra
108108
if (0 < $duration) {
109109
if ($execCounter === $multi->execCounter) {
110110
$multi->execCounter = !\is_float($execCounter) ? 1 + $execCounter : \PHP_INT_MIN;
111-
foreach ($multi->handles as $mh) {
112-
curl_multi_remove_handle($mh, $ch);
113-
}
111+
curl_multi_remove_handle($multi->handle, $ch);
114112
}
115113

116114
$lastExpiry = end($multi->pauseExpiries);
@@ -122,7 +120,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra
122120
} else {
123121
unset($multi->pauseExpiries[(int) $ch]);
124122
curl_pause($ch, \CURLPAUSE_CONT);
125-
curl_multi_add_handle($multi->handles[0], $ch);
123+
curl_multi_add_handle($multi->handle, $ch);
126124
}
127125
};
128126

@@ -176,7 +174,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra
176174
// Schedule the request in a non-blocking way
177175
$multi->lastTimeout = null;
178176
$multi->openHandles[$id] = [$ch, $options];
179-
curl_multi_add_handle($multi->handles[0], $ch);
177+
curl_multi_add_handle($multi->handle, $ch);
180178

181179
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
182180
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
@@ -186,9 +184,7 @@ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, arra
186184
return;
187185
}
188186

189-
foreach ($multi->handles as $mh) {
190-
curl_multi_remove_handle($mh, $ch);
191-
}
187+
curl_multi_remove_handle($multi->handle, $ch);
192188
curl_setopt_array($ch, [
193189
\CURLOPT_NOPROGRESS => true,
194190
\CURLOPT_PROGRESSFUNCTION => null,
@@ -270,7 +266,7 @@ public function __destruct()
270266
*/
271267
private static function schedule(self $response, array &$runningResponses): void
272268
{
273-
if (isset($runningResponses[$i = (int) $response->multi->handles[0]])) {
269+
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
274270
$runningResponses[$i][1][$response->id] = $response;
275271
} else {
276272
$runningResponses[$i] = [$response->multi, [$response->id => $response]];
@@ -303,47 +299,39 @@ private static function perform(ClientState $multi, array &$responses = null): v
303299
try {
304300
self::$performing = true;
305301
++$multi->execCounter;
302+
$active = 0;
303+
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
304+
}
306305

307-
foreach ($multi->handles as $i => $mh) {
308-
$active = 0;
309-
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($mh, $active))) {
310-
}
306+
if (\CURLM_OK !== $err) {
307+
throw new TransportException(curl_multi_strerror($err));
308+
}
311309

312-
if (\CURLM_OK !== $err) {
313-
throw new TransportException(curl_multi_strerror($err));
310+
while ($info = curl_multi_info_read($multi->handle)) {
311+
if (\CURLMSG_DONE !== $info['msg']) {
312+
continue;
314313
}
314+
$result = $info['result'];
315+
$id = (int) $ch = $info['handle'];
316+
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
315317

316-
while ($info = curl_multi_info_read($mh)) {
317-
if (\CURLMSG_DONE !== $info['msg']) {
318-
continue;
319-
}
320-
$result = $info['result'];
321-
$id = (int) $ch = $info['handle'];
322-
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
323-
324-
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
325-
curl_multi_remove_handle($mh, $ch);
326-
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
327-
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
328-
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
329-
330-
if (0 === curl_multi_add_handle($mh, $ch)) {
331-
continue;
332-
}
333-
}
318+
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /*CURLE_HTTP2*/ 16, /*CURLE_HTTP2_STREAM*/ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
319+
curl_multi_remove_handle($multi->handle, $ch);
320+
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
321+
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
322+
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
334323

335-
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
336-
$multi->handlesActivity[$id][] = new FirstChunk();
324+
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
325+
continue;
337326
}
338-
339-
$multi->handlesActivity[$id][] = null;
340-
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
341327
}
342328

343-
if (!$active && 0 < $i) {
344-
curl_multi_close($mh);
345-
unset($multi->handles[$i]);
329+
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
330+
$multi->handlesActivity[$id][] = new FirstChunk();
346331
}
332+
333+
$multi->handlesActivity[$id][] = null;
334+
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
347335
}
348336
} finally {
349337
self::$performing = false;
@@ -368,11 +356,11 @@ private static function select(ClientState $multi, float $timeout): int
368356

369357
unset($multi->pauseExpiries[$id]);
370358
curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT);
371-
curl_multi_add_handle($multi->handles[0], $multi->openHandles[$id][0]);
359+
curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]);
372360
}
373361
}
374362

375-
if (0 !== $selected = curl_multi_select($multi->handles[array_key_last($multi->handles)], $timeout)) {
363+
if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) {
376364
return $selected;
377365
}
378366

Response/HttplugPromise.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\HttpClient\Response;
1313

14-
use function GuzzleHttp\Promise\promise_for;
14+
use GuzzleHttp\Promise\Create;
1515
use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface;
1616
use Http\Promise\Promise as HttplugPromiseInterface;
1717
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
@@ -74,7 +74,7 @@ private function wrapThenCallback(?callable $callback): ?callable
7474
}
7575

7676
return static function ($value) use ($callback) {
77-
return promise_for($callback($value));
77+
return Create::promiseFor($callback($value));
7878
};
7979
}
8080
}

Tests/CurlHttpClientTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public function testHandleIsReinitOnReset()
6262
$r = new \ReflectionProperty($httpClient, 'multi');
6363
$r->setAccessible(true);
6464
$clientState = $r->getValue($httpClient);
65-
$initialHandleId = (int) $clientState->handles[0];
65+
$initialShareId = $clientState->share;
6666
$httpClient->reset();
67-
self::assertNotSame($initialHandleId, (int) $clientState->handles[0]);
67+
self::assertNotSame($initialShareId, $clientState->share);
6868
}
6969

7070
public function testProcessAfterReset()

0 commit comments

Comments
 (0)