Skip to content
Extraits de code Groupes Projets
Valider f169ee42 rédigé par diosmosis's avatar diosmosis
Parcourir les fichiers

Add ability to serve only part of a static file. Includes tests for new functionality.

parent 6c197044
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -56,8 +56,13 @@ class ProxyHttp ...@@ -56,8 +56,13 @@ class ProxyHttp
* @param string $contentType The content type of the static file. * @param string $contentType The content type of the static file.
* @param bool $expireFarFuture Day in the far future to set the Expires header to. * @param bool $expireFarFuture Day in the far future to set the Expires header to.
* Should be set to false for files that should not be cached. * Should be set to false for files that should not be cached.
* @param int|false $byteStart The starting byte in the file to serve. If false, the data from the beginning
* of the file will be served.
* @param int|false $byteEnd The ending byte in the file to serve. If false, the data from $byteStart to the
* end of the file will be served.
*/ */
public static function serverStaticFile($file, $contentType, $expireFarFutureDays = 100) public static function serverStaticFile($file, $contentType, $expireFarFutureDays = 100, $byteStart = false,
$byteEnd = false)
{ {
// if the file cannot be found return HTTP status code '404' // if the file cannot be found return HTTP status code '404'
if (!file_exists($file)) { if (!file_exists($file)) {
...@@ -65,7 +70,6 @@ class ProxyHttp ...@@ -65,7 +70,6 @@ class ProxyHttp
return; return;
} }
// conditional GET
$modifiedSince = Http::getModifiedSinceHeader(); $modifiedSince = Http::getModifiedSinceHeader();
$fileModifiedTime = @filemtime($file); $fileModifiedTime = @filemtime($file);
...@@ -88,6 +92,14 @@ class ProxyHttp ...@@ -88,6 +92,14 @@ class ProxyHttp
} }
// if we have to serve the file, serve it now, either in the clear or compressed // if we have to serve the file, serve it now, either in the clear or compressed
if ($byteStart === false) {
$byteStart = 0;
}
if ($byteEnd === false) {
$byteEnd = filesize($file);
}
$compressed = false; $compressed = false;
$encoding = ''; $encoding = '';
$compressedFileLocation = AssetManager::getInstance()->getAssetDirectory() . '/' . basename($file); $compressedFileLocation = AssetManager::getInstance()->getAssetDirectory() . '/' . basename($file);
...@@ -102,11 +114,14 @@ class ProxyHttp ...@@ -102,11 +114,14 @@ class ProxyHttp
// compress the file if it doesn't exist or is newer than the existing cached file, and cache // compress the file if it doesn't exist or is newer than the existing cached file, and cache
// the compressed result // the compressed result
if (self::shouldCompressFile($file, $filegz)) { if (self::shouldCompressFile($file, $filegz)) {
self::compressFile($file, $filegz, $encoding); self::compressFile($file, $filegz, $encoding, $byteStart, $byteEnd);
} }
$compressed = true; $compressed = true;
$file = $filegz; $file = $filegz;
$byteStart = 0;
$byteEnd = filesize($file);
} }
} else { } else {
// if a compressed file exists, the file was manually compressed so we just serve that // if a compressed file exists, the file was manually compressed so we just serve that
...@@ -115,6 +130,9 @@ class ProxyHttp ...@@ -115,6 +130,9 @@ class ProxyHttp
) { ) {
$compressed = true; $compressed = true;
$file = $filegz; $file = $filegz;
$byteStart = 0;
$byteEnd = filesize($file);
} }
} }
} }
...@@ -122,7 +140,7 @@ class ProxyHttp ...@@ -122,7 +140,7 @@ class ProxyHttp
@header('Last-Modified: ' . $lastModified); @header('Last-Modified: ' . $lastModified);
if (!$phpOutputCompressionEnabled) { if (!$phpOutputCompressionEnabled) {
@header('Content-Length: ' . filesize($file)); @header('Content-Length: ' . ($byteEnd - $byteStart));
} }
if (!empty($contentType)) { if (!empty($contentType)) {
...@@ -133,7 +151,7 @@ class ProxyHttp ...@@ -133,7 +151,7 @@ class ProxyHttp
@header('Content-Encoding: ' . $encoding); @header('Content-Encoding: ' . $encoding);
} }
if (!_readfile($file)) { if (!_readfile($file, $byteStart, $byteEnd)) {
self::setHttpStatus('505 Internal server error'); self::setHttpStatus('505 Internal server error');
} }
} }
...@@ -248,9 +266,11 @@ class ProxyHttp ...@@ -248,9 +266,11 @@ class ProxyHttp
return !file_exists($compressedFilePath) || ($toCompressLastModified > $compressedLastModified); return !file_exists($compressedFilePath) || ($toCompressLastModified > $compressedLastModified);
} }
private static function compressFile($fileToCompress, $compressedFilePath, $compressionEncoding) private static function compressFile($fileToCompress, $compressedFilePath, $compressionEncoding, $byteStart,
$byteEnd)
{ {
$data = file_get_contents($fileToCompress); $data = file_get_contents($fileToCompress);
$data = substr($data, $byteStart, $byteEnd - $byteStart);
if ($compressionEncoding == 'deflate') { if ($compressionEncoding == 'deflate') {
$data = gzdeflate($data, 9); $data = gzdeflate($data, 9);
......
...@@ -612,20 +612,27 @@ function safe_unserialize( $str ) ...@@ -612,20 +612,27 @@ function safe_unserialize( $str )
* @param resource $context * @param resource $context
* @return int the number of bytes read from the file, or false if an error occurs * @return int the number of bytes read from the file, or false if an error occurs
*/ */
function _readfile($filename, $useIncludePath = false, $context = null) function _readfile($filename, $byteStart, $byteEnd, $useIncludePath = false, $context = null)
{ {
$count = @filesize($filename); $count = @filesize($filename);
// built-in function has a 2 MB limit when using mmap // built-in function has a 2 MB limit when using mmap
if (function_exists('readfile') && $count <= (2 * 1024 * 1024)) { if (function_exists('readfile')
&& $count <= (2 * 1024 * 1024)
&& $byteStart == 0
&& $byteEnd == $count
) {
return @readfile($filename, $useIncludePath, $context); return @readfile($filename, $useIncludePath, $context);
} }
// when in doubt (or when readfile() function is disabled) // when in doubt (or when readfile() function is disabled)
$handle = @fopen($filename, SettingsServer::isWindows() ? "rb" : "r"); $handle = @fopen($filename, SettingsServer::isWindows() ? "rb" : "r");
if ($handle) { if ($handle) {
while(!feof($handle)) { fseek($handle, $byteStart);
echo fread($handle, 8192);
for ($pos = $byteStart; $pos < $byteEnd && !feof($handle); $pos = ftell($handle)) {
echo fread($handle, min(8192, $byteEnd - $pos));
ob_flush(); ob_flush();
flush(); flush();
} }
......
...@@ -36,8 +36,16 @@ define("UNIT_TEST_MODE", "unitTestMode"); ...@@ -36,8 +36,16 @@ define("UNIT_TEST_MODE", "unitTestMode");
define("NULL_FILE_SRV_MODE", "nullFile"); define("NULL_FILE_SRV_MODE", "nullFile");
define("GHOST_FILE_SRV_MODE", "ghostFile"); define("GHOST_FILE_SRV_MODE", "ghostFile");
define("TEST_FILE_SRV_MODE", "testFile"); define("TEST_FILE_SRV_MODE", "testFile");
define("PARTIAL_TEST_FILE_SRV_MODE", "partialTestFile");
define("WHOLE_TEST_FILE_WITH_RANGE_SRV_MODE", "wholeTestFileWithRange");
define("PARTIAL_BYTE_START", 1204);
define("PARTIAL_BYTE_END", 14724);
// If the static file server has not been requested, the standard unit test case class is defined // If the static file server has not been requested, the standard unit test case class is defined
/**
* @group ServeStaticFileTest
*/
class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
{ {
public function tearDown() public function tearDown()
...@@ -388,6 +396,90 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase ...@@ -388,6 +396,90 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
$this->removeCompressedFiles(); $this->removeCompressedFiles();
} }
/**
* @group Core
*/
public function test_partialFileServeNoCompression()
{
$this->removeCompressedFiles();
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_URL, $this->getPartialTestFileSrvModeUrl());
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
$partialResponse = curl_exec($curlHandle);
$responseInfo = curl_getinfo($curlHandle);
curl_close($curlHandle);
clearstatcache();
// check no compressed files created
$this->assertFalse(file_exists($this->getCompressedFileLocation() . ".deflate"));
$this->assertFalse(file_exists($this->getCompressedFileLocation() . ".gz"));
// check $partialResponse
$this->assertEquals(PARTIAL_BYTE_END - PARTIAL_BYTE_START, $responseInfo["size_download"]);
$expectedPartialContents = substr(file_get_contents(TEST_FILE_LOCATION), PARTIAL_BYTE_START,
PARTIAL_BYTE_END - PARTIAL_BYTE_START);
$this->assertEquals($expectedPartialContents, $partialResponse);
}
/**
* @group Core
*/
public function test_partialFileServeWithCompression()
{
$this->removeCompressedFiles();
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_URL, $this->getPartialTestFileSrvModeUrl());
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_ENCODING, "deflate");
$partialResponse = curl_exec($curlHandle);
$responseInfo = curl_getinfo($curlHandle);
curl_close($curlHandle);
clearstatcache();
// check the correct compressed file is created
$this->assertTrue(file_exists($this->getCompressedFileLocation() . ".deflate"));
$this->assertFalse(file_exists($this->getCompressedFileLocation() . ".gz"));
// check $partialResponse
$expectedPartialContents = substr(file_get_contents(TEST_FILE_LOCATION), PARTIAL_BYTE_START,
PARTIAL_BYTE_END - PARTIAL_BYTE_START);
$this->assertEquals($expectedPartialContents, $partialResponse);
$this->removeCompressedFiles();
}
/**
* @group Core
*/
public function test_wholeFileServeWithByteRange()
{
$this->removeCompressedFiles();
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_URL, $this->getWholeTestFileWithRangeSrvModeUrl());
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_ENCODING, "deflate");
$fullResponse = curl_exec($curlHandle);
$responseInfo = curl_getinfo($curlHandle);
curl_close($curlHandle);
clearstatcache();
// check the correct compressed file is created
$this->assertTrue(file_exists($this->getCompressedFileLocation() . ".deflate"));
$this->assertFalse(file_exists($this->getCompressedFileLocation() . ".gz"));
// check $fullResponse
$this->assertEquals(file_get_contents(TEST_FILE_LOCATION), $fullResponse);
$this->removeCompressedFiles();
}
/** /**
* Helper methods * Helper methods
*/ */
...@@ -415,6 +507,16 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase ...@@ -415,6 +507,16 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
return $this->getStaticSrvUrl() . TEST_FILE_SRV_MODE; return $this->getStaticSrvUrl() . TEST_FILE_SRV_MODE;
} }
private function getPartialTestFileSrvModeUrl()
{
return $this->getStaticSrvUrl() . PARTIAL_TEST_FILE_SRV_MODE;
}
private function getWholeTestFileWithRangeSrvModeUrl()
{
return $this->getStaticSrvUrl() . WHOLE_TEST_FILE_WITH_RANGE_SRV_MODE;
}
private function setZlibOutputRequest($url) private function setZlibOutputRequest($url)
{ {
return $url . "&" . ZLIB_OUTPUT_REQUEST_VAR . "=1"; return $url . "&" . ZLIB_OUTPUT_REQUEST_VAR . "=1";
......
...@@ -54,6 +54,11 @@ define("ZLIB_OUTPUT_REQUEST_VAR", "zlibOutput"); ...@@ -54,6 +54,11 @@ define("ZLIB_OUTPUT_REQUEST_VAR", "zlibOutput");
define("NULL_FILE_SRV_MODE", "nullFile"); define("NULL_FILE_SRV_MODE", "nullFile");
define("GHOST_FILE_SRV_MODE", "ghostFile"); define("GHOST_FILE_SRV_MODE", "ghostFile");
define("TEST_FILE_SRV_MODE", "testFile"); define("TEST_FILE_SRV_MODE", "testFile");
define("PARTIAL_TEST_FILE_SRV_MODE", "partialTestFile");
define("WHOLE_TEST_FILE_WITH_RANGE_SRV_MODE", "wholeTestFileWithRange");
define("PARTIAL_BYTE_START", 1204);
define("PARTIAL_BYTE_END", 14724);
/** /**
...@@ -89,4 +94,14 @@ switch ($staticFileServerMode) { ...@@ -89,4 +94,14 @@ switch ($staticFileServerMode) {
ProxyHttp::serverStaticFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE); ProxyHttp::serverStaticFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE);
break; break;
case PARTIAL_TEST_FILE_SRV_MODE:
ProxyHttp::serverStaticFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE, $expireFarFutureDays = 100, PARTIAL_BYTE_START, PARTIAL_BYTE_END);
break;
case WHOLE_TEST_FILE_WITH_RANGE_SRV_MODE:
ProxyHttp::serverStaticFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE, $expireFarFutureDays = 100, 0, filesize(TEST_FILE_LOCATION));
break;
} }
\ No newline at end of file
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter