Page 1 sur 1

Le cache HTTP

Posté : 05 juin 2007, 07:45
par Klomac
Bonjour bonjour,

Je suis en ce moment en train de tester une des connaissances que j'ai piochées dans l'évangile selon Cyril Pierre de Geyer et Eric Daspet ;) à savoir le cache HTTP.

Le bouquin nous propose de procéder comme suit :
$serverDate = gmdate('D, d M Y H:i:s', time());
$lastModifiedDate = gmdate('D, d M Y H:i:s', $lastModif);
			
header('Date: '.$serverDate.' GMT');
header('Last-Modified: '.$lastModifiedDate.' GMT');
			
$requestDate = substr(@$_SERVER['HTTP_IF_MODIFIED_SINCE'],0,29);
			
if ($requestDate != '' AND strtotime($requestDate) >= $this -> lastModified)
{
	header('Not Modified', TRUE, 304);
}
Seulement après quelques tests je me rends compte que toute la dernière partie n'a aucun effet. Si je la supprime et que j'envoie une date "Last-Modified" inférieure à celle de mon dernier passage, le navigateur utilise son cache.

Ma question est donc : à quoi sert ce "Note Modified" si mon navigateur ne prend en compte que la date "Last-Modified" ?

Posté : 05 juin 2007, 09:12
par titerm
Attention c'est long...

Le cache des navigateur est une longue histoire qu'Eric & Cyril Pierre (coucou) ne peuvent pas traiter completement dans un livre généraliste. Il faudrait un livre dédié si on voulait pouvoir en couvrir tous les aspects. Néanmoins, dans les parties qu'ils abordent, dit toi bien que rien n'est inutile.



On peut déjà rapeller plusieurs choses, cela ne coute rien.
- Le cache se paramètre coté client, on n'a donc pas coté serveur la posibilité de savoir comment est paramétré un client, ni de modifier ce paramétrage. C'est particulièrement vrai avec IE qui propose dans les options 4 modes de gestion du cache...

- les algorythme s diffère d'une browser a l'autre et notement les heuristique d'expiration.
Par exemple, pour une page dont ont a pas donnée de durée de peremption (elle n'expire jamais). Le navigateurs va quand meme aller vérifier de temps en temps qu'elle na pas changée, mais la fréquence à laquelle il va le faire dépend de son heuristique.
* Pour un navigateur, une page qui n'a pas été modifiée depuis un mois (durée exemple) va etre considérée comme stable et il ira de moins en moins souvent vérifier si n'est n'a pas changée, en partant du principe que plus elle reste stable longtemps, plus elle a de chance d'etre statique.
* Pour un autre navigateur, l'approche est oposée et il vas partir du principe que si une page n'a pas évoluée depuis longtemps, la probabilité qu'elle est changée est de plus en plus forte.

- Hormis les approches avec des dates, il y a d'autre moyen de savoir qu'un page a évoluée, c'est le principe des etags. On n'utilise plus la date de derniere modification mais un code arbitraire qui permet est affecté suivant un algorithme au choix, souvent un cheksum quelquonque sur le contenu de la page, ce qui fait que si la page change, le checksum change et donc le etag avec. C'est dailleurs la meilleur aproche, bien plus simple et plus fiable que le principe de last-modified. etc.... Helas, (bah oui, c'était trop beau), IE gère bien les etags tant qu'il n'y a pas de compression html, mais ne les renvoie pas au serveur si la page qui lui a été servie était compressé, et donc, le système d'étag n'est pas valable avec IE si on fait de la compression, on est donc obligé d'utiliser a nouveau des last-modified. Le gros problème des last-modified, c'est que si ton site est très fréquenté et a plusieurs frontaux pour faire face à la charge, chaque frontal vas avoir une date de last modified disincte, et comme la répartion de charge entre les frontaux est souvent faites via le round-robin dns, a chaque chargement, tu risques de charger la page sur un frontal distinct, et donc d'avoir un last modified différends.


Voila juste quelques petits exemples des problèmes de gestion de cache . Quand je te dit qu'on pourrait en faire un livre dédié...

