Wikini

GererLesCachesAuNiveauApplicatif

PagePrincipale :: DerniersChangements :: DerniersCommentaires :: ParametresUtilisateur :: Vous êtes ec2-54-161-40-41.compute-1.amazonaws.com
Le but de cette page est d'évaluer les solutions pour gérer le cache des navigateurs au niveau applicatif, à l'aide des en-têtes HTTP.

Le principe fonctionnel


1. Activation du cache pour une période déterminée


2. Comparaison de date



Affichage des en-têtes HTTP : outillage préalable


Il peut-être très fastidueux d'observer ou déboguer l'échange des en-têtes HTTP entre clients et serveurs. Nous détaillons ici des possibilités non limitatives.

1. Solution côté serveur


2. Solution côté client



La réalisation


Désactiver les caches

Exemple de code qui désactive tous les caches entre un client et un serveur :
<?php
  header
("Expires: Mon, 26 Jul 1997 05:00:00 GMT");             // Date du passé
  
header("Last-Modified: " gmdate("D, d M Y H:i:s") . " GMT"); // toujours modifié
  
header("Cache-Control: no-cache, must-revalidate");           // HTTP/1.1
  
header("Pragma: no-cache");                                   // HTTP/1.0
?>



Activation du cache pour une période déterminée en PhP

Exemple de code qui active le cache local du navigateur :
<?php
  $offset 
60 60 24 3;
  
$ExpireString "Expires: " gmdate("D, d M Y H:i:s"time() + $offset) . " GMT";
  
Header($ExpireString);
?>

Avec ce code, le serveur indique au navigateur que la page peut être demandée au cache jusque pendant 3 jours (60 * 60 * 24 * 3 secondes).
Au delà de cette date le navigateur va voir la version qui est sur le serveur. Ces directives devraient bien fonctionner avec la plupart des navigateurs (MoZilla 1.1 est OK).
Un clic sur "recharger la page" ou "reload" provoque en revanche, sur MoZilla, une demande au serveur : le cache ne fonctionne qu'avec le bouton "Back" ou lorsque l'on clique sur un lien qui mène à la page qui intègre ce code. -- CharlesNepote



Cacher en fonction de la date de modification de la page

Ce qu'il faut arriver à faire, et qui est, par exemple, mis en oeuvre sur FPWiki, c'est de faire dépendre le rafraichissement d'une page en fonction de la dernière date de modification d'une page.
Je pense avoir réalisé ce que nous souhaitons. Il va falloir tout de même tester quelque temps et voir encore si l'on ne peut pas optimiser.
Le code suivant détecte dans WikiNi la date de dernière modification de la page et demande au navigateur d'aller la chercher dans son cache si elle n'a pas été modifée.

<?php
        
// CACHE CONTROL : Control the last update date send by browser, and decide if the page need to be resend
        
function CacheControl()
        {
        
// Get the page's date (2002-12-04 20:13:47)
        
list($year$month$day$page_time) = split("[ -]"$this->GetPageTime());
        
// and transform it to HTTP date format : Thu, 05 Dec 2002 20:13:47 GMT
        
$begin_date date("D, d M Y"mktime (0,0,0,$month,$day,$year));
        
$last_modified $begin_date." ".$page_time." GMT";
        
$last_modified $begin_date." ".$page_time." GMT";
        
// Read the headers sent by the browser
        //$headers = getallheaders(); // Does not work on every Php installation
        //$if_modified_since = $headers['If-Modified-Since']; // Does not work on every Php installation
        
$if_modified_since $_SERVER['HTTP_IF_MODIFIED_SINCE']; // Seems to work on every Php installation [to be confirmed]
        // Purify the $if_modified_since so that it can be well compared with the page's last modification
        // (The browser usually give this type of string "Thu, 05 Dec 2002 20:13:47 GMT", but some, like Netscape 4.7
        //   give other strings such as ""Fri, 05 Sep 1997 01:03:46 GMT; length=2291") -- Thanks to Eric Segui.
        
$if_modified_since preg_replace ("/^(.*)(Mon|Tue|Wed|Thu|Fri|Sat|Sun)(.*)(GMT)(.*)/""$2$3 GMT"$if_modified_since);

        
// Transform strings into dates
        
$date_last_modified strtotime($last_modified);
        
$date_if_modified_since strtotime($if_modified_since);

                
// If the server's page hasn't been modified since last visit
                
if ($date_if_modified_since === $date_last_modified)
                {
                        
// Tells the browser page hasn't been modified ; the browser will then look for the page in his cache
                        
header("HTTP/1.1 304 Not Modified");
                        
header("Last-modified: ".$last_modified);
                        
header("Cache-Control: Public"); // Tells HTTP 1.1 clients to cache
                        
header("Pragma:"); // Tells HTTP 1.0 clients to cache
                        
exit();
                }
                
// else If the server's page has been modified since last visit
                
else
                {
                        
header("Last-modified: ".$last_modified);
                        
header("Cache-Control: Public"); // Tells HTTP 1.1 clients to cache
                        
header("Pragma:"); // Tells HTTP 1.0 clients to cache
                
}
        }
