mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 14:46:19 +08:00 
			
		
		
		
	Merge branch 'Ragazzo-partial-response-enh'
* Ragazzo-partial-response-enh: refactored web/Response::sendFile() Display Name of HttpException instead of classname string helper fixed, mime-type reverted code style fix partial response added, new code-style applied
This commit is contained in:
		
							
								
								
									
										86
									
								
								tests/unit/framework/web/ResponseTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tests/unit/framework/web/ResponseTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace yii\web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use yiiunit\framework\web\ResponseTest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mock PHP header function to check for sent headers
 | 
				
			||||||
 | 
					 * @param string $string
 | 
				
			||||||
 | 
					 * @param bool $replace
 | 
				
			||||||
 | 
					 * @param int $httpResponseCode
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function header($string, $replace = true, $httpResponseCode = null) {
 | 
				
			||||||
 | 
						ResponseTest::$headers[] = $string;
 | 
				
			||||||
 | 
						// TODO implement replace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ($httpResponseCode !== null) {
 | 
				
			||||||
 | 
							ResponseTest::$httpResponseCode = $httpResponseCode;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace yiiunit\framework\web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use yii\helpers\StringHelper;
 | 
				
			||||||
 | 
					use yii\web\Response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ResponseTest extends \yiiunit\TestCase
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public static $headers = array();
 | 
				
			||||||
 | 
						public static $httpResponseCode = 200;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected function setUp()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							parent::setUp();
 | 
				
			||||||
 | 
							$this->reset();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected function reset()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							static::$headers = array();
 | 
				
			||||||
 | 
							static::$httpResponseCode = 200;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public function ranges()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							// TODO test more cases for range requests and check for rfc compatibility
 | 
				
			||||||
 | 
							// http://www.w3.org/Protocols/rfc2616/rfc2616.txt
 | 
				
			||||||
 | 
							return array(
 | 
				
			||||||
 | 
								array('0-5', '0-5', 6, '12ёж'),
 | 
				
			||||||
 | 
								array('2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'),
 | 
				
			||||||
 | 
								array('-12', '55-66', 12, '(ёжик)=?'),
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @dataProvider ranges
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedFile)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							$content = $this->generateTestFileContent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
 | 
				
			||||||
 | 
							$sent = $this->runSendFile('testFile.txt', $content, null);
 | 
				
			||||||
 | 
							$this->assertEquals($expectedFile, $sent);
 | 
				
			||||||
 | 
							$this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers));
 | 
				
			||||||
 | 
							$this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers));
 | 
				
			||||||
 | 
							$this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers));
 | 
				
			||||||
 | 
							$this->assertTrue(in_array('Content-Type: text/plain', static::$headers));
 | 
				
			||||||
 | 
							$this->assertTrue(in_array('Content-Length: ' . $length, static::$headers));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected function generateTestFileContent()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected function runSendFile($fileName, $content, $mimeType)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							ob_start();
 | 
				
			||||||
 | 
							ob_implicit_flush(false);
 | 
				
			||||||
 | 
							$response = new Response();
 | 
				
			||||||
 | 
							$response->sendFile($fileName, $content, $mimeType, false);
 | 
				
			||||||
 | 
							$file = ob_get_clean();
 | 
				
			||||||
 | 
							return $file;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -75,8 +75,11 @@ class ErrorHandler extends Component
 | 
				
			|||||||
			\Yii::$app->runAction($this->errorAction);
 | 
								\Yii::$app->runAction($this->errorAction);
 | 
				
			||||||
		} elseif (\Yii::$app instanceof \yii\web\Application) {
 | 
							} elseif (\Yii::$app instanceof \yii\web\Application) {
 | 
				
			||||||
			if (!headers_sent()) {
 | 
								if (!headers_sent()) {
 | 
				
			||||||
				$errorCode = $exception instanceof HttpException ? $exception->statusCode : 500;
 | 
									if ($exception instanceof HttpException) {
 | 
				
			||||||
				header("HTTP/1.0 $errorCode " . get_class($exception));
 | 
										header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName());
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										header('HTTP/1.0 500 ' . get_class($exception));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
 | 
								if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
 | 
				
			||||||
				\Yii::$app->renderException($exception);
 | 
									\Yii::$app->renderException($exception);
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,10 @@
 | 
				
			|||||||
namespace yii\web;
 | 
					namespace yii\web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Yii;
 | 
					use Yii;
 | 
				
			||||||
 | 
					use yii\base\HttpException;
 | 
				
			||||||
use yii\helpers\FileHelper;
 | 
					use yii\helpers\FileHelper;
 | 
				
			||||||
use yii\helpers\Html;
 | 
					use yii\helpers\Html;
 | 
				
			||||||
 | 
					use yii\helpers\StringHelper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author Qiang Xue <qiang.xue@gmail.com>
 | 
					 * @author Qiang Xue <qiang.xue@gmail.com>
 | 
				
			||||||
@ -31,27 +33,76 @@ class Response extends \yii\base\Response
 | 
				
			|||||||
	 * @param string $content content to be set.
 | 
						 * @param string $content content to be set.
 | 
				
			||||||
	 * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
 | 
						 * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
 | 
				
			||||||
	 * @param boolean $terminate whether to terminate the current application after calling this method
 | 
						 * @param boolean $terminate whether to terminate the current application after calling this method
 | 
				
			||||||
	 * @todo
 | 
						 * @throws \yii\base\HttpException when range request is not satisfiable.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public function sendFile($fileName, $content, $mimeType = null, $terminate = true)
 | 
						public function sendFile($fileName, $content, $mimeType = null, $terminate = true)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		if ($mimeType === null && ($mimeType = FileHelper::getMimeType($fileName)) === null) {
 | 
							if ($mimeType === null && (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null)) {
 | 
				
			||||||
			$mimeType = 'application/octet-stream';
 | 
								$mimeType = 'application/octet-stream';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							$fileSize = StringHelper::strlen($content);
 | 
				
			||||||
 | 
							$contentStart = 0;
 | 
				
			||||||
 | 
							$contentEnd = $fileSize - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// tell the client that we accept range requests
 | 
				
			||||||
 | 
							header('Accept-Ranges: bytes');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (isset($_SERVER['HTTP_RANGE'])) {
 | 
				
			||||||
 | 
								// client sent us a multibyte range, can not hold this one for now
 | 
				
			||||||
 | 
								if (strpos(',', $_SERVER['HTTP_RANGE']) !== false) {
 | 
				
			||||||
 | 
									header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 | 
				
			||||||
 | 
									throw new HttpException(416, 'Requested Range Not Satisfiable');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								$range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// range requests starts from "-", so it means that data must be dumped the end point.
 | 
				
			||||||
 | 
								if ($range[0] === '-') {
 | 
				
			||||||
 | 
									$contentStart = $fileSize - substr($range, 1);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									$range = explode('-', $range);
 | 
				
			||||||
 | 
									$contentStart = $range[0];
 | 
				
			||||||
 | 
									$contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize - 1;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/* Check the range and make sure it's treated according to the specs.
 | 
				
			||||||
 | 
								 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								// End bytes can not be larger than $end.
 | 
				
			||||||
 | 
								$contentEnd = ($contentEnd > $fileSize) ? $fileSize : $contentEnd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Validate the requested range and return an error if it's not correct.
 | 
				
			||||||
 | 
								$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ($wrongContentStart) {   
 | 
				
			||||||
 | 
									header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 | 
				
			||||||
 | 
									throw new HttpException(416, 'Requested Range Not Satisfiable');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								header('HTTP/1.1 206 Partial Content');
 | 
				
			||||||
 | 
								header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								header('HTTP/1.1 200 OK');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							$length = $contentEnd - $contentStart + 1; // Calculate new content length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		header('Pragma: public');
 | 
							header('Pragma: public');
 | 
				
			||||||
		header('Expires: 0');
 | 
							header('Expires: 0');
 | 
				
			||||||
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
 | 
							header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
 | 
				
			||||||
		header("Content-type: $mimeType");
 | 
							header('Content-Type: ' . $mimeType);
 | 
				
			||||||
		if (ob_get_length() === false) {
 | 
							header('Content-Length: ' . $length);
 | 
				
			||||||
			header('Content-Length: ' . (function_exists('mb_strlen') ? mb_strlen($content, '8bit') : strlen($content)));
 | 
							header('Content-Disposition: attachment; filename="' . $fileName . '"');
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		header("Content-Disposition: attachment; filename=\"$fileName\"");
 | 
					 | 
				
			||||||
		header('Content-Transfer-Encoding: binary');
 | 
							header('Content-Transfer-Encoding: binary');
 | 
				
			||||||
 | 
							$content = StringHelper::substr($content, $contentStart, $length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ($terminate) {
 | 
							if ($terminate) {
 | 
				
			||||||
			// clean up the application first because the file downloading could take long time
 | 
								// clean up the application first because the file downloading could take long time
 | 
				
			||||||
			// which may cause timeout of some resources (such as DB connection)
 | 
								// which may cause timeout of some resources (such as DB connection)
 | 
				
			||||||
			Yii::app()->end(0, false);
 | 
								ob_start();
 | 
				
			||||||
 | 
								Yii::$app->end(0, false);
 | 
				
			||||||
 | 
								ob_end_clean();
 | 
				
			||||||
			echo $content;
 | 
								echo $content;
 | 
				
			||||||
			exit(0);
 | 
								exit(0);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user