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

Refactoring ProxyStaticFile class:

Small static file server proxy, with compression, caching.
 Used to server piwik.js and the merged+minified CSS and JS files
parent 6ea0fe5c
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
......@@ -12,13 +12,8 @@ namespace Piwik;
use Exception;
use JSMin;
use Piwik\Config;
use Piwik\Piwik;
use Piwik\Common;
use Piwik\Version;
use Piwik\PluginsManager;
use Piwik\Translate;
use lessc;
use Piwik\Translate;
/**
* @see libs/jsmin/jsmin.php
......@@ -492,7 +487,7 @@ class AssetManager
* Also tries to remove compressed version of the merged file.
*
* @param string $filename filename of the merged asset
* @see Piwik::serveStaticFile()
* @see ProxyStaticFile::serveStaticFile(serveFile
* @throws Exception if the file couldn't be deleted
*/
private static function removeMergedAsset($filename)
......@@ -505,7 +500,7 @@ class AssetManager
}
// Tries to remove compressed version of the merged file.
// See Piwik::serveStaticFile() for more info on static file compression
// See Piwik::serveFile() for more info on static file compression
$compressedFileLocation = PIWIK_USER_PATH . Piwik::COMPRESSED_FILE_LOCATION . $filename;
@unlink($compressedFileLocation . ".deflate");
......
......@@ -188,22 +188,6 @@ class Piwik
return Url::getCurrentScheme() === 'https';
}
/**
* Set response header, e.g., HTTP/1.0 200 Ok
*
* @param string $status Status
* @return bool
*/
static public function setHttpStatus($status)
{
if (substr_compare(PHP_SAPI, '-fcgi', -5)) {
@header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status);
} else {
// FastCGI
@header('Status: ' . $status);
}
}
/**
* Workaround IE bug when downloading certain document types over SSL and
* cache control headers are present, e.g.,
......@@ -665,163 +649,6 @@ class Piwik
return $messages;
}
/**
* Test if php output is compressed
*
* @return bool True if php output is (or suspected/likely) to be compressed
*/
static public function isPhpOutputCompressed()
{
// Off = ''; On = '1'; otherwise, it's a buffer size
$zlibOutputCompression = ini_get('zlib.output_compression');
// could be ob_gzhandler, ob_deflatehandler, etc
$outputHandler = ini_get('output_handler');
// output handlers can be stacked
$obHandlers = array_filter(ob_list_handlers(), function($var) {
return $var !== "default output handler";
});
// user defined handler via wrapper
$autoPrependFile = ini_get('auto_prepend_file');
$autoAppendFile = ini_get('auto_append_file');
return !empty($zlibOutputCompression) ||
!empty($outputHandler) ||
!empty($obHandlers) ||
!empty($autoPrependFile) ||
!empty($autoAppendFile);
}
/**
* Serve static files through php proxy.
*
* It performs the following actions:
* - Checks the file is readable or returns "HTTP/1.0 404 Not Found"
* - Returns "HTTP/1.1 304 Not Modified" after comparing the HTTP_IF_MODIFIED_SINCE
* with the modification date of the static file
* - Will try to compress the static file according to HTTP_ACCEPT_ENCODING. Compressed files are store in
* the /tmp directory. If compressing extensions are not available, a manually gzip compressed file
* can be provided in the /tmp directory. It has to bear the same name with an added .gz extension.
* Using manually compressed static files requires you to manually update the compressed file when
* the static file is updated.
* - Overrides server cache control config to allow caching
* - Sends Very Accept-Encoding to tell proxies to store different version of the static file according
* to users encoding capacities.
*
* Warning:
* Compressed filed are stored in the /tmp directory.
* If this method is used with two files bearing the same name but located in different locations,
* there is a risk of conflict. One file could be served with the content of the other.
* A future upgrade of this method would be to recreate the directory structure of the static file
* within a /tmp/compressed-static-files directory.
*
* @param string $file The location of the static file to serve
* @param string $contentType The content type of the static file.
* @param bool $expireFarFuture If set to true, will set Expires: header in far future.
* Should be set to false for files that don't have a cache buster (eg. piwik.js)
*/
static public function serveStaticFile($file, $contentType, $expireFarFuture = true)
{
if (file_exists($file)) {
// conditional GET
$modifiedSince = '';
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
// strip any trailing data appended to header
if (false !== ($semicolon = strpos($modifiedSince, ';'))) {
$modifiedSince = substr($modifiedSince, 0, $semicolon);
}
}
$fileModifiedTime = @filemtime($file);
$lastModified = gmdate('D, d M Y H:i:s', $fileModifiedTime) . ' GMT';
// set HTTP response headers
self::overrideCacheControlHeaders('public');
@header('Vary: Accept-Encoding');
@header('Content-Disposition: inline; filename=' . basename($file));
if ($expireFarFuture) {
// Required by proxy caches potentially in between the browser and server to cache the request indeed
@header("Expires: " . gmdate('D, d M Y H:i:s', time() + 86400 * 100) . ' GMT');
}
// Returns 304 if not modified since
if ($modifiedSince === $lastModified) {
self::setHttpStatus('304 Not Modified');
} else {
// optional compression
$compressed = false;
$encoding = '';
$compressedFileLocation = PIWIK_USER_PATH . self::COMPRESSED_FILE_LOCATION . basename($file);
$phpOutputCompressionEnabled = self::isPhpOutputCompressed();
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && !$phpOutputCompressionEnabled) {
$acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
if (extension_loaded('zlib') && function_exists('file_get_contents') && function_exists('file_put_contents')) {
if (preg_match('/(?:^|, ?)(deflate)(?:,|$)/', $acceptEncoding, $matches)) {
$encoding = 'deflate';
$filegz = $compressedFileLocation . '.deflate';
} else if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches)) {
$encoding = $matches[1];
$filegz = $compressedFileLocation . '.gz';
}
if (!empty($encoding)) {
// compress-on-demand and use cache
if (!file_exists($filegz) || ($fileModifiedTime > @filemtime($filegz))) {
$data = file_get_contents($file);
if ($encoding == 'deflate') {
$data = gzdeflate($data, 9);
} else if ($encoding == 'gzip' || $encoding == 'x-gzip') {
$data = gzencode($data, 9);
}
file_put_contents($filegz, $data);
}
$compressed = true;
$file = $filegz;
}
} else {
// manually compressed
$filegz = $compressedFileLocation . '.gz';
if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches) && file_exists($filegz) && ($fileModifiedTime < @filemtime($filegz))) {
$encoding = $matches[1];
$compressed = true;
$file = $filegz;
}
}
}
@header('Last-Modified: ' . $lastModified);
if (!$phpOutputCompressionEnabled) {
@header('Content-Length: ' . filesize($file));
}
if (!empty($contentType)) {
@header('Content-Type: ' . $contentType);
}
if ($compressed) {
@header('Content-Encoding: ' . $encoding);
}
if (!_readfile($file)) {
self::setHttpStatus('505 Internal server error');
}
}
} else {
self::setHttpStatus('404 Not Found');
}
}
/**
* Create CSV (or other delimited) files
*
......@@ -1734,7 +1561,7 @@ class Piwik
static public function disconnectDatabase()
{
\Zend_Registry::get('db')->closeConnection();
}
}
/**
* Checks the database server version against the required minimum
......
......@@ -5,7 +5,7 @@
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
use Piwik\Piwik;
use Piwik\ProxyStaticFile;
/**
* Tracker proxy
......@@ -31,6 +31,6 @@ $file = '../piwik.js';
// There is no cache buster parameter so we don't set Expires: header
$expireFarFuture = false;
Piwik::serveStaticFile($file, "application/javascript; charset=UTF-8", $expireFarFuture);
ProxyStaticFile::serveFile($file, "application/javascript; charset=UTF-8", $expireFarFuture);
exit;
......@@ -10,9 +10,10 @@
*/
namespace Piwik\Plugins\Proxy;
use Piwik\Piwik;
use Piwik\Common;
use Piwik\AssetManager;
use Piwik\Common;
use Piwik\Piwik;
use Piwik\ProxyStaticFile;
use Piwik\Url;
/**
......@@ -34,7 +35,7 @@ class Controller extends \Piwik\Controller
public function getCss()
{
$cssMergedFile = AssetManager::getMergedCssFileLocation();
Piwik::serveStaticFile($cssMergedFile, "text/css");
ProxyStaticFile::serveFile($cssMergedFile, "text/css");
}
/**
......@@ -46,7 +47,7 @@ class Controller extends \Piwik\Controller
public function getJs()
{
$jsMergedFile = AssetManager::getMergedJsFileLocation();
Piwik::serveStaticFile($jsMergedFile, self::JS_MIME_TYPE);
ProxyStaticFile::serveFile($jsMergedFile, self::JS_MIME_TYPE);
}
/**
......@@ -64,7 +65,7 @@ class Controller extends \Piwik\Controller
AssetManager::removeTranslationsJsFile();
$translationsJsFile = AssetManager::getTranslationsJsFileLocation();
Piwik::serveStaticFile($translationsJsFile, self::JS_MIME_TYPE);
ProxyStaticFile::serveFile($translationsJsFile, self::JS_MIME_TYPE);
}
/**
......
<?php
/**
* This php file is used to unit test Piwik::serveStaticFile()
* This php file is used to unit test Piwik::serveFile()
* Unit tests for this method should normally be located in /tests/core/Piwik.test.php
* To make a comprehensive test suit for Piwik::serveStaticFile() (ie. being able to test combinations of request
* To make a comprehensive test suit for Piwik::serveFile() (ie. being able to test combinations of request
* headers, being able to test response headers and so on) we need to simulate static file requests in a controlled
* environment
* The php code which simulates requests using Piwik::serveStaticFile() is provided in the same file (ie. this one)
* as the unit testing code for Piwik::serveStaticFile()
* The php code which simulates requests using Piwik::serveFile() is provided in the same file (ie. this one)
* as the unit testing code for Piwik::serveFile()
* This decision has a structural impact on the usual unit test file structure
* serveStaticFile.test.php has been created to avoid making too many modifications to /tests/core/Piwik.test.php
* serveFile.test.php has been created to avoid making too many modifications to /tests/core/Piwik.test.php
*/
// This is Piwik logo, the static file used in this test suit
use Piwik\Piwik;
use Piwik\Common;
use Piwik\Piwik;
use Piwik\ProxyStaticFile;
define("TEST_FILE_LOCATION", realpath(dirname(__FILE__) . "/../../resources/lipsum.txt"));
define("TEST_FILE_CONTENT_TYPE", "text/plain");
......@@ -25,7 +26,7 @@ define("ZLIB_OUTPUT_REQUEST_VAR", "zlibOutput");
/**
* These constants define the mode in which this php file is used :
* - for unit testing Piwik::serveStaticFile() or
* - for unit testing Piwik::serveFile() or
* - as a static file server
*/
define("STATIC_SERVER_MODE", "staticServerMode");
......@@ -52,11 +53,11 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
*/
public function test_phpOutputCompression()
{
$this->assertFalse(Piwik::isPhpOutputCompressed());
$this->assertFalse(ProxyStaticFile::isPhpOutputCompressed());
}
/**
* Checks that "HTTP/1.0 404 Not Found" is returned when Piwik::serveStaticFile is called with a null file
* Checks that "HTTP/1.0 404 Not Found" is returned when Piwik::serveFile is called with a null file
*
* @group ServeStaticFile
*/
......@@ -73,7 +74,7 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
}
/**
* Checks that "HTTP/1.0 404 Not Found" is returned when Piwik::serveStaticFile is called with a non existing file
* Checks that "HTTP/1.0 404 Not Found" is returned when Piwik::serveFile is called with a non existing file
*
* @group ServeStaticFile
*/
......@@ -90,7 +91,7 @@ class Test_Piwik_ServeStaticFile extends PHPUnit_Framework_TestCase
}
/**
* Checks that "HTTP/1.0 505 Internal server error" is returned when Piwik::serveStaticFile is called with a
* Checks that "HTTP/1.0 505 Internal server error" is returned when Piwik::serveFile is called with a
* non-readable file
*
* @group ServeStaticFile
......
<?php
/**
* This php file is used to unit test Piwik::serveStaticFile()
* To make a comprehensive test suit for Piwik::serveStaticFile() (ie. being able to test combinations of request
* This php file is used to unit test Piwik::serveFile()
* To make a comprehensive test suit for Piwik::serveFile() (ie. being able to test combinations of request
* headers, being able to test response headers and so on) we need to simulate static file requests in a controlled
* environment
* The php code which simulates requests using Piwik::serveStaticFile() is provided in the same file (ie. this one)
* as the unit testing code for Piwik::serveStaticFile()
* The php code which simulates requests using Piwik::serveFile() is provided in the same file (ie. this one)
* as the unit testing code for Piwik::serveFile()
* This decision has a structural impact on the usual unit test file structure
* serveStaticFile.test.php has been created to avoid making too many modifications to /tests/core/Piwik.test.php
* serveFile.test.php has been created to avoid making too many modifications to /tests/core/Piwik.test.php
*/
use Piwik\Piwik;
use Piwik\Common;
use Piwik\ProxyStaticFile;
define('PIWIK_DOCUMENT_ROOT', dirname(__FILE__).'/../../');
if(file_exists(PIWIK_DOCUMENT_ROOT . '/bootstrap.php'))
......@@ -58,7 +58,7 @@ define("TEST_FILE_SRV_MODE", "testFile");
/**
* If the static file server has been requested, the response sent back to the browser will be the content produced by
* the execution of Piwik:serveStaticFile(). In this case, unit tests won't be executed
* the execution of Piwik:serveFile(). In this case, unit tests won't be executed
*/
// Getting the server mode
$staticFileServerMode = Common::getRequestVar(SRV_MODE_REQUEST_VAR, "");
......@@ -72,21 +72,21 @@ if ($staticFileServerMode === "") {
}
switch ($staticFileServerMode) {
// The static file server calls Piwik::serveStaticFile with a null file
// The static file server calls Piwik::serveFile with a null file
case NULL_FILE_SRV_MODE:
Piwik::serveStaticFile(null, TEST_FILE_CONTENT_TYPE);
ProxyStaticFile::serveFile(null, TEST_FILE_CONTENT_TYPE);
break;
// The static file server calls Piwik::serveStaticFile with a non-existing file
// The static file server calls Piwik::serveFile with a non-existing file
case GHOST_FILE_SRV_MODE:
Piwik::serveStaticFile(TEST_FILE_LOCATION . ".ghost", TEST_FILE_CONTENT_TYPE);
ProxyStaticFile::serveFile(TEST_FILE_LOCATION . ".ghost", TEST_FILE_CONTENT_TYPE);
break;
// The static file server calls Piwik::serveStaticFile with the test file
// The static file server calls Piwik::serveFile with the test file
case TEST_FILE_SRV_MODE:
Piwik::serveStaticFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE);
ProxyStaticFile::serveFile(TEST_FILE_LOCATION, TEST_FILE_CONTENT_TYPE);
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