diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 11f2fe5..7716d1a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -24,7 +24,6 @@ Yii Framework 2 Change Log - Bug #1798: Fixed label attributes for array fields (zhuravljov) - Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark) - Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue) -- Bug #1844: Calling `Response::sendFile()` would cause sending the response twice (qiangxue) - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) @@ -69,6 +68,7 @@ Yii Framework 2 Change Log - Chg #1796: Removed `yii\base\Controller::getActionParams()` (samdark) - Chg #1835: `CheckboxColumn` now renders checkboxes whose values are the corresponding data key values (qiangxue) - Chg #1821: Changed default values for yii\db\Connection username and password to null (cebe) +- Chg #1844: `Response::sendFile()` and other file sending methods will not send the response (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) - Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index a28f381..660183d 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -121,6 +121,12 @@ class Response extends \yii\base\Response */ public $content; /** + * @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle, + * the begin position and the end position. Note that when this property is set, the [[data]] and [[content]] + * properties will be ignored by [[send()]]. + */ + public $stream; + /** * @var string the charset of the text response. If not set, it will use * the value of [[Application::charset]]. */ @@ -308,6 +314,7 @@ class Response extends \yii\base\Response $this->_statusCode = 200; $this->statusText = 'OK'; $this->data = null; + $this->stream = null; $this->content = null; $this->isSent = false; } @@ -361,14 +368,44 @@ class Response extends \yii\base\Response */ protected function sendContent() { - echo $this->content; + if ($this->stream === null) { + echo $this->content; + return; + } + + set_time_limit(0); // Reset time limit for big files + $chunkSize = 8 * 1024 * 1024; // 8MB per chunk + + if (is_array($this->stream)) { + list ($handle, $begin, $end) = $this->stream; + fseek($handle, $begin); + while (!feof($handle) && ($pos = ftell($handle)) <= $end) { + if ($pos + $chunkSize > $end) { + $chunkSize = $end - $pos + 1; + } + echo fread($handle, $chunkSize); + flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. + } + fclose($handle); + } else { + while (!feof($this->stream)) { + echo fread($this->stream, $chunkSize); + flush(); + } + fclose($this->stream); + } } /** * Sends a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * * @param string $filePath the path of the file to be sent. * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`. * @param string $mimeType the MIME type of the content. If null, it will be guessed based on `$filePath` + * @return static the response object itself */ public function sendFile($filePath, $attachmentName = null, $mimeType = null) { @@ -380,13 +417,20 @@ class Response extends \yii\base\Response } $handle = fopen($filePath, 'rb'); $this->sendStreamAsFile($handle, $attachmentName, $mimeType); + + return $this; } /** * Sends the specified content as a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * * @param string $content the content to be sent. The existing [[content]] will be discarded. * @param string $attachmentName the file name shown to the user. * @param string $mimeType the MIME type of the content. + * @return static the response object itself * @throws HttpException if the requested range is not satisfiable */ public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream') @@ -419,14 +463,20 @@ class Response extends \yii\base\Response } $this->format = self::FORMAT_RAW; - $this->send(); + + return $this; } /** * Sends the specified stream as a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * * @param resource $handle the handle of the stream to be sent. * @param string $attachmentName the file name shown to the user. * @param string $mimeType the MIME type of the stream content. + * @return static the response object itself * @throws HttpException if the requested range cannot be satisfied. */ public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream') @@ -460,20 +510,9 @@ class Response extends \yii\base\Response ->setDefault('Content-Length', $length) ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\""); $this->format = self::FORMAT_RAW; - $this->data = $this->content = null; - $this->send(); + $this->stream = [$handle, $begin, $end]; - fseek($handle, $begin); - set_time_limit(0); // Reset time limit for big files - $chunkSize = 8 * 1024 * 1024; // 8MB per chunk - while (!feof($handle) && ($pos = ftell($handle)) <= $end) { - if ($pos + $chunkSize > $end) { - $chunkSize = $end - $pos + 1; - } - echo fread($handle, $chunkSize); - flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. - } - fclose($handle); + return $this; } /** @@ -559,6 +598,7 @@ class Response extends \yii\base\Response * @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`. * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`. * @param string $xHeader the name of the x-sendfile header. + * @return static the response object itself */ public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile') { @@ -574,7 +614,7 @@ class Response extends \yii\base\Response ->setDefault('Content-Type', $mimeType) ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\""); - $this->send(); + return $this; } /** @@ -785,7 +825,7 @@ class Response extends \yii\base\Response */ protected function prepare() { - if ($this->data === null) { + if ($this->stream !== null || $this->data === null) { return; }