-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[2.2] [output-stream] Refactor StreamingResponse implementation to use an OutputStream #4146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a2f32a9
7c44cba
56a9192
cc8fca3
27e8732
8c6fcec
6e8cbe6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpFoundation\OutputStream; | ||
|
||
/** | ||
* OutputStreamInterface defines how streaming responses | ||
* send data to the client. | ||
* | ||
* @author Igor Wiedler <[email protected]> | ||
*/ | ||
interface OutputStreamInterface | ||
{ | ||
/** | ||
* Write some data to the stream. | ||
* | ||
* @param string $data The data to write | ||
*/ | ||
function write($data); | ||
|
||
/** | ||
* Close the stream. | ||
* | ||
* This should be called after the sending of data | ||
* has been completed. In case of persistent connections, | ||
* it is never called. | ||
*/ | ||
function close(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpFoundation\OutputStream; | ||
|
||
/** | ||
* StreamOutputStream is an implementation of the OutputStreamInterface | ||
* that uses any writable PHP stream as a target. | ||
* | ||
* Example usage: | ||
* | ||
* $stream = fopen('php://output'); | ||
* $output = new StreamOutputStream($stream, 'w'); | ||
* $output->write('foo'); | ||
* $output->write('bar'); | ||
* $output->close(); | ||
* | ||
* @author Igor Wiedler <[email protected]> | ||
*/ | ||
class StreamOutputStream implements OutputStreamInterface | ||
{ | ||
private $stream; | ||
|
||
/** | ||
* @param resource $stream A valid stream resource, such as the return value of | ||
* a fopen() call. | ||
*/ | ||
public function __construct($stream) | ||
{ | ||
if (!is_resource($stream)) { | ||
throw new \InvalidArgumentException('The supplied stream is invalid.'); | ||
} | ||
|
||
$this->stream = $stream; | ||
} | ||
|
||
/** | ||
* @{inheritdoc} | ||
*/ | ||
public function write($data) | ||
{ | ||
fwrite($this->stream, $data); | ||
} | ||
|
||
/** | ||
* @{inheritdoc} | ||
*/ | ||
public function close() | ||
{ | ||
fclose($this->stream); | ||
} | ||
|
||
/** | ||
* Expose the stream resource, so that it can be used for more efficient | ||
* operations, such as stream_copy_to_stream. | ||
* | ||
* @return The stream resource | ||
*/ | ||
public function getStreamResource() | ||
{ | ||
return $this->stream; | ||
} | ||
|
||
/** | ||
* Static factory method | ||
*/ | ||
static public function create($filename) | ||
{ | ||
$stream = fopen($filename, 'w'); | ||
|
||
return new static($stream); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing empty line |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,8 @@ | |
|
||
namespace Symfony\Component\HttpFoundation; | ||
|
||
use Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface; | ||
|
||
/** | ||
* StreamedResponse represents a streamed HTTP response. | ||
* | ||
|
@@ -29,6 +31,7 @@ | |
class StreamedResponse extends Response | ||
{ | ||
protected $callback; | ||
protected $outputStream; | ||
protected $streamed; | ||
|
||
/** | ||
|
@@ -72,7 +75,17 @@ public function setCallback($callback) | |
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* Sets the output stream used for streaming the response. | ||
* | ||
* @param OutputStreamInterface $outputStream stream | ||
*/ | ||
public function setOutputStream(OutputStreamInterface $outputStream) | ||
{ | ||
$this->outputStream = $outputStream; | ||
} | ||
|
||
/** | ||
* @{inheritdoc} | ||
*/ | ||
public function prepare(Request $request) | ||
{ | ||
|
@@ -102,7 +115,11 @@ public function sendContent() | |
throw new \LogicException('The Response callback must not be null.'); | ||
} | ||
|
||
call_user_func($this->callback); | ||
if (null === $this->outputStream) { | ||
throw new \LogicException('The Response output stream must not be null.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about using stdout by default here instead of throwing an exception? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @igorw what do you think about this ? |
||
} | ||
|
||
call_user_func($this->callback, $this->outputStream); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpFoundation\Tests\OutputStream; | ||
|
||
use Symfony\Component\HttpFoundation\OutputStream\StreamOutputStream; | ||
|
||
class OutputStreamTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
public function testConstructor() | ||
{ | ||
$fp = fopen('php://memory', 'rw'); | ||
$output = new StreamOutputStream($fp); | ||
} | ||
|
||
/** | ||
* @expectedException InvalidArgumentException | ||
*/ | ||
public function testConstructorWithInvalidStream() | ||
{ | ||
$fp = 'foobar'; | ||
$output = new StreamOutputStream($fp); | ||
} | ||
|
||
public function testWrite() | ||
{ | ||
$fp = fopen('php://memory', 'rw'); | ||
$output = new StreamOutputStream($fp); | ||
|
||
$output->write('sample output'); | ||
rewind($fp); | ||
$this->assertEquals('sample output', fread($fp, 50)); | ||
} | ||
|
||
public function testClose() | ||
{ | ||
$fp = fopen('php://memory', 'rw'); | ||
$output = new StreamOutputStream($fp); | ||
|
||
$output->close(); | ||
try { | ||
fread($fp, 1); | ||
$this->fail(); | ||
} catch (\PHPUnit_Framework_Error_Warning $e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more explicit as it verifies exactly where the notice occurs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @eriksencosta thus, I'm not sure we can use expectedException for PHPUnit internal exceptions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stof Yes, we can :) |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
namespace Symfony\Component\HttpKernel\EventListener; | ||
|
||
use Symfony\Component\HttpFoundation\StreamedResponse; | ||
use Symfony\Component\HttpFoundation\OutputStream\OutputStreamInterface; | ||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | ||
use Symfony\Component\HttpKernel\HttpKernelInterface; | ||
use Symfony\Component\HttpKernel\KernelEvents; | ||
|
@@ -25,6 +26,11 @@ | |
*/ | ||
class StreamedResponseListener implements EventSubscriberInterface | ||
{ | ||
public function __construct(OutputStreamInterface $outputStream) | ||
{ | ||
$this->outputStream = $outputStream; | ||
} | ||
|
||
/** | ||
* Filters the Response. | ||
* | ||
|
@@ -39,7 +45,10 @@ public function onKernelResponse(FilterResponseEvent $event) | |
$response = $event->getResponse(); | ||
|
||
if ($response instanceof StreamedResponse) { | ||
$response->setOutputStream($this->outputStream); | ||
$response->send(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not close the stream here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea was to allow async execution where the callback closes the stream later on. But since that doesn't really work properly with HttpKernel it makes sense to close it. |
||
|
||
$this->outputStream->close(); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be
public static