Expires et Cache-Control, une date limite de consommation pour vos contenus

Je parle de détails et de ce que je rencontre dans mes lectures, et j’en oublie le principal. alors voilà, si vous ne devez retenir qu’une chose c’est d’utiliser des dates d’expiration explicites sur vos contenus.

Il s’agit d’informer le navigateur que votre contenu est valable pendant une certaine durée. Cela peut être dix minutes, une heure, un jour, un mois, ou plusieurs années. Tant que cette durée n’a pas expirée, le navigateur sait que son contenu est à jour, il peut donc éviter de le retélécharger. Vous gagnez du temps en évitant la requête HTTP et vous laissez de la place pour d’autres téléchargements. Au lieu de prendre une demi seconde, votre contenu sera lu quasiment instantanément.

Les détails HTTP 1.0

En fait tout ça c’est assez simple. En HTTP 1.0, le serveur envoie avec le contenu une entête Expires avec une date au format de la RFC 1123.

Expires: Thu, 01 Dec 1994 16:00:00 GMT

Le navigateur et les proxy stockent dans le cache la ressource avec cette date. Les prochaines fois c’est le contenu sauvegardé dans le cache qui est utilisé, sans aucun appel au serveur.

Une fois la date dépassée, le navigateur fait de nouveau appel au serveur pour obtenir un contenu mis à jour, et éventuellement une nouvelle date d’expiration explicite. C’est normalement une requête conditionnelle qui est faite à ce niveau là donc si le contenu n’a toujours pas changé il ne sera toujours pas retéléchargé inutilement.

Les détails HTTP 1.1

Le gros défaut HTTP 1.0 c’est qu’en général on ne souhaite pas une date d’expiration fixe, on cherche une expiration de type « maintenant + 10 jours ». Résultat, il faut générer la date à chaque fois.

HTTP 1.1 nous propose un mécanisme plus sympathique avec l’entête Cache-Control dont je vous avais détaillé un des paramètres il y a quelques temps. Le paramètre qui nous intéresse ici est le paramètre max-age. Il prend un nombre de secondes pendant lesquelles le contenu doit être considéré comme à jour.

Cache-Control: public;max-age=3600

Cette directive est valable autant pour les caches partagés (proxy) que les caches personnels (navigateur). Il existe toutefois un second paramètre s-maxage, qui ne cible que les caches partagés. Le fonctionnement est similaire.

J’utilise quoi ?

Autant que possible, utilisez les deux. Rien n’est parfait dans ce bas monde et vous n’avez pas de garantie que la directive Cache-Control sera interprétée correctement, même si le client utilise la version 1.1 du protocole.

En cas de conflit le client est censé utiliser la directive Cache-Control. Mais après tout, pourquoi voudriez-vous envoyer deux informations contradictoires ?

Comment ?

Le plus simple sous Apache c’est mod_expires. Vous pouvez régler le comportement par défaut et par type de fichier. À chaque fois vous pouvez définir une durée pendant laquelle le contenu est considéré comme à jour, et si cette durée est à prendre à partir de la dernière modification de votre contenu ou de la date de la requête.

ExpiresDefault "access plus 1 month 15 days 2 hours"
ExpiresByType image/gif "modification plus 5 hours 3 minutes"

Pour Lighttpd le module a le même nom, et la syntaxe est assez similaire :

$HTTP["url"] =~ "^/images/" {
     expire.url = ( "" => "access 1 hours" )
}

Sur quoi ?

Ces entêtes sont pertinentes pour tout ce qui bouge peu, pas du tout, ou à une fréquence déterminée :

Prenez en compte au moins vos logos, vos icônes, vos images de fond, bref, ce qui ne change quasiment jamais, sans exception. Pour ces contenus vous pouvez mettre une expiration virtuellement infinie, de plusieurs années. Le jour où vous faites une modification il suffira de faire un nouveau fichier avec une nouvelle adresse publique.

