8000 [HttpClient] throw exception when AsyncDecoratorTrait gets an already… · symfony/symfony@2062372 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2062372

Browse files
[HttpClient] throw exception when AsyncDecoratorTrait gets an already consumed response
1 parent 05a7020 commit 2062372

File tree

2 files changed

+38
-2
lines changed

2 files changed

+38
-2
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
3636
private $info = ['canceled' => false];
3737
private $passthru;
3838
private $stream;
39+
private $firstYielded = false;
3940
private $lastYielded = false;
4041

4142
/**
@@ -272,6 +273,14 @@ public static function stream(iterable $responses, float $timeout = null, string
272273
continue;
273274
}
274275

276+
if (null !== $chunk->getError()) {
277+
// no-op
278+
} elseif ($chunk->isFirst()) {
279+
$r->firstYielded = true;
280+
} elseif (!$r->firstYielded && null === $chunk->getInformationalStatus()) {
281+
throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class));
282+
}
283+
275284
foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) {
276285
yield $r => $chunk;
277286
}

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@
1919
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
2020
use Symfony\Contracts\HttpClient\HttpClientInterface;
2121
use Symfony\Contracts\HttpClient\ResponseInterface;
22+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
2223

2324
class AsyncDecoratorTraitTest extends NativeHttpClientTest
2425
{
25-
protected function getHttpClient(string $testCase, \Closure $chunkFilter = null): HttpClientInterface
26+
protected function getHttpClient(string $testCase, \Closure $chunkFilter = null, HttpClientInterface $decoratedClient = null): HttpClientInterface
2627
{
2728
if ('testHandleIsRemovedOnException' === $testCase) {
2829
$this->markTestSkipped("AsyncDecoratorTrait doesn't cache handles");
2930
}
3031

3132
$chunkFilter = $chunkFilter ?? static function (ChunkInterface $chunk, AsyncContext $context) { yield $chunk; };
3233

33-
return new class(parent::getHttpClient($testCase), $chunkFilter) implements HttpClientInterface {
34+
return new class($decoratedClient ?? parent::getHttpClient($testCase), $chunkFilter) implements HttpClientInterface {
3435
use AsyncDecoratorTrait;
3536

3637
private $chunkFilter;
@@ -303,4 +304,30 @@ public function testMultipleYieldInInitializer()
303304
$this->assertSame(404, $response->getStatusCode());
304305
$this->assertStringContainsString('injectedFoo', $response->getContent(false));
305306
}
307+
308+
public function testConsumingDecoratedClient()
309+
{
310+
$client = $this->getHttpClient(__FUNCTION__, null, new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface {
311+
use AsyncDecoratorTrait;
312+
313+
public function request(string $method, string $url, array $options = []): ResponseInterface
314+
{
315+
$response = $this->client->request($method, $url, $options);
316+
$response->getStatusCode(); // should be avoided and breaks compatibility with AsyncDecoratorTrait
317+
318+
return $response;
319+
}
320+
321+
public function stream($responses, float $timeout = null): ResponseStreamInterface
322+
{
323+
return $this->client->stream($responses, $timeout);
324+
}
325+
});
326+
327+
$response = $client->request('GET', 'http://localhost:8057/');
328+
329+
$this->expectException(\LogicException::class);
330+
$this->expectExceptionMessage('Instance of "Symfony\Component\HttpClient\Response\NativeResponse" is already consumed and cannot be managed by "Symfony\Component\HttpClient\Response\AsyncResponse". A decorated client should not call any of the response\'s methods in its "request()" method.');
331+
$response->getStatusCode();
332+
}
306333
}

0 commit comments

Comments
 (0)
0