Commit c6c51baa by Qiang Xue

Fixes #1844: `Response::sendFile()` and other file sending methods will not send the response

parent 35f88232
......@@ -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)
......
......@@ -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()
{
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;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment