From 0a8c93b1f1bb4355faf5b87c9f4c120a663fdf3a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 6 Sep 2016 23:06:03 -0400 Subject: [PATCH 1/2] Don't attempt to rewind unseekable streams If a stream does not support seek, we have to rely on __toString() to access the stream's contents. If a stream cannot be rewound, it likely can't be incrementally read either. Refs #199 --- src/Response/SapiStreamEmitter.php | 10 +++++++--- test/Response/SapiStreamEmitterTest.php | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Response/SapiStreamEmitter.php b/src/Response/SapiStreamEmitter.php index be3e3f31..e24f9ad9 100644 --- a/src/Response/SapiStreamEmitter.php +++ b/src/Response/SapiStreamEmitter.php @@ -56,10 +56,14 @@ public function emit(ResponseInterface $response, $maxBufferLength = 8192) private function emitBody(ResponseInterface $response, $maxBufferLength) { $body = $response->getBody(); - $body->rewind(); + if ($body->isSeekable()) { + $body->rewind(); - while (! $body->eof()) { - echo $body->read($maxBufferLength); + while (! $body->eof()) { + echo $body->read($maxBufferLength); + } + } else { + echo $body; } } diff --git a/test/Response/SapiStreamEmitterTest.php b/test/Response/SapiStreamEmitterTest.php index be11995d..714c21f9 100644 --- a/test/Response/SapiStreamEmitterTest.php +++ b/test/Response/SapiStreamEmitterTest.php @@ -12,6 +12,7 @@ use PHPUnit_Framework_TestCase as TestCase; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; +use Zend\Diactoros\CallbackStream; use Zend\Diactoros\Response; use Zend\Diactoros\Response\SapiStreamEmitter; use ZendTest\Diactoros\TestAsset\HeaderStack; @@ -24,6 +25,19 @@ public function setUp() $this->emitter = new SapiStreamEmitter(); } + public function testEmitCallbackStreamResponse() + { + $stream = new CallbackStream(function () { + return 'it works'; + }); + $response = (new Response()) + ->withStatus(200) + ->withBody($stream); + ob_start(); + $this->emitter->emit($response); + $this->assertEquals('it works', ob_get_clean()); + } + public function testDoesNotInjectContentLengthHeaderIfStreamSizeIsUnknown() { $stream = $this->prophesize('Psr\Http\Message\StreamInterface'); From 92b1e34e2e6424c81e80c5a8a8a202a0eef98b3d Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 6 Sep 2016 23:12:25 -0400 Subject: [PATCH 2/2] Fix byte ranges with unseekable response bodies. While it would be preferrable to seek around the stream, some streams cannot be looked at. In these cases the best effort we can have is to get the entire stream and slice out the requested byte range. Refs #199 --- src/Response/SapiStreamEmitter.php | 9 ++++++++- test/Response/SapiStreamEmitterTest.php | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Response/SapiStreamEmitter.php b/src/Response/SapiStreamEmitter.php index e24f9ad9..4ec4c303 100644 --- a/src/Response/SapiStreamEmitter.php +++ b/src/Response/SapiStreamEmitter.php @@ -76,10 +76,17 @@ private function emitBody(ResponseInterface $response, $maxBufferLength) */ private function emitBodyRange(array $range, ResponseInterface $response, $maxBufferLength) { - list($unit, $first, $last, $lenght) = $range; + list($unit, $first, $last, $length) = $range; ++$last; //zero-based position $body = $response->getBody(); + + if (!$body->isSeekable()) { + $contents = $body->getContents(); + echo substr($contents, $first, $last - $first); + return; + } + $body->seek($first); $pos = $first; diff --git a/test/Response/SapiStreamEmitterTest.php b/test/Response/SapiStreamEmitterTest.php index 714c21f9..5ba2e71c 100644 --- a/test/Response/SapiStreamEmitterTest.php +++ b/test/Response/SapiStreamEmitterTest.php @@ -80,4 +80,18 @@ public function testContentRange($header, $body, $expected) $this->emitter->emit($response); $this->assertEquals($expected, ob_get_clean()); } + + public function testContentRangeUnseekableBody() + { + $body = new CallbackStream(function () { + return 'Hello world'; + }); + $response = (new Response()) + ->withBody($body) + ->withHeader('Content-Range', 'bytes 3-6/*'); + + ob_start(); + $this->emitter->emit($response); + $this->assertEquals('lo w', ob_get_clean()); + } }