Skip to content

Commit 444626b

Browse files
Merge branch '5.1' into 5.x
* 5.1: [HttpClient] fix reading the body after a ClientException [HttpClient] fix tests and merge
2 parents c31015e + b9e76b2 commit 444626b

File tree

7 files changed

+100
-67
lines changed

7 files changed

+100
-67
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Internal;
13+
14+
/**
15+
* @author Nicolas Grekas <[email protected]>
16+
*
17+
* @internal
18+
*/
19+
final class Canary
20+
{
21+
private $canceller;
22+
23+
public function __construct(\Closure $canceller)
24+
{
25+
$this->canceller = $canceller;
26+
}
27+
28+
public function cancel()
29+
{
30+
if (($canceller = $this->canceller) instanceof \Closure) {
31+
$this->canceller = null;
32+
$canceller();
33+
}
34+
}
35+
36+
public function __destruct()
37+
{
38+
$this->cancel();
39+
}
40+
}

src/Symfony/Component/HttpClient/Response/AmpResponse.php

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Component\HttpClient\Internal\AmpBody;
3131
use Symfony\Component\HttpClient\Internal\AmpCanary;
3232
use Symfony\Component\HttpClient\Internal\AmpClientState;
33+
use Symfony\Component\HttpClient\Internal\Canary;
3334
use Symfony\Component\HttpClient\Internal\ClientState;
3435
use Symfony\Contracts\HttpClient\ResponseInterface;
3536

@@ -127,6 +128,11 @@ public function __construct(AmpClientState $multi, Request $request, array $opti
127128

128129
$multi->openHandles[$id] = $id;
129130
++$multi->responseCount;
131+
132+
$this->canary = new Canary(static function () use ($canceller, $multi, $id) {
133+
$canceller->cancel();
134+
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
135+
});
130136
}
131137

132138
/**
@@ -142,29 +148,14 @@ public function __destruct()
142148
try {
143149
$this->doDestruct();
144150
} finally {
145-
$multi = clone $this->multi;
146-
147-
$this->close();
148-
149151
// Clear the DNS cache when all requests completed
150152
if (0 >= --$this->multi->responseCount) {
151153
$this->multi->responseCount = 0;
152154
$this->multi->dnsCache = [];
153155
}
154-
155-
$this->multi = $multi;
156156
}
157157
}
158158

159-
/**
160-
* {@inheritdoc}
161-
*/
162-
private function close(): void
163-
{
164-
$this->canceller->cancel();
165-
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
166-
}
167-
168159
/**
169160
* {@inheritdoc}
170161
*/

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpClient\Chunk\FirstChunk;
1616
use Symfony\Component\HttpClient\Chunk\InformationalChunk;
1717
use Symfony\Component\HttpClient\Exception\TransportException;
18+
use Symfony\Component\HttpClient\Internal\Canary;
1819
use Symfony\Component\HttpClient\Internal\ClientState;
1920
use Symfony\Component\HttpClient\Internal\CurlClientState;
2021
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -171,6 +172,31 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
171172
// Schedule the request in a non-blocking way
172173
$multi->openHandles[$id] = [$ch, $options];
173174
curl_multi_add_handle($multi->handle, $ch);
175+
176+
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
177+
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
178+
curl_setopt($ch, \CURLOPT_PRIVATE, '_0');
179+
180+
if (self::$performing) {
181+
return;
182+
}
183+
184+
curl_multi_remove_handle($multi->handle, $ch);
185+
curl_setopt_array($ch, [
186+
\CURLOPT_NOPROGRESS => true,
187+
\CURLOPT_PROGRESSFUNCTION => null,
188+
\CURLOPT_HEADERFUNCTION => null,
189+
\CURLOPT_WRITEFUNCTION => null,
190+
\CURLOPT_READFUNCTION => null,
191+
\CURLOPT_INFILE => null,
192+
]);
193+
194+
if (!$multi->openHandles) {
195+
// Schedule DNS cache eviction for the next request
196+
$multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals;
197+
$multi->dnsCache->removals = $multi->dnsCache->hostnames = [];
198+
}
199+
});
174200
}
175201

