Fichiers statiques et le cache de Apache

Contexte

Depuis des années, je suis embêté par gestion du cache du navigateur sur les fichiers statiques servis par le serveur HTTP Apache.

Encore une fois, ce matin, ce problème de cache me pose problème : alors que j'avais publié une mise à jour sur la page d'actualité du site Coworking Metz, je constate qu'un certain nombre de personnes ne voient pas la mise à jour, car pour la voir il faut vider le cache du navigateur en faisant par exemple un CTRL-F5 sur la page.

J'ai donc décidé d'en savoir plus… pourquoi la configuration par défaut de Apache gère "mal" à mes yeux le cache.

Le comportement que je souhaite avoir est celui-ci :

« Je souhaite que dès que je modifie le contenu d'un fichier statique, celui-ci soit tout de suite visible par mes visiteurs »

Analyse

Lors de mes développements web, j'ai déjà "joué" avec les champs header Cache-Control, If-Modified-Since, ETag, Expires, Last-Modified

Je me suis dit « Apache doit envoyer un champ Expires avec une valeur par défaut ».

J'ai cherché dans la documentation de Apache et je n'ai rien trouvé au sujet de Expires en dehors du module Expires qui n'était pas activé sur mon serveur.

J'ai ensuite cherché à analyser les champs HTTP Response retournés par le serveur sur le site Coworking Metz.

Dans un premier temps, j'ai regardé du coté de Firebug mais je n'ai pas trouvé comment voir le résultat de la requête HTTP de la page courante (dans Network on voit uniquement les ressources de la page).

J'ai ensuite trouvé l'extension HttpFox (que l'on active via le menu Affichage => HttpFox).

Avec cette extension, j'ai pu voir que le serveur Apache retourne les champs Header suivants :

(Status-Line)        HTTP/1.1 304 Not Modified
Date                 Sat, 04 Jan 2014 11:19:29 GMT
Server               Apache/2.2.22 (Debian)
Connection           Keep-Alive
Keep-Alive           timeout=5, max=100
Etag                 "1920d20-442b-4ef22c61a1396"
Vary                 Accept-Encoding

Contrairement à ce que je pensais, par défaut Apache ne retourne pas le champ Expires.

C'est donc Firefox qui a une valeur par défaut et cette valeur par défaut est expliqué ici : How are expiration times calculated (since not every response includes an Expires header) ?.

Le texte suivant mérite un exemple pour être plus facilement compris :

Finally, if neither header is present, then we look for a "Last-Modified" header. If this header is present, then the cache's freshness lifetime is equal to the value of the "Date" header minus the value of the "Last-modified" header divided by 10. This is the simplified heuristic algorithm suggested in RFC 2616 section 13.2.4.

Exemple :

  • la dernière fois que le fichier index.html a été modifié : 2014-01-04 à 11h00 (last_modified_time)
  • le 2014-01-04 à 12h00 le navigateur fait une première requête (date_value)
age_expiration = (date_value - last_modified_time) * 0.1 => 3600 secondes * 0.1 => 360s
  • le 2014-01-04 à 12h01 le navagiteur fait une seconde requête, utilisation du cache car 60s < 360s
  • le 2014-01-04 à 12h10 le navigateur fait une troisième requête, le cache n'est pas utilisé car : 600s > 360s

Conclusion pour atteindre mon objectif, mes requêtes HTTP doivent retourner Expires: 0.

Solution : configuration de Apache

Activation du module Expires :

# a2enmod expires
# service apache2 restart

Placer un fichier .htaccess à la racine du site, avec le contenu suivant :

<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 0 seconds"
</IfModule>

Code HTTP 304

Avec cette configuration, l'activité sur le serveur est certe plus élevée… mais un cache est toujours présent.

Lorsque le navigateur envoie la requête HTTP avec les paramètres suivants :

(Request-Line)      GET /news.html HTTP/1.1
Host                coworking.a-metz.info
Accept-Language     fr,en;q=0.8,fr-fr;q=0.5,en-us;q=0.3
Accept-Encoding     gzip, deflate
Connection          keep-alive
If-Modified-Since   Sat, 04 Jan 2014 11:33:53 GMT
If-None-Match       "1920d20-442d-4ef2366c0e11a"

en fonction de la date de modification du fichier news.html et de la valeur du champ If-Modified-Since, le serveur va renvoyer une réponse avec un Status 200 ou 304.

Dans le cas d'une réponse 304, le contenu de la page n'est pas envoyé au navigateur, c'est le cache du navigateur qui est utilisé.

Conclusion, avec des réponses 304, l'activité du serveur est certe supérieur mais consommation de la bande passante est tout de même limitée car le contenu des pages, des images… n'est pas transmis.

Qu'en est-t-il de Nginx ?

Par défaut, Nginx ne renvoie pas de champ HTTP ETag, ni de champ Expires.

J'ai observé que quand le serveur HTTP ne retourne pas de champ ETag alors le navigateur va interroger le serveur avec un champ HTTP If-Modified-Since pour savoir si il faut ou non utiliser le cache du navigateur.

Conclusion, la configuration par défaut de Nginx correspond au comportement que je souhaite, celui indiqué en début de billet.

Read and Post Comments

Mes flux :