Pour vos feuilles de style et vos javascript, même chose. Vos contenus changent plus souvent mais si vous arrivez à changer l’URL à chaque modification vous pouvez encore mettre une expiration quasi infinie. Pour changer l’adresse à chaque version vous pouvez ajouter le numéro de version dans le nom du fichier, ou ajouter un paramètre à l’url qui contient la date de dernière modification. Je ferai un billet plus complet sur le sujet plus tard.

Enfin, les pages HTML. Là il faut être plus fin. Toutes les pages ne sont pas pertinentes pour une expiration. Plus classiquement si à deux minutes d’intervalle il est important que le contenu change, oubliez l’expiration explicite. Par contre une bonne partie des pages peuvent avoir une expiration de quelques minutes, voire d’une ou deux heures. C’est souvent le cas des pages d’accueil et des pages principales. L’utilisateur y revient souvent lors de sa navigation. Si ces pages peuvent être instantanées, c’est un réel gain pour l’utilisateur. Si vous avez un système d’authentification sur vos pages, n’oubliez pas de déclarer aussi le cache comme privé avec la directive Cache-Control.

Le bénéfice

Un contenu avec une expiration explicite c’est un contenu qu’on évite de retélécharger plusieurs fois inutilement. Pour l’utilisateur c’est la sensation que le téléchargement est instantané, que l’interface et que le site réagissent immédiatement — et n’oubliez pas, cela a une influence importante sur votre business. Pour vous c’est autant de charge en moins pour votre serveur, et autant de bande passante économisée. Rien que là dessus vous pouvez économiser des sommes importantes.

Pour exemple, la page de Yahoo! met environ deux secondes tout compris à charger avec plus de 40 requêtes HTTP, ce qui en soit est assez honorable face au reste du web. Par contre sur cette quarantaine de requêtes HTTP, plus des trois quarts ont une expiration explicite comme détaillé plus haut. Le résultat c’est que ma seconde visite ne télécharge plus que 7 composants externes. Ma bande passante est libre pour autre chose et ma page se charge bien plus rapidement. Suivant les publicités sur la page, c’est entre une demi seconde et une seconde que je gagne. Pour une page d’accueil qui ne doit surtout pas donner une impression de lenteur, c’est significatif et même important.

Publié par edaspet

Plus d'informations sur mon profil en ligne