176202
/**
@@ -221,48 +247,11 @@ public function getContent(bool $throw = true): string
221247

222248
public function __destruct()
223249
{
224-
try {
225-
if (null === $this->timeout) {
226-
return; // Unused pushed response
227-
}
228-
229-
$this->doDestruct();
230-
} finally {
231-
$multi = clone $this->multi;
232-
233-
$this->close();
234-
235-
if (!$this->multi->openHandles) {
236-
// Schedule DNS cache eviction for the next request
237-
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
238-
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
239-
}
240-
241-
$this->multi = $multi;
242-
}
243-
}
244-
245-
/**
246-
* {@inheritdoc}
247-
*/
248-
private function close(): void
249-
{
250-
unset($this->multi->pauseExpiries[$this->id], $this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
251-
curl_setopt($this->handle, \CURLOPT_PRIVATE, '_0');
252-
253-
if (self::$performing) {
254-
return;
250+
if (null === $this->timeout) {
251+
return; // Unused pushed response
255252
}
256253

257-
curl_multi_remove_handle($this->multi->handle, $this->handle);
258-
curl_setopt_array($this->handle, [
259-
\CURLOPT_NOPROGRESS => true,
260-
\CURLOPT_PROGRESSFUNCTION => null,
261-
\CURLOPT_HEADERFUNCTION => null,
262-
\CURLOPT_WRITEFUNCTION => null,
263-
\CURLOPT_READFUNCTION => null,
264-
\CURLOPT_INFILE => null,
265-
]);
254+
$this->doDestruct();
266255
}
267256

268257
/**

src/Symfony/Component/HttpClient/Response/NativeResponse.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\HttpClient\Chunk\FirstChunk;
1616
use Symfony\Component\HttpClient\Exception\TransportException;
17+
use Symfony\Component\HttpClient\Internal\Canary;
1718
use Symfony\Component\HttpClient\Internal\ClientState;
1819
use Symfony\Component\HttpClient\Internal\NativeClientState;
1920
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -45,7 +46,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
4546
public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger)
4647
{
4748
$this->multi = $multi;
48-
$this->id = (int) $context;
49+
$this->id = $id = (int) $context;
4950
$this->context = $context;
5051
$this->url = $url;
5152
$this->logger = $logger;
@@ -70,6 +71,13 @@ public function __construct(NativeClientState $multi, $context, string $url, arr
7071
$info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) {
7172
$pauseExpiry = 0 < $duration ? microtime(true) + $duration : 0;
7273
};
74+
75+
$this->canary = new Canary(static function () use ($multi, $id) {
76+
if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) {
77+
unset($multi->hosts[$host]);
78+
}
79+
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
80+
});
7381
}
7482

7583
/**
@@ -95,17 +103,11 @@ public function __destruct()
95103
try {
96104
$this->doDestruct();
97105
} finally {
98-
$multi = clone $this->multi;
99-
100-
$this->close();
101-
102106
// Clear the DNS cache when all requests completed
103107
if (0 >= --$this->multi->responseCount) {
104108
$this->multi->responseCount = 0;
105109
$this->multi->dnsCache = [];
106110
}
107-
108-
$this->multi = $multi;
109111
}
110112
}
111113

@@ -203,11 +205,8 @@ private function open(): void
203205
*/
204206
private function close(): void
205207
{
206-
if (null !== ($host = $this->multi->openHandles[$this->id][6] ?? null) && 0 >= --$this->multi->hosts[$host]) {
207-
unset($this->multi->hosts[$host]);
208-
}
209-
unset($this->multi->openHandles[$this->id], $this->multi->handlesActivity[$this->id]);
210-
$this->handle = $this->buffer = $this->onProgress = null;
208+
$this->canary->cancel();
209+
$this->handle = $this->buffer = $this->inflate = $this->onProgress = null;
211210
}
212211

213212
/**

src/Symfony/Component/HttpClient/Response/TransportResponseTrait.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ trait TransportResponseTrait
4141
private $timeout = 0;
4242
private $inflate;
4343
private $finalInfo;
44+
private $canary;
4445
private $logger;
4546

4647
/**
@@ -81,6 +82,15 @@ public function cancel(): void
8182
$this->close();
8283
}
8384

85+
/**
86+
* Closes the response and all its network handles.
87+
*/
88+
protected function close(): void
89+
{
90+
$this->canary->cancel();
91+
$this->inflate = null;
92+
}
93+
8494
/**
8595
* Adds pending responses to the activity list.
8696
*/

src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ public function testHandleIsRemovedOnException()
319319
// The response content-type mustn't be json as that calls getContent
320320
// @see src/Symfony/Component/HttpClient/Exception/HttpExceptionTrait.php:58
321321
$this->assertStringNotContainsString('json', $e->getResponse()->getHeaders(false)['content-type'][0] ?? '');
322+
unset($e);
322323

323324
$r = new \ReflectionProperty($client, 'multi');
324325
$r->setAccessible(true);

src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
case '/404-gzipped':
6767
header('Content-Type: text/plain', true, 404);
6868
ob_start('ob_gzhandler');
69+
@ob_flush();
70+
flush();
71+
usleep(300000);
6972
echo 'some text';
7073
exit;
7174

0 commit comments

Comments
 (0)