Sfoglia il codice sorgente

Préchargement et requêtes conditionnelles HTTP/1.1

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.
Alexandre Alapetite 12 anni fa
parent
commit
adc9a958af

+ 1 - 0
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');

+ 8 - 1
app/layout/layout.phtml

@@ -12,7 +12,14 @@
 		<script>//<![CDATA[
 <?php $this->renderHelper ('../javascript/main'); ?>
 		//]]></script>
-		<?php echo self::headScript (); ?>
+<?php
+	$next = isset($this->entryPaginator) ? $this->entryPaginator->next() : '';
+	if (!empty($next)) {
+		$params = Request::params ();
+		$params['next'] = $next;
+?>
+		<link id="prefetch" rel="next prefetch" href="<?php echo Url::display (array ('c' => Request::controllerName (), 'a' => Request::actionName (), 'params' => $params)); ?>" />
+<?php } ?>
 	</head>
 	<body>
 <?php $this->partial ('header'); ?>

+ 4 - 0
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;
 

+ 207 - 0
lib/http-conditional.php

@@ -0,0 +1,207 @@
+<?php
+/*
+ Enable support for HTTP/1.x conditional requests in PHP.
+ Goal: Optimisation
+ - If the client sends a HEAD request, avoid transferring data and return the correct headers.
+ - If the client already has the same version in its cache, avoid transferring data again (304 Not Modified).
+ - Possibility to control cache for client and proxies (public or private policy, life time).
+ - When $feedMode is set to true, in the case of a RSS/ATOM feed,
+   it puts a timestamp in the global variable $clientCacheDate to allow the sending of only the articles newer than the client's cache.
+ - When $compression is set to true, compress the data before sending it to the client and persitent connections are allowed.
+ - When $session is set to true, automatically checks if $_SESSION has been modified during the last generation the document.
+
+ Interface:
+ - function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false)
+  [Required] $UnixTimeStamp: Date of the last modification of the data to send to the client (Unix Timestamp format).
+  [Implied] $cacheSeconds=0: Lifetime in seconds of the document. If $cacheSeconds<0, cache is disabled. If $cacheSeconds==0, the document will be revalidated each time it is accessed. If $cacheSeconds>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:
+ <?php
+  require_once('http-conditional.php');
+  //Date of the last modification of the content (Unix Timestamp format).
+  //Examples: query the database, or last modification of a static file.
+  $dateLastModification=...;
+  if (httpConditional($dateLastModification))
+  {
+   ... //Close database connections, and other cleaning.
+   exit(); //No need to send anything
+  }
+  //Do not send any text to the client before this line.
+  ... //Rest of the script, just as you would do normally.
+ ?>
+
+ 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);
+}

+ 19 - 1
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);
+	}
+}

+ 10 - 0
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

+ 5 - 0
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();