HTTP dispose d’un mécanisme permettant de tirer parti efficacement du système de cache et des autres capacités du navigateur client, pour économiser la bande passante, la puissance de calcul du serveur, et améliorer les temps de réponse.
Lorsqu’un client demande pour la première fois un document, celui-ci lui est transmis. Mais lorsque le client vient à redemander ce document, celui-ci ayant peut-être été mis à jour entre temps, le client fourni une date et un identifiant unique de la version dont il dispose ; le serveur transmettra à nouveau le document uniquement si celui-ci a été modifié depuis la version dont dispose le client, sinon le serveur enverra un avis de non-modification. Dans tous les cas, le client transmet aussi ses capacités, et la communication est optimisée selon ses paramètres, avec compression des données et connexions persistantes.
Résumé : Je propose une librairie libre — une seule fonction — qui permet de gérer les différents types de requêtes conditionnelles (304, 412), les requêtes HEAD, le cache au niveau client et proxys, la compression des données et les connexions persistantes. En mode RSS/ATOM, permet de filtrer les articles par date côté serveur pour n’envoyer au client que les nouveaux articles. Propose aussi un support basique des sessions. Aucune modification dans la configuration de PHP ou du serveur HTTP n’est requise. Aucun logiciel supplémentaire n’est nécessaire, ni côté serveur, ni côté client. Nécessite seulement l’inclusion au début du script PHP de la bibliothèque avec un
require()
et un appel de fonction.
Last-Modified: Thu, 08 Jul 2004 17:33:54 GMT
Etag: "82e81980-27f3-4a6ae480"
Last-Modified
ou tout seul (HTTP/1.1).
Le fait d’avoir un code crypté permet de masquer si on le souhaite la date de dernière modification.
Voir la référence pour le format du ETag.
If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT
Last-Modified
lors de la dernière réception de ce document.
If-None-Match: "82e81980-27f3-4a6ae480"
Etag
lors de la dernière réception de ce document.
Cache-Control: private, max-age=0, must-revalidate
Accept-Encoding: gzip,deflate
Content-Encoding: gzip
Accept-Encoding
.
Content-Length: 3495
Connection: keep-alive
Content-Length
soit renseigné.
Voir ma documentation sur les redirections Web en HTTP et HTML pour des commentaires sur les entêtes HTTP.
Dans l’exemple ci-dessous, seuls les entêtes HTTP utiles aux requêtes conditionnelles ont été mentionnés.
1. Le client demande une première fois un document
GET /test.php HTTP/1.1
2. Le serveur fourni le document
HTTP/1.x 200 OK Date: Thu, 08 Jul 2004 17:42:26 GMT Last-Modified: Thu, 08 Jul 2004 17:33:54 GMT Etag: "82e81980-27f3-4a6ae480" <html> ... </html>
3. Le client demande une deuxième fois le document, et envoie les références de la version dont il dispose déjà (en cache)
GET /test.php HTTP/1.1 If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT If-None-Match: "82e81980-27f3-4a6ae480"
4. Le serveur retourne un avis de non-modification car le document n’a pas été modifié depuis le dernier passage du client
HTTP/1.x 304 Not Modified Date: Thu, 08 Jul 2004 17:46:31 GMT Etag: "82e81980-27f3-4a6ae480"
5. Le client demande une troisième fois le document
GET /test.php HTTP/1.1 If-Modified-Since: Thu, 08 Jul 2004 17:33:54 GMT If-None-Match: "82e81980-27f3-4a6ae480"
6. Le serveur fourni la nouvelle version du document car celui-ci a été modifié
HTTP/1.x 200 OK Date: Thu, 08 Jul 2004 17:48:54 GMT Last-Modified: Thu, 08 Jul 2004 17:48:52 GMT Etag: "82e81980-2bf2-7ff14900" <html> ... </html>
Ce mécanisme est normalement pris en charge automatiquement par les serveurs HTTP (comme Apache, IIS, ...) pour les documents statiques, comme les pages HTML, les images JPEG, etc. mais il est laissé à la charge du programmeur pour les documents dynamiques générés en PHP, CGI, etc.
Aucun logiciel supplémentaire n’est nécessaire, ni côté client ni côté serveur. Les serveurs et les navigateurs les plus courants sont tous nativement compatibles avec cette technique. Dans le cas où le navigateur client ne serait pas compatible HTTP/1.1, l’utilisation de ces techniques n’a aucun effet secondaire et la communication se déroule normalement, mais sans optimisation.
Je propose un module à inclure en haut de vos pages PHP pour gérer automatiquement ces requêtes conditionnelles, et économiser ainsi du temps de calcul, de la bande passante, et fournir vos réponses plus rapidement. Il permet de gérer les caches au niveau du client et des proxys, et permet la compression des données. Les connexions persistantes sont permises, lorsque la compression des données est activée, selon la politique du serveur. Il y a aussi une fonctionnalité spéciale pour les fils RSS/Atom qui permet de ne transmettre au client que les nouveaux articles. Un support basique des sessions est inclus.
Ce module s’occupe des différents entêtes conditionnels, tient compte de la dernière modification du script lui-même, et gère les requêtes HEAD.
Avant d’envoyer le moindre texte au client, il suffit d’appeler la fonction httpConditional()
avec :
$UnixTimeStamp
(obligatoire)httpConditional()
prend soin de vérifier si le script lui-même a été modifié.$cacheSeconds=0
(facultatif)$cachePrivacy=0
(facultatif)$feedMode=false
(facultatif)$clientCacheDate
contiendra la date de la version dans le cache du client,
la politique de cache est forcée automatiquement à privé, la connexion est fermée rapidement car le client ne prend en général qu’un seul fichier,
et on ne tient pas compte de la date de modification du script.$compression=false
(facultatif)zlib.output_compression_level
dans php.ini
ou avec ini_set('zlib.output_compression_level',7)
par exemple (1..9).$session=false
(facultatif)$_SESSION
ont été modifiées depuis l’appel à cette fonction lors de la dernière génération du document.session_cache_limiter('');
et/ou session.cache_limiter=''
dans php.ini
Usage basique :
exemple.php <?php require_once('http-conditional.php'); //Date de la dernière modification du contenu (format Timestamp Unix) //Exemple: requête base de données $dateDerniereModification=...; if (httpConditional($dateDerniereModification)) {//Aucune modification depuis le dernier passage du client ... //Ferme la base de données, et autres nettoyages exit(); //Pas besoin d’envoyer autre chose } //!\ Ne pas envoyer de texte au client avant cette ligne ... //La suite du script, comme s’il n’y avait pas cette première partie ?>
http-conditional.php <?php /*Optimisation : Permet le support des requêtes HTTP/1.x conditionnelles en PHP.*/ //En mode RSS/ATOM, contient la date de dernière mise à jour du client. $clientCacheDate=0; //Variable globale publique car PHP4 ne permet pas les arguments optionels par référence $_sessionMode=false; //Variable globale privée function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false,$session=false) {//Credits: https://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 //Si les entêtes HTTP ont déjà été envoyés au client, trop tard, on ne peut rien faire. 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; //Date de modification la plus récente entre les données et le script lui-même //La date de dernière modification ne doit pas être ultérieure à la date courante du serveur $UnixTimeStamp=min($UnixTimeStamp,time()); //Si les conditions de la requête HTTP montrent que version présente dans le cache du navigateur client est bonne $is304=true; //Si les préconditions sont inacceptables $is412=false; //Pour une requête HTTP conditionnelle avec une réponse 304, il faut au moins une condition validée $nbCond=0; /* Format des dates: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 # En HTTP/1.x, la date et l’heure doivent être dans le fuseau horaire de Greenwich (GMT). # Plusieurs recommandations on cohabitées: - HTTP/1.1 RFC2616 préfère le RFC822 mis à jour par RFC1123, compatible RFC733. - HTTP/1.0 se base sur la fonction Usenet getdate(3), RFC850 mis à jour par RFC1036, # Ces recommandations ont un dénominateur commun, plus strict, dans la zone horaire GMT, de la forme : Mon, 28 Jun 2004 18:31:54 GMT # D’où en PHP $httpDate=gmdate('D, d M Y H:i:s \G\M\T',$timeStamp); */ $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); $dateCacheClient='Tue, 10 Jan 1980 20:30:40 GMT'; //Entity tag (Etag) de la ressource retournée. //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 //Doit changer si la ressource retourne un document différent, ou si le document a été modifié if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; else $myQuery=''; if ($session&&isset($_SESSION)) {//Dans le cas de sessions, intègre les variables de $_SESSION dans le calcul du ETag global $_sessionMode; $_sessionMode=$session; $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); } $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; //='"0123456789abcdef0123456789abcdef"' if ((!$is412)&&isset($_SERVER['HTTP_IF_MATCH'])) {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 $etagsClient=stripslashes($_SERVER['HTTP_IF_MATCH']); //Comparaison du Etag actuel et de ceux du client $etagsClient=str_ireplace('-gzip','',$etagsClient); $is412=(($etagsClient!=='*')&&(strpos($etagsClient,$etagServer)===false)); } if ($is304&&isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 //http://www.w3.org/Protocols/rfc1945/rfc1945.txt //Récupère la date de la version dans le cache du navigateur client //Inutile de vérifier la consistance de la valeur, puisqu’on fera une comparaison de chaînes. $nbCond++; $dateCacheClient=$_SERVER['HTTP_IF_MODIFIED_SINCE']; $p=strpos($dateCacheClient,';'); //Internet Explorer ne respecte pas les standards if ($p!==false) //IE6 envoit quelque chose du style "Sat, 26 Feb 2005 20:57:12 GMT; length=134" $dateCacheClient=substr($dateCacheClient,0,$p); //Enlève l’information ajoutée par IE après la date //Comparaison des chaînes des dates du cache client et de la dernière modification //Doivent être identiques pour dire qu’il n’y a pas eu de modification (304 Not Modified) $is304=($dateCacheClient==$dateLastModif); } if ($is304&&isset($_SERVER['HTTP_IF_NONE_MATCH'])) {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 //Comparons les Etag pour vérifier que ce n’est pas cette version de ce document qui est en cache client $nbCond++; $etagClient=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); //Comparaison du Etag actuel et de celui du client $etagClient=str_ireplace('-gzip','',$etagClient); $is304=(($etagClient===$etagServer)||($etagClient==='*')); } //$_SERVER['HTTP_IF_RANGE'] //On ne gère pas les envois partiels. Retourne un 200 dans tous les cas. //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27 if ((!$is412)&&isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) {//http://www.w3.org/Protocols/rfc2616/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) {//Spécial RSS global $clientCacheDate; $clientCacheDate=@strtotime($dateCacheClient); $cachePrivacy=0; } if ($is412) {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.13 header('HTTP/1.1 412 Precondition Failed'); header('Content-Type: text/plain'); header('Cache-Control: private, max-age=0, must-revalidate'); echo "HTTP/1.1 Error 412 Precondition Failed: Precondition request failed positive evaluation\n"; //La réponse est terminée, la requête du client est annulée //car le document a été changé depuis que le client a décidé une action //(évite un conflit d’édition par exemple) return true; } elseif ($is304&&($nbCond>0)) {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 header('HTTP/1.0 304 Not Modified'); header('Etag: '.$etagServer); if ($feedMode) header('Connection: close'); //Vous devriez mettre cette ligne en commentaires si vous utilisez IIS return true; //La réponse est terminée, le client utilisera sa version en cache } else //La requête sera gérée de manière normale, sans condition {//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1 //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 if ($compression) ob_start('_httpConditionalCallBack'); //Utilise la compression des données //header('HTTP/1.0 200 OK'); //Géré par défaut par PHP //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 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('Cache-Control: '.$cache); header('Last-Modified: '.$dateLastModif); header('Etag: '.$etagServer); //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.10 //Inutile de garder une connexion ouverte pour les fils RSS/ATOM //car la plupart du temps les clients ne vont prendre qu’un seul fichier if ($feedMode) header('Connection: close'); //Vous devriez mettre cette ligne en commentaires si vous utilisez IIS //http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 //Dans le cas d’une requête HEAD, //on doit retourner les mêmes entêtes que dans le cas d’un GET, //mais le script n’a pas besoin de calculer le contenu return $_SERVER['REQUEST_METHOD']=='HEAD'; } } function _httpConditionalCallBack(&$buffer,$mode=5) {//Fonction privée appelée automatiquement à la fin du script lorsque la compression est activée //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 //Le niveau de compression peut être ajusté avec zlib.output_compression_level dans php.ini if (extension_loaded('zlib')&&(!ini_get('zlib.output_compression'))) { $buffer2=ob_gzhandler($buffer,$mode); //Va vérifier HTTP_ACCEPT_ENCODING et mettre les entêtes appropriés if (strlen($buffer2)>1) //Quand ob_gzhandler réussit $buffer=$buffer2; } header('Content-Length: '.strlen($buffer)); //Permet les connexions persistantes //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13 return $buffer; } function httpConditionalRefresh($UnixTimeStamp) {//Met à jour les entêtes HTTP si le contenu est vient d’être modifié par la requête du client //Voir un exemple sur https://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); } ?>
Avec $dateDerniereModification
étant votre variable contenant la date de dernière modification des données au format UNIX,
voici les arguments recommandés à passer à la fonction dans différents cas :
httpConditional($dateDerniereModification,18000,1,false,true)
httpConditional($dateDerniereModification,3600,0,false,true)
httpConditional($dateDerniereModification,5,0,false,true)
httpConditional($dateDerniereModification,3600,0,true,true)
$clientCacheDate
pour filtrer par date les articles dans la requête SQL.Quelques fonctions PHP utiles pour gérer les dates, et les transmettre à httpConditional()
au format UNIX :
time()
strtotime()
getlastmod()
filemtime()
MySQL UNIX_TIMESTAMP()
Ce module a été testé par exemple avec PHP/4/5/7 sous Apache/1.3/2.0.50 et IIS/5.1. Tous les clients ne prennent pas en charge l’ensemble des optimisations proposées, mais la communication a eu lieu sans problème avec tous les clients testés, comme InternetExplorer/5.0/5.5/6.0, Netscape/1.22/2.02/3.04/4.8/Mozilla, Opera/7, SharpReader/0.9.5, RSSreader/1.0.88...
Voici maintenant quelques exemples utilisant cette bibliothèque.
Pour tirer pleinement parti des avantages des requêtes conditionnelles, il faut que la date de dernière modification soit facilement et rapidement accessible. Aussi, une optimisation de la base de données dans ce sens est parfois souhaitable. On peut par exemple disposer d’une table contenant les principales dates de dernières modifications nécessaires.
Cas simple d’une page PHP avec un article issu d’une base de données MySQL.
La table des articles possède un champ “modified” contenant une date au format MySQL.
On veut retourner par SQL la date de modification de cet article au format timestamp UNIX.
article.php <?php if isset($_GET['id']) $num=$_GET['id']; //Numéro de l’article demandé else $num=0; if (($connect=mysql_connect('server','user','password'))&&mysql_select_db('mybase')) { $query='SELECT UNIX_TIMESTAMP(ar.modified) AS lastmod FROM articles ar WHERE ar.id='.$num; if (($result=mysql_query($query))&&($row=mysql_fetch_assoc($result))) { $dateLastModification=$row['lastmod']; mysql_free_result($result); if (httpConditional($dateLastModification)) {//Aucune modification depuis le dernier passage du client mysql_close($connect); exit(); } } } else $connect=false; ?> <html> ... <?php if ($connect) { ... //Autres requêtes vers la base de données mysql_close($connect); } ?> ... </html>
Dans cet exemple, nous avons laissé les paramètres par défaut, qui ont une politique de cache privée, avec une durée de vie de 0. Si cet article est public, sans condition d’accès particulière, on peut gagner des ressources en activant le cache et avec une politique publique, comme nous allons le voir dans le cas de l’image PNG suivante.
Dans ce cas-ci, nous devons générer une image PNG dynamique.
Il s’agit d’une étiquette dont le titre provient d’un fichier texte label.txt
séparé.
La date de dernière mise à jour de cette image est donc celle
de la dernière modification du fichier label.txt
contenant les données.
Cette image publique est très accédée. On souhaite qu’une copie soit conservée dans le cache du client et les caches intermédiaires (proxy, fournisseurs d’accès, etc.) afin de décharger le serveur. On choisit une durée de vie du cache de 180 secondes. C’est un compromis à faire entre la charge serveur et la fraîcheur de l’information, selon la fréquence des modifications de cette image.
image.png.php <?php require_once('http-conditional.php'); header('Content-type: image/png'); //Date de modification du fichier contenant le texte de l’étiquette $dateLastModification=filemtime('label.txt'); if (httpConditional($dateLastModification,180,2)) //Cache public, 180 secondes exit(); //Aucune modification depuis le dernier passage du client if ($handle=fopen('label.txt','r')) //Ce fichier contient “Hello World!” { $label=fread($handle,255); //Lecture du texte de l’étiquette fclose($handle); } else $label='Error'; $im=@imagecreate(120,30) or die('GD library error'); $bgcolor=imagecolorallocate($im,160,150,255); $color=imagecolorallocate($im,5,5,5); imagestring($im,5,7,7,$label,$color); imagepng($im); imagedestroy($im); ?>
Dans ce cas-ci, on veut savoir par SQL la date de modification la plus récente d’éléments situés dans plusieurs tables.
Ce fil RSS1.0 est composé des modifications les plus récentes des tables “articles”, “breves”, “documents”.
La date de dernière mise à jour est donc celle de la de plus récente modification d’une de ces tables.
Chaque table dispose d’un champ “modified” contenant une date timestamp UNIX.
Afin d’éviter de retransmettre l’ensemble des articles du fichier RSS a chaque passage du client,
nous allons filtrer les articles par date, et ne donner au client que les articles postérieurs à son dernier passage ($clientCacheDate
).
Cela procure un gain important en bande passante. Ceci implique que la réponse est personnalisée en fonction du client,
et que la mise en cache doit être privée ($cachePrivacy=0
).
Ceci est vérifié automatiquement par la fonction lorsque $feedMode
est mis à vrai.
De plus, nous allons activer la compression des données si le client la gère ($compression=true
),
ce qui permettra de diminuer la taille du texte envoyé.
Si aucun nouvel article n’est disponible, un code de non-modification 304 sera envoyé au client,
comme dans les exemples précédant.
Le support HTTP/1.1 et de la compression dans les aggrégateurs RSS (clients) est moins fréquent que dans les cas des navigateurs Internet, mais de plus en plus permettent d’utiliser cette optimisation, comme SharpReader. Là encore, si le client ne supporte pas HTTP/1.1, l’optimisation est perdue, mais la communication se déroule normalement, sans effet secondaire.
rss.php <?php require_once('http-conditional.php'); header('Content-Type: application/rss+xml'); if ($odbc=odbc_connect('basetest','user','password')) { $sql='SELECT MAX(modif) AS lastmod FROM ('. 'SELECT MAX(ar.date) AS modif FROM articles ar UNION '. 'SELECT MAX(br.date) AS modif FROM breves br UNION '. 'SELECT MAX(dc.date) AS modif FROM documents dc)'; if (($query=odbc_exec($odbc,$sql))&&odbc_fetch_row($query)) { $dateLastModification=odbc_result($query,'lastmod'); odbc_free_result($query); if (httpConditional($dateLastModification,1800,0,true,true)) //Cache privé, 30 minutes et compression activée {//Aucune modification depuis le dernier passage du client odbc_close($odbc); exit(); } //La variable globale $clientCacheDate contient maintenant la date du dernier passage du client } } echo '<','?xml version="1.0" encoding="UTF-8"?',">\n"; ?> <rdf:RDF xmlns="http://purl.org/rss/1.0/" xml:lang="en-GB" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"> <channel rdf:about="https://alexandre.alapetite.fr/blog/actu.en.html"> <title>My RSS</title> <description>Description of my RSS</description> <link>https://alexandre.alapetite.fr/</link> <dc:language>en-GB</dc:language> <dc:date>2004-07-22</dc:date> <dc:creator>Alexandre Alapetite</dc:creator> <items> <rdf:Seq> <?php if ($odbc) { /* Filtre les articles : - Uniquement les articles plus récents que $clientCacheDate (le dernier passage du client) - Articles âgés de 30 jours au maximum - Au maximum les 20 articles les plus récents. */ $clientDate=max($clientCacheDate-60,time()-(30*86400)); $sql='SELECT TOP 20 * FROM ('. 'SELECT title,link,date,description FROM articles WHERE date>'.$clientDate. ' UNION SELECT title,link,date,description FROM breves WHERE date>'.$clientDate. ' UNION SELECT title,link,date,description FROM documents WHERE date>'.$clientDate. ') ORDER BY date DESC'; if (($query=odbc_exec($odbc,$sql))&&odbc_fetch_row($query,1)) { odbc_fetch_row($query,0); while (odbc_fetch_row($query)) echo "\t\t\t\t".'<rdf:li rdf:resource="https://alexandre.alapetite.fr'.odbc_result($query,'link').'"/>'."\n"; } } ?> </rdf:Seq> </items> </channel> <?php if ($odbc&&$query) { odbc_fetch_row($query,0); while (odbc_fetch_row($query)) echo "\t\t".'<item rdf:about="https://alexandre.alapetite.fr'.odbc_result($query,'link').'">'."\n", "\t\t\t".'<title>'.odbc_result($query,'title').'</title>'."\n", "\t\t\t".'<link>'.odbc_result($query,'link').'</link>'."\n", "\t\t\t".'<date>'.substr(date('Y-m-d\TH:i:sO',$myDate=odbc_result($query,'date')),0,22).':'.substr(date('O',$myDate),3).'</date>'."\n", "\t\t\t".'<description><![CDATA['.odbc_result($query,'description').']]></description>'."\n", "\t\t".'</item>'."\n"; odbc_close($odbc); } ?> </rdf:RDF>
Cette librairie peut être utilisée conjointement aux sessions,
en activant le paramètre session
.
Elle vérifie alors automatiquement si les données contenues dans $_SESSION
ont été modifiées depuis l’appel à cette fonction lors de la dernière génération du document,
en utilisant un hachage MD5
stocké dans l’entête HTTP ETag.
Les trois cas de modifications détectées sont :
$_SESSION
a été modifié après l’appel à httpConditional()
lors de la dernière génération du document$_SESSION
a été modifié à partir d’un autre document$_SESSION
a été modifié avant l’appel à httpConditional()
dans l’exécution courante
Il faut penser à désactiver la génération automatique d’entêtes des sessions
avec session_cache_limiter('');
et/ou session.cache_limiter=''
dans php.ini
session.php <?php session_cache_limiter(''); //Désactive la génération automatique d’entêtes par la session session_start(); //Démarre la session ... require_once('http-conditional.php'); //Date de la dernière modification du contenu (format Timestamp Unix) //Exemple: requête base de données $dateDerniereModification=...; if (httpConditional($dateDerniereModification,0,0,false,true,true)) {//Aucune modification depuis le dernier passage du client ... //Ferme la base de données, et autres nettoyages exit(); //Pas besoin d’envoyer autre chose } //!\ Ne pas envoyer de texte au client avant cette ligne ... //La suite du script, comme s’il n’y avait pas cette première partie ?>
Voir l’exemple de compteur de visites en PHP/HTTP sur sa page dédiée.
$cacheSeconds==0
(valeur par défaut) une revalidation conditionnelle auprès du serveur est exigée à chaque accès au document ;
lorsque $cacheSeconds<0
la mise en cache est désactivée.Content-Length
lorsque le paramètre compression
est activé,
et ce même pour les navigateurs ne supportant pas la compression.Accept-Encoding
ne contenant ni gzip
ni deflate
.httpConditionalRefresh()
permettant de mettre à jour les entêtes HTTP si le contenu est modifié par la requête du clientIf-Modified-Since
"CC BY-SA (FR)"
Ce contenu est protégé par une licence
Creative Commons Paternité - Partage des Conditions Initiales à l’Identique 2.0 France “BY-SA (FR)”