?>

J'ai ensuite placé l'appel de fonction "CacheControl?" dans la fonction "Run" de la manière suivante :
Les résultats en local semblent plutôt concluants. Je vous donne en exemple ci-dessous, le journal des accès d'Apache (quand il y a 304 suivit d'un 0 c'est que le serveur n'a rien renvoyé) :
localhost - - [06/Dec/2002:10:35:50 +0100] "GET /wakka/wakka.php?wakka=BacASable HTTP/1.1" 200 96204
localhost - - [06/Dec/2002:10:36:05 +0100] "GET /wakka/wakka.php?wakka=BacASable HTTP/1.1" 304 0
localhost - - [06/Dec/2002:10:36:21 +0100] "GET /wakka/wakka.php?wakka=PagePrincipale HTTP/1.1" 200 5024
localhost - - [06/Dec/2002:10:36:23 +0100] "GET /wakka/wakka.php?wakka=DerniersChangements HTTP/1.1" 200 16156
localhost - - [06/Dec/2002:10:36:27 +0100] "GET /wakka/wakka.php?wakka=DerniersChangements HTTP/1.1" 304 0
localhost - - [06/Dec/2002:10:36:29 +0100] "GET /wakka/wakka.php?wakka=PagePrincipale HTTP/1.1" 304 0
localhost - - [06/Dec/2002:10:36:33 +0100] "GET /wakka/wakka.php?wakka=BacASable HTTP/1.1" 304 0
localhost - - [06/Dec/2002:10:36:36 +0100] "GET /wakka/wakka.php?wakka=PagePrincipale HTTP/1.1" 304 0
localhost - - [06/Dec/2002:10:36:38 +0100] "GET /wakka/wakka.php?wakka=BacASable HTTP/1.1" 304 0


For requests which come from a HTTP/1.0 compliant client (either a browser or a cache), the directive CacheNegotiatedDocs? can be used to allow caching of responses which were subject to negotiation. This directive can be given in the server config or virtual host, and takes no arguments. It has no effect on requests from HTTP/1.1 clients.
Problème du rafraichissement des pages contenant une action
Actuellement le cache fonctionne tellement bien (en local), que la page DerniersChagnements? n'est pas rafraichie, même si une page a été modifiée... Techniquement, c'est normal : cela est du au fait que les actions sont le résultat d'un calcul : les pages contenant des actions peuvent ne pas "changer" en terme d'édition alors que le résultat des actions change. Il faudrait donc détecter, à la suite d'une modification de page, que telle page demandée a été impactée et doit être raffraichie.
-- CharlesNepote

[ J'ai l'impression, qu'au final on se retrouve face aux mêmes difficultés que la gestion d'un cache dans l'application aurait pu apporter. La gestion d'un cache n'est pas si triviale que cela dans un wiki dès que l'on veut traiter les actions. Une enquête rapide sur les autres wiki ne m'a pas permis de trouver une solution élégante à ce problème, d'ailleurs peu de wikis fonctionnent avec des caches.

Quelques solutions ou propositions de gestion de cache dans d'autres wikis :

-- DavidDelon]

