La directive Cache-Control
est une vrai mine d’or pour la gestion du cache, au risque même de faire un peu fourre-tout. Aujourd’hui je m’intéresse surtout aux notions de document public et de document privé.
Visibilité et autorisation de cache
Pour faire court, cette directive contient quatre paramètres de visibilité : public
, private
, no-cache
et no-store
.
- La valeur
no-cache
est assez simple : Les navigateurs et proxys ne doivent pas réutiliser le document reçu sans faire une nouvelle validation sur le serveur. - La valeur
private
impose aux cache partagés (les proxys) de ne pas mettre en cache la page, mais autorise les caches privés (navigateurs) à utiliser un cache sur le document. - La valeur
public
autorise explicitement le cache quel que soit les autres paramètres et le comportement normal sur ce document. - Enfin, la valeur
no-store
demande aux caches de ne stocker le message ni de manière permanente ni en mémoire volatile.
Les subtilités
On a l’impression d’une simple hiérarchie qui va de « pas de cache » à « cache partout » en passant par l’intermédiaire « cache privé ». Malheureusement les détails sont plus complexes que ça.
Le serveur proxy de votre entreprise pourra accélérer le téléchargement d’un document en no-cache
. L’astuce c’est que contrairement à ce que le nom laisse entendre, le cache n’est pas interdit. On impose juste au serveur de revalider le document avec une requête conditionnelle if-modified-since
ou if-none-match
. Le document sera quand même dans le cache partagé, et si vous gérez mal les requêtes conditionnelles il pourra être partagé entre plusieurs utilisateurs différents. À l’inverse, un document marqué comme private
ne pourra pas, lui, être accéléré par le proxy partagé car c’est directement la notion de cache qui y est prohibée. Troublant non ?
C’est no-store
qui interdit le stockage, que ce soit sur disque ou en mémoire (et empêche donc les requêtes conditionnelles par la suite). Par contre ce no-store
n’interdit pas formellement le cache. Si un proxy recevait une seconde requête similaire avant de recevoir la réponse à la première, il pourrait techniquement servir la même réponse aux deux clients tout en se conformant aux spécifications. Bref, on ne peut pas stocker mais on peut toujours partager les réponses sur un proxy commun. Je doute qu’un seul proxy sur le marché fasse de telles optimisations qui « sonnent faux », mais à défaut c’est un Cache-Control: no-cache, no-store
qu’il faut utiliser (oui, on peut cumuler).
La valeur public
pose moins de contre-sens mais cela ne veut pas dire qu’elle soit forcément bien comprise elle aussi. Elle n’implique pas uniquement que le document est public, donc potentiellement mis en cache partout. Cette valeur autorise aussi un cache là où il n’aurait pas été autorisé autrement. Ainsi une requête POST
avec un résultat public
pourra utiliser le cache. Votre formulaire de contact est public, vous avez réglé naïvement le Cache-Control
à public
(après tout, il n’est pas privé ce formulaire), et voilà que si deux personnes utilisent le formulaire à partir de la même entreprise le second aura le message de confirmation mais son message ne sera pas envoyé. Gênant non ? Même chose si vous mettez en place une authentification HTTP cumulée avec un Cache-Control: public
, vous risquez d’avoir des utilisateurs non autorisés sur le site.
Bref, pas grand chose d’intuitif et il faut faire attention aux détails.
D’autres problèmes
Du coup, conséquence directe de ces subtilités, la valeur par défaut n’est ni public
, ni private
, ni no-cache
et encore moins no-store
. Par défaut il n’y a rien et on ne peut rien préciser pour forcer explicitement le comportement « sain » par défaut.
C’est d’autant plus gênant avec PHP. Ce dernier ajoute automatiquement un Cache-Control: no-cache
à toutes les réponses si une session est utilisée sur la page. Les frameworks modernes en PHP ouvrent la session par défaut, la conséquence c’est que toutes les pages sont d’un coup interdites de partage, même les pages publiques. Il ne vous reste plus qu’à réécrire manuellement les entêtes Cache-Control
, Pragma
et Expires
(oui, les trois, PHP ne fait pas dans la dentelle). PHP mérite un billet à part entière, donc je ne m’étendrai pas plus dessus pour l’instant.
Pour ne rien arranger Microsoft Internet Explorer a un problème avec le téléchargement de certains fichiers sur HTTPS quand le Cache-Control
est à no-cache
ou no-store
(il interprête les deux de la même façon, traitant no-cache comme un no-store). Il refuse alors le stockage temporaire du fichier et ne permet donc pas son ouverture avec une application tierce (traitement de texte par exemple). Bref, même si c’est malheureux pour l’efficacité des proxy, il vaut souvent mieux cumuler un Cache-Control: private
avec un paramètre tiers pour forcer la revalidation que d’utiliser no-cache
. Et pour ceux qui suivent bien ça veut dire que télécharger une pièce jointe via https à partir d’une application PHP qui utilise les sessions, ça posera un problème si vous ne bidouillez pas session_cache_limiter()
.
Rapidement
Pour récapituler, la règle de base c’est donc : ne rien préciser sur les parties publiques du site et indiquer private
sur les parties privées (et uniquement celles ci car ça désactive tous les proxys partagés).
La valeur public
est à éviter ou utiliser avec précautions car elle risque de mettre en cache des requêtes qui ne le devraient pas ; la valeur no-cache
permettra à vos documents privés de se retrouver sur des caches partagés tout en posant des problèmes pour les téléchargements de fichiers par HTTPS, et la valeur no-store
ne garantit pas l’absence de cache commun tout en ayant les mêmes problèmes de téléchargement.
Plus loin
En allant un peu plus loin on peut même voir que le private
et le no-cache
peuvent avoir un paramètre. Il s’agit de définir précisément quel champ de la réponse ou de la requête doit être considéré comme privé ou ne doit pas être mis en cache. Je n’ai jamais vu ces paramètres utilisés et je ne vois pas comment ils pourraient l’être (pour revalider un des champs il faut de fait relancer toute la requête donc ça revient à appliquer le private
ou le no-cache
sur l’ensemble du message). Si quelqu’un voit comment ça pourrait être utilisé, qu’il n’hésite pas à commenter.
Très intéressant billet qui remets complètement à plat mes connaissances sur ces valeurs de cache. Merci d’avoir clarifier ce point !
Hello,
je ne découvre que maintenant ce billet, et me rend compte que j’ai longtemps fait fausse route : mon framework maison balance du « private, revalidate » à tour de bras alors qu’au final le comportement de « no-cache » semble correspondre à ce que je cherchais… hormis pour les sections privées, évidement.
Je vais donc devoir faire quelques tests de mon coté et corriger cela.
Il me semble d’ailleurs avoir vu cet exemple de « revalidate » sur votre ancien blog (@dreams4net), mais vous n’en parlez pas ici ; à moins qu’il s’agisse du fameux « paramètre tiers » ?
En tous cas merci !
Bonjour,
« Il ne vous reste plus qu’à réécrire manuellement les entêtes Cache-Control, Pragma et Expires (oui, les trois, PHP ne fait pas dans la dentelle). PHP mérite un billet à part entière »
Ce billet est-il écrit? je ne l’ai pas trouvé…
On ne trouve rien en français sur le net. En particulier les différentes options/attributs et leur utilité. (et encore moins pour pragma!)
Si j’ai bien compris: Pragma est utilisé avec HTTP 1.0 et 1.1. Mais est écrasé par les directives Expires (HTTP 1.0) et Cache-Control (1.1).
Tu nous dis « ne rien préciser sur les parties publiques du site », mais si on utilise php avec sessions tu dis « réécrire manuellement les entêtes Cache-Control, Pragma et Expires ». => suis paumé… j’attends avec impatience les 2 articles promis sur le caching en php et le versioning.
Merci
@Hubert : Dans l’idéal une page publique non personnalisée n’a aucune raison d’utiliser les sessions. Du coup, pas de session_start, pas de problèmes.
Sans le bazar de PHP l’avantage c’est que le navigateur fait tout ça comme il faut.
Si PHP met le bazar, tu n’as plus d’autres choix que de tout préciser explicitement. Dans ce cas c’est à toi de savoir si la page en question peut être mise en cache « public » ou pas, et éventuellement quelle est la bonne valeur à indiquer.
Malheureusement, de ma lecture de la RFC HTTP, il n’existe pas de valeur « comportement par défaut ».
Ce n’est pas le sujet ici, mais j’utilise les sessions pour suspendre la publication du site (quand on est loggé en tant qu’admin, on y a accès, sinon 503), ce n’est peut-être pas la bonne solution..?
Je ne sais pas si c’est propre à mon hébergeur mais, j’utilise session_cache_limiter (‘private_no_expire, must-revalidate’) (sans trop savoir pourquoi, l’ayant lu qqpart) sur toutes les pages avec session donc, et je peux écraser ces directives du cache avec header() en re-spécifiant des valeurs propres à chaque page.
Ça me semble bizarre, mais ça marche…
En ce qui concerne mes pages publiques personnalisées (via cookie, ex: affichage d’un panier) j’utilise no-store.
Autre chose, j’ai découvert un super site pour les réglages de cache via .htaccess:
http://www.askapache.com/htaccess/apache-speed-cache-control.html
bien pratique…
Bonjour et désolé de déterrer,
Pour les mises en cache de champs, il s’agit d’un mécanisme inventé par Tim Berners Lee, et dont j’ai du mal à comprendre la finalité dans un contexte de fichiers statiques. Quand on parles de service web REST, ça prend tout son sens. Imaginons un objet représenté par http://www.url.fr/objet. Cet objet possède deux propriété, une publique et une admin. toutes les requêtes publiques ne concernent que la propriété publique, qui sera mise en cache. Si l’administrateur modifie seulement la propriété admin, l’objet est modifié, mais il n’y a pas de raisons de recharger le cache de la vue publique de l’objet. C’est justement ce que permet ce mécanisme.
Piero