Dans ton cas, assure toi que ton navigateur est bien paramétré pour utiliser le cache.
Le 304 (not modified) est renvoyé au navigateur quand le navigateur redemande une page au serveur en lui indiquand
je veux telle page, avec tel etag, tel last modified. Le serveur vas constuire la page et comparer le etag ou comparer la date de last-modified. Si il y a eu modification entre temps, il renvoie la totalité du contenue de la page. Si aucune modification n'a eu lieu, il ne retourne qu'un header avec code 304 et le browser comprend que la page n'ayant pas évolué, il peux utiliser celle qui se trouve dans son cache.

Attention, si tu fais f5, tu peux obtenir des des 304, mais si tu fais ctrl-f5, tu forces le rechargement de la page en ignorant date et etags.

Posté : 05 juin 2007, 19:02
par Klomac
Je vois...

Pour ma part je n'ai pas besoin de gérer de façon très approfondie le cache utilisateur, c'est surtout histoire d'apporter un petit confort supplémentaire à l'utilisateur tant qu'à faire (pour économiser des ressources le cache serveur fait déjà bien son job).

Cela dit j'ai fais quelques tests avec plusieurs configurations, sous Firefox et sous IE et le résultat est toujours le même : si le header "Last-Modified" est antérieur à la date de dernière visite la page ne se recalcule pas, même sans envoyer de "Not Modified".

Dans ce cas ne pensez-vous pas que l'envoi du header "Last-Modified" soit suffisant, pour la plupart des configurations ?

Posté : 05 juin 2007, 23:48
par Hubert Roksor
L'en-tête "Last-Modified" ne fait rien par lui même, ce n'est qu'une information. Ça n'indique pas au navigateur quand demander une version plus récente, pour celà tu peux utiliser "Cache-control". Par exemple, si tu veux que le navigateur utilise son cache sans vérifier auprès du serveur jusqu'à 10 minutes (600s) après la dernière modification de la page ("Last-Modified") alors tu lui envoies
header('Cache-Control: max-age=600');
Au passage, si le contenu de la page diffère selon l'utilisateur il faut également le préciser pour éviter qu'un serveur proxy sur le chemin n'intercepte la page et la renvoit à un autre utilisateur
header('Cache-Control: private, max-age=600');
Passé ce délai, le navigateur redemandera la page au serveur en précisant un en-tête "If-Modified-Since", et ce n'est que là que tu peux renvoyer ou non
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304);
(l'en-tête de ton exemple plus haut est faux, il doit impérativement commencer par "HTTP/xxx" où xxx est la version en question)

Posté : 06 juin 2007, 00:09
par Klomac
Mmh... mais moi je veux pas qu'il garde la page en cache après la date de dernière modification, je veux que si la date de dernière modification est antérieure à la date de dernière visite on garde la page en cache, sinon on recalcule la page. Y'a besoin du Cache-Control pour ça ?

Posté : 06 juin 2007, 00:17
par Hubert Roksor
Je ne comprends pas ce que tu veux dire,
si la date de dernière modification est antérieure à la date de dernière visite
...le contraire est impossible. Comment visiter une page dont la date de dernière modification est dans le futur ?

"Last-Modified" est la date à laquelle le contenu de la page a changé. Pour cette page, par exemple ce serait l'heure de ce message. Jusqu'à ce que tu postes le tiens, qui deviendrait alors la date de dernière modification et ainsi de suite. "If-Modified-Since" permet de demander "y'a eu des nouveaux messages ?" au serveur pour savoir s'il faut télécharger la nouvelle version ou pas.

Posté : 06 juin 2007, 09:08
par Klomac
Je ne comprends pas ce que tu veux dire,
si la date de dernière modification est antérieure à la date de dernière visite
...le contraire est impossible. Comment visiter une page dont la date de dernière modification est dans le futur ?
Je me suis mal exprimé. Quand je parle de "dernière visite" je parle pas de la visite actuelle mais de celle d'avant.

Genre je visite la page à un instant T.
Je la revisite à un instant T+30.
Si la date de modification de la page est antérieure à T je ressors la page en cache, et si la date de modification est comprise entre T et T+30 je recalcule.

Mais ça quand je teste sur différents navigateurs je vois que ça se fait tout seul, Firefox et IE ont l'air de gérer eux-même correctement le cache en fonction du "Last-Modified".

Qu'en pensez-vous ?

Posté : 06 juin 2007, 09:14
par titerm
le cache controle va te permettre de définir une durée de validité pour une page, durée pendant laquelle, le navigateur se servira de son cache sans même faire référence au serveur. Au dela de cette durée, si tu as précisé un last-modified dans ta réponse, le client emettra une requete pour la page en incluant un if-modifed-since.
Partant de la, cela dépend comment le test a été codé, soit il y a comparaison du type
if( if-modified-since < last-modified) retourne la page
else send 304

Soit, c'est sur l'égalité, si c'est egale, 304, sinon la page. (c'est ce que j'ai vu de plus courant, evite les pb si jamais l'heure serveur était modifié vers le passé)