Je viens de découvrir la fonction session_cache_limiter qu'il faut utiliser dans le cas où on gère des sessions.
Je vais refaire des tests. La solution proposée sur Ontosys semble fonctionner partiellement sur Tuxfamily : cf : http://development.wikini.net/charles/wikini/tests/cacheable.php
-- CharlesNepote


Sinon, je viens d'imaginer une solution qui pourrait fonctionner : gérer la date de dernière consultation d'une page via un cookie, au lieu de le faire gérer par un en-tête HTTP.
Le principe :
Je vais tester ça.

<?php
    
// CACHE CONTROL : Control the last update date send by browser, and decide if the page need to be resend
    
function CacheControl()
    {
    
// Get the page's date (2002-12-04 20:13:47)
    
list($year$month$day$page_time) = split("[ -]"$this->GetPageTime());
    
// and transform it to HTTP date format : Thu, 05 Dec 2002 20:13:47 GMT
    
$begin_date date("D, d M Y"mktime (0,0,0,$month,$day,$year));
    
$last_modified $begin_date." ".$page_time." GMT";
    
    
// Transform strings into dates
    
$date_last_modified strtotime($last_modified);
    
$page_actuelle $this->GetPageTag();

        
// If the server's page hasn't been modified since last visit and visitor has already been saw this page
        
if (($_SESSION["last-visit"] > $date_last_modified) && (isset ($_SESSION[$page_actuelle])))
        {
            
// Tells the browser page hasn't been modified ; the browser will then look for the page in his cache
            
header("HTTP/1.x 304 Not Modified");
            
header("Date:" $last_modified);
            
//header("Last-modified: ".$last_modified);
            //header("Cache-Control: Public"); // Tells HTTP 1.1 clients to cache
            //header("Pragma:"); // Tells HTTP 1.0 clients to cache
            //echo "304"; // debug
            
exit();
        }
        
// else If the server's page has been modified since last visit
        
else
        {
            
// Save in session's user his last visit and the page's name
            
$_SESSION["last-visit"] = time();
            
$_SESSION[$page_actuelle] = "1";
        }
    }
?>


Voilà donc un premier code qui fonctionne parfaitement chez moi mais qui me donne un superbe "The server made a boo boo." sur Tuxfamily... manifestement à cause de l'instruction :
header("HTTP/1.x 304 Not Modified");
-- CharlesNepote (qui commence à baisser un peu les bras... d'autant que je n'ai toujours aucune réponse des admins de TF)


Je me permet d'intervenir. Si votre PHP est en 4.3.0 ou supérieur une solution probablement fonctionnelle est header('Not Modified', TRUE, 304) à la place de header("HTTP/1.x 304 Not Modified");
-- EricDaspet? (blog : article sur les caches HTTP) [fr]

Au cours du débug de DotClear?, j'ai pu constaté que header('HTTP/1.x 304 Not Modified') était pas apprécié du tout chez Free. La solution header(''Not Modified',true,304) est bien mais demande un PHP 4.3.0+ (pas le cas chez free) (Jarod: Free fournit php 4.3.4 - 3 mai 2004). J'ai finalement trouvé que header('Status: 304 Not Modified') marchait très bien dans ce cas.
Concernant les erreurs chez TF, elles sont peut-être dues à leur Apache 2, voir à ce propos le bug sur php.net : http://bugs.php.net/bug.php?id=17098
-- OlivierMeunier?



Côté serveur

[à compléter]
Extrait de la documentation d'Apache :
Note on Caching
When a cache stores a representation, it associates it with the request URL. The next time that URL is requested, the cache can use the stored representation. But, if the resource is negotiable at the server, this might result in only the first requested variant being cached and subsequent cache hits might return the wrong response. To prevent this, Apache normally marks all responses that are returned after content negotiation as non-cacheable by HTTP/1.0 clients. Apache also supports the HTTP/1.1 protocol features to allow caching of negotiated responses.
Sur CacheNegociatedDocs [en].
Voir aussi le module mod_headers [en].


Références :




Il n'y a pas de commentaire sur cette page. [Afficher commentaires/formulaire]