From c651f63813cf7f228494c6ed44f3f1d795f5f4ac Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 4 Jan 2020 14:48:34 +0100 Subject: [PATCH] [HttpClient] fix support for non-blocking resource streams --- .../HttpClient/Response/StreamWrapper.php | 17 ++++++++++++++++- .../HttpClient/Tests/HttpClientTestCase.php | 16 ++++++++++++++++ .../HttpClient/Tests/MockHttpClientTest.php | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php index 59fd118e86e0..c8a32d820a25 100644 --- a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php +++ b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php @@ -37,6 +37,8 @@ class StreamWrapper /** @var resource|null */ private $handle; + private $blocking = true; + private $timeout; private $eof = false; private $offset = 0; @@ -147,7 +149,7 @@ public function stream_read(int $count) return $data; } - foreach ($this->client->stream([$this->response]) as $chunk) { + foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) { try { $this->eof = true; $this->eof = !$chunk->isTimeout(); @@ -178,6 +180,19 @@ public function stream_read(int $count) return ''; } + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + if (STREAM_OPTION_BLOCKING === $option) { + $this->blocking = (bool) $arg1; + } elseif (STREAM_OPTION_READ_TIMEOUT === $option) { + $this->timeout = $arg1 + $arg2 / 1e6; + } else { + return false; + } + + return true; + } + public function stream_tell(): int { return $this->offset; diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php index b4b5b9ec1a63..29171969b457 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTestCase.php @@ -75,4 +75,20 @@ public function testToStream404() $response = $client->request('GET', 'http://localhost:8057/404'); $stream = $response->toStream(); } + + public function testNonBlockingStream() + { + $client = $this->getHttpClient(__FUNCTION__); + $response = $client->request('GET', 'http://localhost:8057/timeout-body'); + $stream = $response->toStream(); + + $this->assertTrue(stream_set_blocking($stream, false)); + $this->assertSame('<1>', fread($stream, 8192)); + $this->assertFalse(feof($stream)); + + $this->assertTrue(stream_set_blocking($stream, true)); + $this->assertSame('<2>', fread($stream, 8192)); + $this->assertSame('', fread($stream, 8192)); + $this->assertTrue(feof($stream)); + } } diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index 7bc528354811..bce4bfafea8c 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -171,6 +171,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface return $client; + case 'testNonBlockingStream': + $responses[] = new MockResponse((function () { yield '<1>'; yield ''; yield '<2>'; })(), ['response_headers' => $headers]); + break; + case 'testMaxDuration': $mock = $this->getMockBuilder(ResponseInterface::class)->getMock(); $mock->expects($this->any())