22 réponses sur « Expires et Cache-Control, une date limite de consommation pour vos contenus »

  1. Une des optimisations les moins coûteuses (une ou deux lignes dans un fichier de configuration) et qui apporte le plus en terme de performance (mais aussi bien sur en terme de bande passante et de charge serveur).

  2. Bonjour.

    « Pour changer l’adresse à chaque version vous pouvez ajouter le numéro de version dans le nom du fichier, ou ajouter un paramètre à l’url qui contient la date de dernière modification. Je ferai un billet plus complet sur le sujet plus tard. »

    est-ce que ce billet exixte? je ne l’ai pas trouvé.

    Merci

  3. Bonjour,

    Excellent blog ! Bravo, très instructif ! 😉

    J’aurais juste une petite question technique si vous permettez : quelles différences entre les directives « max-age » et « max-stale » de Cache-Control ?

    Merci !
    Sébastien

  4. max-age est une information envoyée par le serveur pour gérer le cache client.

    max-stale et min-fresh sont des informations envoyées par le client pour gérer les caches intermédiaires (les proxys) et éventuellement le cache applicatif.

    Un serveur ou un proxy qui reçoit min-fresh=30 est informé de renvoyer une information qui date de 30 secondes au maximum et de la recalculer si besoin.

    Un serveur ou un proxy qui reçoit max-stale=30 est informé qu’il a le droit de renvoyer une information qui a déjà expirée mais dont l’expiration date d’au plus 30 secondes.

    Plus de détails sur la RFC HTTP : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

  5. Merci beaucoup pour ces explications !

    Encore félicitations pour ce blog, il est dans mes favoris !

    Il y manquerait peut-être encore un petit billet sur les livres et les références que vous recommandez pour aller plus loin. 😉

    A quand un « optimisation web – avancé » ? ^^

    Bon weekend,
    Sébastien

  6. Bonjour,
    j’ai lu ici
    http://www.sitepoint.com/article/save-cash-optimize-cache/
    qu’il fallait utiliser expires OU cache-control, tu dis les deux… (j’ai mis les 2)
    Mais au fait http 1.0 ou http 1.1 c quoi au juste? je sais un protocol; mais qui détermine l’utilisation de l’un ou l’autre (le serveur, le navigateur, mon fournisseur d’accès) et comment fait-on pour savoir quel est le protocol utilisé? Excusez la naïveté, mais j’aurais besoin d’éclaircissement.
    Merci
    (et merci surtout car grâce à ce site j’obtiens B au lieu de F initialement dans Yslow, mais c du boulot! mais le client est content lol)

  7. Je confirme pour l’utilisation des deux. Ils disent d’ailleurs explicitement que Cache-Control prend le pas sur Expires. Il n’y a aucun désavantage (à ma connaissance) à utiliser les deux.

    Pour le protocole la plupart des clients et des serveurs utilisent la version 1.1. Certains client sont en 1.0, c’est rare mais ça arrive. Il y a une auto-négociation assez simple l le client déclare s’il veut du 1.0 ou du 1.1, le serveur répond au mieux (1.1 si le client le supporte et le serveur aussi, 1.0 sinon).

    Le 1.1 c’est juste une mise à jour, avec des fonctionnalités en plus. C’est globalement compatible avec le 1.0, mais ça a quelques impacts quand même.

  8. Bonjour Eric,

    Tout d’abord, un grand merci pour toutes les connaissances que tu prends le temps de partager.

    Voilà donc ma question:

    Qu’est ce que mod_expires apporte de plus que le fonctionnement par défaut du système de cache d’un navigateur ?

    En effet, dans le livre de Steve Souders, on peut lire page 23 (Règle3: le champ d’en-tête Expires):
    «Lors de sa première visite sur votre page, l’utilisateur exécute via son navigateur un certain nombre de requêtes HTTP. Toutefois, l’utilisation du champ d’en-tête Expires offre à vos composants la possibilité d’être mis en cache.»

    Or j’ai fait des tests en m’aidant de l’extension LiveHTTPHeaders (sous Firefox 3.0.7) et ce que je n’ai pu que constater c’est que, alors que mod_expires est désactivé, tous les éléments de mes pages sont cachés par le navigateur et réutilisés à chaque nouvelle visite.
    J’ai pu donc vérifier que le simple usage du système de cache du navigateur dispense ce dernier de faire d’innombrables requêtes GET conditionnelles. C’est sur la base de cette observation que je te pose ma question.

    Cependant, ce comportement du navigateur soulève une autre question:
    Puisqu’aucun élément caché n’est associé à une date d’expiration, comment se fait-il que le navigateur n’envoie pas de requêtes GET conditionnelles pour s’assurer qu’il n’existe pas de versions plus récentes sur le serveur ?

    Dernière toute petite question:
    Dans ta présentation (celle de la vidéo) tu expliques que le grand avantage de mod_expires est que les éléments statiques sont cachés «à vie» (en tout cas pour une très longue période).
    Faut-il comprendre qu’un élément associé à une date d’expiration, disons en 2020, ne sera pas retiré du cache avant cette date même pour faire de la place à d’autres éléments ? Pour moi la réponse est bien sûr non, mais….

    Cordialement

    Pascal

    Cordialement,

    Pascal

  9. Bonjour,

    Par défaut le navigateur devrait utiliser la revalidation. C’est d’autant plus vrai si la directive Cache-Control contient un « must-revalidate ».

    Maintenant techniquement le navigateur est habilité à mettre en cache les informations comme il le souhaite. Il a un algorithme propre pour ça, ainsi que des préférences dans le navigateur. IE 6 est connu pour parfois mettre un peu trop de choses en cache.

    Parfois il y aura revalidation par requête conditionnelle, souvent, mais parfois pas. Généralement ces éléments qui sont mis en cache spontanément par le navigateur ne le sont que pendant une durée courte, ou pendant la session (en fermant/rouvrant le navigateur, il y a revalidation).

    Tu as un exemple d’url à me proposer qu’on puisse regarder sur la même base ?

    Sinon pour la date, c’est mis en cache à vie … sous réserve que l’utilisateur ne vide pas son cache et que jamais ce cache ne devient plein. Une fois plein, le navigateur est obligé de retirer des éléments anciens, il choisit tout seul lesquels, les tiens peuvent en faire partie.

    Bref, en pratique tu donnes des instructions mais tu ne contrôles rien. Le client peut en tenir compte, ou pas.

    En pratique entre ceux qui vident leurs cache, les proxys filtrant, ceux qui viennent pour la première fois et ceux qui ont un cache qui a été renouvelé, tu auras toujours une part non négligeable d’utilisateurs qui n’ont pas les éléments en cache.
    Yahoo! a publié un graphique intéressant à ce sujet. Regardes la figure 3 sur http://yuiblog.com/blog/2007/01/04/performance-research-part-2/

  10. Bonjour Eric,

    Voici mon protocole de test:

    Apache 1.3.41 (compilé à la main) + PHP 4.4.9 + eZ Publish

    1/ désactivation de mod_expires

    2/ vidage du cache de Firefox 3.0.7

    3/ Visualisation des pages de luxpopuli.fr + d’autres sites

    Quand je reviens au bout d’un moment sur mon site, je visualise les en-têtes avec livehttpheaders 0.14 ou Firebug (module Réseau -> Tout)

    Tu peux tester sur tout mon site, je remettrai mod_expires quand tu auras fini.

    Comment faites-vous chez Yahoo! pour déterminer la proportion de visiteurs qui ont un cache vide ou pré-rempli ? Vous avez développé un logiciel ou bien est-ce en recoupant d’autres informations (taux de rebond, nombre de page par visiteur, nombre de visites inférieures à 10s, etc…) que vous faites une estimation ?

    A propos des ETags, sous Apache sous 1.3 il est impossible de les désactiver. La doc offcielle dit pour l’option None que «If a document is file-based, no ETag field will be included in the response». Comme tu pourras le constater, seul mon document HTML n’est pas associé à un ETag. Tout les autres éléments si. Peut-être que j’interpète mal l’expression «file-based» ?

    Cordialement,

    Pascal

  11. Salut Eric,

    Je viens de découvrir que si je mets dans mon .htaccess les lignes suivantes, ça ne marche pas chez moi. Une idée ?

    <IfModule mod_expires>
    ExpiresActive On
    ExpiresDefault « access plus 24 hours »
    ExpiresByType text/javascript « access plus 30 days »
    ExpiresByType text/css « access plus 30 days »
    ExpiresByType image/* « access plus 30 days »

    </IfModule>

  12. Ca m’a l’air bon à première vue.

    Tu veux bien retirer ton ifModule voir s’il te sort une erreur ? éventuellement tenter un IfModule mod_expires.c

    Sinon une expiration par défaut à 24 heures ça fait finalement beaucoup. Ca veut dire que si on répond à un de tes billets, on ne verra pas son propre commentaire (ni les suivants) avant 24 heures, et que toi tu ne peux pas faire de corrections quelques minutes après la publication (et si tu es comme moi tu en as besoin à chaque fois).

    Plus probablement, pour un blog :
    – accueil et catégories : 10 minutes (juste histoire que ça tienne une session de navigation)
    – billets récents : quelques minutes au plus, on peut se contenter de gérer les requêtes conditionnelles
    – images de décoration génériques, css, javascripts : une semaine si ce n’est pas versionné dans l’url, quelques années sinon
    – images de contenu dans les billets : entre 10 minutes et quelques heures (sachant que les requêtes conditionnelles prendront le relai au delà et qu’en général il n’y a pas 150 images de contenu par billet de toutes façons)

  13. moi, je voudrais faire une exception sur les images, pour qu’elles ne soient pas enregistrées dans le cache.

    le : header(« Cache-Control: no-cache »); ne suffit pas pour firefox

    quels sont les bons paramètres ?

Les commentaires sont fermés.