Si tu ne met pas d'expires, le navigateur va checker a chaque fois.

Dans ton code, comment est init le $lastModif ?
utilise plutot === que >=

Posté : 06 juin 2007, 09:28
par Klomac
Bon je vous colle mon code (c'est une classe) :
class LambdaHTTPCache
	{
		private $lastModified; //The last-modified time of the document
		private $proxy = 'Public'; //The proxy option (public or private)
		private $userAgent = FALSE;
		
		//The errors which may be displayed
		private $errors = array (
			'The $time parameter must be an integer');
		
		//Public methods
		public function __construct () { }
		
		public function checkTime ($time) //This method checks if this time is bigger than the last-modified time
		{
			if (!is_int($time)) die ($this -> errors[0]);
			elseif ($time > $this -> lastModified) $this -> lastModified = $time;
		}
		
		public function setProxy ($private, $userAgent) //This method adds some parameters to use client proxies
		{
			if ($private == TRUE) $this -> proxy = 'Private';
			if ($userAgent == TRUE) $this -> userAgent = TRUE;
		}
		
		public function sendHeaders () //This method sends the HTTP headers
		{
			$serverDate = gmdate('D, d M Y H:i:s', time());
			$lastModifiedDate = gmdate('D, d M Y H:i:s', $this -> lastModified);
			
			header('Date: '.$serverDate.' GMT');
			header('Last-Modified: '.$lastModifiedDate.' GMT');
			if ($this -> userAgent == TRUE) header ('Vary: User-Agent');
			header('Cache-Control: '.$this -> proxy.', must-revalidate');
			header('Pragma: '.strtolower($this -> proxy));
			
			$requestDate = substr(@$_SERVER['HTTP_IF_MODIFIED_SINCE'],0,29);
			
			if ($requestDate != '' AND strtotime($requestDate) >= $this -> lastModified)
			{
				header('Not Modified', TRUE, 304);
			}
		}
	}
La méthode checkTime permet de traiter un temps de modification (d'un fichier de cache serveur par exemple), la date est gardée si elle est antérieure à celle déjà enregistrée. La méthode setProxy permet de définir une page comme étant privée et éventuellement d'indiquer qu'elle dépend du navigateur du client. Et la méthode sendHeaders envoie les headers appropriés.

Le fait est que si je supprime toute la fin (à partir de $requestDate = ....) le code fait exactement la même chose. C'est ça qui m'intrigue.
Si tu ne met pas d'expires, le navigateur va checker a chaque fois.
Oui justement moi je veux qu'il check à chaque fois, mais je ne veux pas qu'il recalcule la page si rien n'a changé.

Posté : 06 juin 2007, 10:12
par titerm
J'ai pas le temps de lire le code now, mais je peux répondre a ta derniere phrase.

La page est recalculé uniquement quand on utilise les etags, il faut bien recalculer le checksum...

Pour un last-modified, not-modified-since, cela se fait uniquement sur la date de reference. En regle générale, la date de modification du fichier (a toi de gérer la validité du cache interne si tu en as un)

Si not-modified-since == last-modified, tu envoies un header 304 ET tu fais un exit , sinon, header script continue...