From 5be540d0f3f3a78b98ceb3d762d073c6c14e2fd0 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Fri, 18 May 2012 11:49:46 +0200 Subject: [PATCH] [HttpFoundation] Add a StreamedFileResponse. In the Drupal 8 branch where we are trying to leverage Symfonys HTTP Kernel we currently have something like this: ``` function file_download(...) { // [...] return new StreamedResponse(function() use ($uri)) { if (... && fopen($uri, 'rb')) { while (!feof($fd)) { print fread($fd, 1024); } fclose($fd); } }, 200, $headers); } ``` This is bad because: - There might be better methods of streaming out a file, like X-Sendfile if available. - Code like this is duplicated in different places. Obvious solution to the latter point: Factor that out to something like a StreamedFileResponse. Basically, then, the question is why such a class shouldn't be in Symfony itself. Heres a sketch of what such a class would have to do. For discussion and as context for the following questions, without testcoverage or a changelog entry (yet). - Generally: Have something like that in Symfony? - Detail: How to do error handling? Can I throw exceptions from the HttpKernel component? - How to best enable the different transfer methods? The switch-case stuff doesn't look good. How to leverage the dependency injection container or keep it compatible with that? --- .../HttpFoundation/StreamedFileResponse.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/Symfony/Component/HttpFoundation/StreamedFileResponse.php diff --git a/src/Symfony/Component/HttpFoundation/StreamedFileResponse.php b/src/Symfony/Component/HttpFoundation/StreamedFileResponse.php new file mode 100644 index 0000000000000..5ed5bc23b02c1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/StreamedFileResponse.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * StreamedFileResponse is an HTTP response that streams a file. + * + * @author Fabien Potencier + * + * @api + */ +class StreamedFileResponse extends StreamedResponse +{ + protected $filename; + protected $method; + + /** + * Constructor. + * + * @param string $filename The file to stream + * @param integer $status The response status code + * @param array $headers An array of response headers + * @param string $method The method to use to stream the file, like 'readfile' or 'x-sendfile' + * + * @api + */ + public function __construct($filename, $status = 200, $headers = array(), $method = 'readfile') + { + parent::__construct(null, $status, $headers); + + $this->filename = $filename; + $this->method = $method; + + switch ($this->method) { + case 'readfile': + $this->setCallback(function () use ($filename) { + if (FALSE === @readfile($filename)) { + throw new NotFoundHttpException(); + } + }); + break; + + case 'x-sendfile': + $this->setCallback(function() { }); + break; + } + } + + /** + * {@inheritDoc} + */ + public static function create($filename, $status = 200, $headers = array()) + { + return new static($filename, $status, $headers); + } + + /** + * @{inheritDoc} + */ + public function prepare(Request $request) + { + switch ($this->method) { + case 'x-sendfile': + $this->headers->set('X-Sendfile', $this->filename); + break; + } + + return $parent::prepare($request); + } +}