From adc9a958afa5fb9f6f2dab4ae8abac1f932a7db4 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 3 Nov 2013 20:28:52 +0100 Subject: Préchargement et requêtes conditionnelles HTTP/1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grosse amélioration des performances en utilisant le cache HTTP : - Implémentation de HTTP/1.1, c.a.d. If-Modified-Since, If-None-Match, If-Unmodified-Since, If-Match... avec la librairie http://alexandre.alapetite.fr/doc-alex/php-http-304/ - Support de HEAD (HTTP /1.0). - Préchargement de la page suivante (avec link next prefetch) dans le cas de pagination. - Et nouvelle possibilité de navigation pour les navigateurs qui supportent "next". - La date de dernier changement est pour l'instant primitive et correspond au dernier changement de la session PHP ou Configuration.array.php ou application.log ou touch.txt. - touch.txt est modifié a chaque requête UPDATE ou INSERT ou DELETE. --- actualize_script.php | 1 + app/layout/layout.phtml | 9 +- app/models/RSSPaginator.php | 4 + lib/http-conditional.php | 207 ++++++++++++++++++++++++++++++++++++++++++++ lib/minz/dao/Model_pdo.php | 20 ++++- public/index.php | 10 +++ public/scripts/main.js | 5 ++ 7 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 lib/http-conditional.php diff --git a/actualize_script.php b/actualize_script.php index 7f72e419e..45b2938cb 100755 --- a/actualize_script.php +++ b/actualize_script.php @@ -26,3 +26,4 @@ $front_controller = new App_FrontController (); $front_controller->init (); Session::_param('mail', true); // permet de se passer de la phase de connexion $front_controller->run (); +touch(PUBLIC_PATH . '/data/touch.txt'); diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml index cfbdc4a2e..40afbd85f 100644 --- a/app/layout/layout.phtml +++ b/app/layout/layout.phtml @@ -12,7 +12,14 @@ - +entryPaginator) ? $this->entryPaginator->next() : ''; + if (!empty($next)) { + $params = Request::params (); + $params['next'] = $next; +?> + + partial ('header'); ?> diff --git a/app/models/RSSPaginator.php b/app/models/RSSPaginator.php index 7010291bc..86b4b5cac 100644 --- a/app/models/RSSPaginator.php +++ b/app/models/RSSPaginator.php @@ -19,6 +19,10 @@ class RSSPaginator { return $this->items; } + public function next () { + return $this->next; + } + public function render ($view, $getteur) { $view = APP_PATH . '/views/helpers/'.$view; diff --git a/lib/http-conditional.php b/lib/http-conditional.php new file mode 100644 index 000000000..bd038aec3 --- /dev/null +++ b/lib/http-conditional.php @@ -0,0 +1,207 @@ +0, the document will be cashed and not revalidated against the server for this delay. + [Implied] $cachePrivacy=0: 0=private, 1=normal (public), 2=forced public. When public, it allows a cashed document ($cacheSeconds>0) to be shared by several users. + [Implied] $feedMode=false: Special RSS/ATOM feeds. When true, it sets $cachePrivacy to 0 (private), does not use the modification time of the script itself, and puts the date of the client's cache (or a old date from 1980) in the global variable $clientCacheDate. + [implied] $compression=false: Enable the compression and allows persistant connections (automatic detection of the capacities of the client). + [implied] $session=false: To be turned on when sessions are used. Checks if the data contained in $_SESSION has been modified during the last generation the document. + Returns: True if the connection can be closed (e.g.: the client has already the lastest version), false if the new content has to be send to the client. + + Typical use: + + + Version 1.6.2a, 2008-03-06, http://alexandre.alapetite.fr/doc-alex/php-http-304/ + + ------------------------------------------------------------------ + Written by Alexandre Alapetite, http://alexandre.alapetite.fr/cv/ + + Copyright 2004-2008, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR), + http://creativecommons.org/licenses/by-sa/2.0/fr/ + http://alexandre.alapetite.fr/divers/apropos/#by-sa + - Attribution. You must give the original author credit + - Share Alike. If you alter, transform, or build upon this work, + you may distribute the resulting work only under a license identical to this one + (Can be included in GPL/LGPL projects) + - The French law is authoritative + - Any of these conditions can be waived if you get permission from Alexandre Alapetite + - Please send to Alexandre Alapetite the modifications you make, + in order to improve this file for the benefit of everybody + + If you want to distribute this code, please do it as a link to: + http://alexandre.alapetite.fr/doc-alex/php-http-304/ +*/ + +//In RSS/ATOM feedMode, contains the date of the clients last update. +$clientCacheDate=0; //Global public variable because PHP4 does not allow conditional arguments by reference +$_sessionMode=false; //Global private variable + +function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false,$session=false) +{//Credits: http://alexandre.alapetite.fr/doc-alex/php-http-304/ + //RFC2616 HTTP/1.1: http://www.w3.org/Protocols/rfc2616/rfc2616.html + //RFC1945 HTTP/1.0: http://www.w3.org/Protocols/rfc1945/rfc1945.txt + + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + if ((!$feedMode)&&(($modifScript=filemtime($scriptName))>$UnixTimeStamp)) + $UnixTimeStamp=$modifScript; + $UnixTimeStamp=min($UnixTimeStamp,time()); + $is304=true; + $is412=false; + $nbCond=0; + + //rfc2616-sec3.html#sec3.3.1 + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + $dateCacheClient='Thu, 10 Jan 1980 20:30:40 GMT'; + + //rfc2616-sec14.html#sec14.19 //='"0123456789abcdef0123456789abcdef"' + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + if ($session&&isset($_SESSION)) + { + global $_sessionMode; + $_sessionMode=$session; + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + } + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + if ((!$is412)&&isset($_SERVER['HTTP_IF_MATCH'])) + {//rfc2616-sec14.html#sec14.24 + $etagsClient=stripslashes($_SERVER['HTTP_IF_MATCH']); + $is412=(($etagClient!='*')&&(strpos($etagsClient,$etagServer)===false)); + } + if ($is304&&isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.25 //rfc1945.txt + $nbCond++; + $dateCacheClient=$_SERVER['HTTP_IF_MODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is304=($dateCacheClient==$dateLastModif); + } + if ($is304&&isset($_SERVER['HTTP_IF_NONE_MATCH'])) + {//rfc2616-sec14.html#sec14.26 + $nbCond++; + $etagClient=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); + $is304=(($etagClient==$etagServer)||($etagClient=='*')); + } + if ((!$is412)&&isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.28 + $dateCacheClient=$_SERVER['HTTP_IF_UNMODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is412=($dateCacheClient!=$dateLastModif); + } + if ($feedMode) + {//Special RSS/ATOM + global $clientCacheDate; + $clientCacheDate=strtotime($dateCacheClient); + $cachePrivacy=0; + } + + if ($is412) + {//rfc2616-sec10.html#sec10.4.13 + header('HTTP/1.1 412 Precondition Failed'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Content-Type: text/plain'); + echo "HTTP/1.1 Error 412 Precondition Failed: Precondition request failed positive evaluation\n"; + return true; + } + elseif ($is304&&($nbCond>0)) + {//rfc2616-sec10.html#sec10.3.5 + header('HTTP/1.0 304 Not Modified'); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //Comment this line under IIS + return true; + } + else + {//rfc2616-sec10.html#sec10.2.1 + //rfc2616-sec14.html#sec14.3 + if ($compression) ob_start('_httpConditionalCallBack'); //Will check HTTP_ACCEPT_ENCODING + //header('HTTP/1.0 200 OK'); + if ($cacheSeconds<0) + { + $cache='private, no-cache, no-store, must-revalidate'; + header('Pragma: no-cache'); + } + else + { + if ($cacheSeconds==0) $cache='private, must-revalidate, '; + elseif ($cachePrivacy==0) $cache='private, '; + elseif ($cachePrivacy==2) $cache='public, '; + else $cache=''; + $cache.='max-age='.floor($cacheSeconds); + } + //header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T',time()+$cacheSeconds)); //HTTP/1.0 //rfc2616-sec14.html#sec14.21 + header('Cache-Control: '.$cache); //rfc2616-sec14.html#sec14.9 + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //rfc2616-sec14.html#sec14.10 //Comment this line under IIS + return $_SERVER['REQUEST_METHOD']=='HEAD'; //rfc2616-sec9.html#sec9.4 + } +} + +function _httpConditionalCallBack($buffer,$mode=5) +{//Private function automatically called at the end of the script when compression is enabled + //rfc2616-sec14.html#sec14.11 + //You can adjust the level of compression with zlib.output_compression_level in php.ini + if (extension_loaded('zlib')&&(!ini_get('zlib.output_compression'))) + { + $buffer2=ob_gzhandler($buffer,$mode); //Will check HTTP_ACCEPT_ENCODING and put correct headers such as Vary //rfc2616-sec14.html#sec14.44 + if (strlen($buffer2)>1) //When ob_gzhandler succeeded + $buffer=$buffer2; + } + header('Content-Length: '.strlen($buffer)); //Allows persistant connections //rfc2616-sec14.html#sec14.13 + return $buffer; +} + +function httpConditionalRefresh($UnixTimeStamp) +{//Update HTTP headers if the content has just been modified by the client's request + //See an example on http://alexandre.alapetite.fr/doc-alex/compteur/ + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + global $_sessionMode; + if ($_sessionMode&&isset($_SESSION)) + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); +} diff --git a/lib/minz/dao/Model_pdo.php b/lib/minz/dao/Model_pdo.php index 343b7004a..a91a4fa00 100755 --- a/lib/minz/dao/Model_pdo.php +++ b/lib/minz/dao/Model_pdo.php @@ -54,7 +54,7 @@ class Model_pdo { . '/data/' . $db['base'] . '.sqlite'; //TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797 } - $this->bd = new PDO ( + $this->bd = new FreshPDO ( $string, $db['user'], $db['password'], @@ -72,3 +72,21 @@ class Model_pdo { } } } + +class FreshPDO extends PDO { + private static function check($statement) { + if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { + touch(PUBLIC_PATH . '/data/touch.txt'); + } + } + + public function prepare ($statement, $driver_options = array()) { + FreshPDO::check($statement); + return parent::prepare($statement, $driver_options); + } + + public function exec ($statement) { + FreshPDO::check($statement); + return parent::exec($statement); + } +} diff --git a/public/index.php b/public/index.php index 64aabb609..daf65c54a 100755 --- a/public/index.php +++ b/public/index.php @@ -28,6 +28,16 @@ define ('CACHE_PATH', realpath (PUBLIC_PATH . '/../cache')); if (file_exists (PUBLIC_PATH . '/install.php')) { include ('install.php'); } else { + session_cache_limiter(''); + require (LIB_PATH . '/http-conditional.php'); + $dateLastModification = max(filemtime(filemtime(PUBLIC_PATH . '/data/touch.txt'), + PUBLIC_PATH . '/data/Configuration.array.php'), + filemtime(LOG_PATH . '/application.log'), + time() - 3600); + if (httpConditional($dateLastModification, 0, 0, false, false, true)) { + exit(); //No need to send anything + } + set_include_path (get_include_path () . PATH_SEPARATOR . LIB_PATH diff --git a/public/scripts/main.js b/public/scripts/main.js index 92259696f..19e2905cc 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -490,6 +490,11 @@ function init_load_more() { } url_load_more = $next_link.attr("href"); + var $prefetch = $('#prefetch'); + if ($prefetch.attr('href') !== url_load_more) { + $.ajax({url: url_load_more, ifModified: true }); //TODO: Try to find a less agressive solution + $prefetch.attr('href', url_load_more); + } $next_link.click(function () { load_more_posts(); -- cgit v1.2.3