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 ...@@ -24,7 +24,6 @@ Yii Framework 2 Change Log
- Bug #1798: Fixed label attributes for array fields (zhuravljov) - Bug #1798: Fixed label attributes for array fields (zhuravljov)
- Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark) - 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 #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 `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: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
...@@ -69,6 +68,7 @@ Yii Framework 2 Change Log ...@@ -69,6 +68,7 @@ Yii Framework 2 Change Log
- Chg #1796: Removed `yii\base\Controller::getActionParams()` (samdark) - Chg #1796: Removed `yii\base\Controller::getActionParams()` (samdark)
- Chg #1835: `CheckboxColumn` now renders checkboxes whose values are the corresponding data key values (qiangxue) - 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 #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 `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (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 ...@@ -121,6 +121,12 @@ class Response extends \yii\base\Response
*/ */
public $content; 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 * @var string the charset of the text response. If not set, it will use
* the value of [[Application::charset]]. * the value of [[Application::charset]].
*/ */
...@@ -308,6 +314,7 @@ class Response extends \yii\base\Response ...@@ -308,6 +314,7 @@ class Response extends \yii\base\Response
$this->_statusCode = 200; $this->_statusCode = 200;
$this->statusText = 'OK'; $this->statusText = 'OK';
$this->data = null; $this->data = null;
$this->stream = null;
$this->content = null; $this->content = null;
$this->isSent = false; $this->isSent = false;
} }
...@@ -361,14 +368,44 @@ class Response extends \yii\base\Response ...@@ -361,14 +368,44 @@ class Response extends \yii\base\Response
*/ */
protected function sendContent() protected function sendContent()
{ {
if ($this->stream === null) {
echo $this->content; 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. * 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 $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 $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` * @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) public function sendFile($filePath, $attachmentName = null, $mimeType = null)
{ {
...@@ -380,13 +417,20 @@ class Response extends \yii\base\Response ...@@ -380,13 +417,20 @@ class Response extends \yii\base\Response
} }
$handle = fopen($filePath, 'rb'); $handle = fopen($filePath, 'rb');
$this->sendStreamAsFile($handle, $attachmentName, $mimeType); $this->sendStreamAsFile($handle, $attachmentName, $mimeType);
return $this;
} }
/** /**
* Sends the specified content as a file to the browser. * 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 $content the content to be sent. The existing [[content]] will be discarded.
* @param string $attachmentName the file name shown to the user. * @param string $attachmentName the file name shown to the user.
* @param string $mimeType the MIME type of the content. * @param string $mimeType the MIME type of the content.
* @return static the response object itself
* @throws HttpException if the requested range is not satisfiable * @throws HttpException if the requested range is not satisfiable
*/ */
public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream') public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream')
...@@ -419,14 +463,20 @@ class Response extends \yii\base\Response ...@@ -419,14 +463,20 @@ class Response extends \yii\base\Response
} }
$this->format = self::FORMAT_RAW; $this->format = self::FORMAT_RAW;
$this->send();
return $this;
} }
/** /**
* Sends the specified stream as a file to the browser. * 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 resource $handle the handle of the stream to be sent.
* @param string $attachmentName the file name shown to the user. * @param string $attachmentName the file name shown to the user.
* @param string $mimeType the MIME type of the stream content. * @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. * @throws HttpException if the requested range cannot be satisfied.
*/ */
public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream') public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream')
...@@ -460,20 +510,9 @@ class Response extends \yii\base\Response ...@@ -460,20 +510,9 @@ class Response extends \yii\base\Response
->setDefault('Content-Length', $length) ->setDefault('Content-Length', $length)
->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\""); ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
$this->format = self::FORMAT_RAW; $this->format = self::FORMAT_RAW;
$this->data = $this->content = null; $this->stream = [$handle, $begin, $end];
$this->send();
fseek($handle, $begin); return $this;
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);
} }
/** /**
...@@ -559,6 +598,7 @@ class Response extends \yii\base\Response ...@@ -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 $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 $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. * @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') public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile')
{ {
...@@ -574,7 +614,7 @@ class Response extends \yii\base\Response ...@@ -574,7 +614,7 @@ class Response extends \yii\base\Response
->setDefault('Content-Type', $mimeType) ->setDefault('Content-Type', $mimeType)
->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\""); ->setDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
$this->send(); return $this;
} }
/** /**
...@@ -785,7 +825,7 @@ class Response extends \yii\base\Response ...@@ -785,7 +825,7 @@ class Response extends \yii\base\Response
*/ */
protected function prepare() protected function prepare()
{ {
if ($this->data === null) { if ($this->stream !== null || $this->data === null) {
return; 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