Blog des Gens Compliqués

Contrôle du cache Nginx

02/02/2017 21:45:09+01:00|Par DkVZ
Informatique & Web
6 minutes de lecture (facile)

Table des matières

Chaque fois que je mets le code de mon blog à jour (côté client), j'ai des problèmes avec mes navigateurs de test en cela qu'ils ne demandent jamais les dernières versions des fichiers. Le serveur de test de Node.js se comporte complètement différemment.

Il semblerait que par défaut, quelque soit le navigateur, les ressources telles que le HTML, Javascript et CSS, ne sont accompagnés d'aucune en-tête de cache. Résultat, ils sont en cache pour une durée indéterminée qui peut être considérée comme théoriquement infinie. Enfin moi je la considère infinie mais j'ai tendance à arrondir au supérieur parfois.

Comportement par défaut

Par défaut Nginx me renvoie une en-tête de ce type (ici pour un .js):

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Thu, 02 Feb 2017 11:54:28 GMT
Content-Type: application/javascript
Content-Length: 40556
Last-Modified: Mon, 30 Jan 2017 09:46:06 GMT
Connection: keep-alive
ETag: "588f0b5e-9e6c"
Accept-Ranges: bytes

La seule en-tête relative au caching qui soit présente ici est ETag. C'est une signature du fichier qui permet de contrôler si on a bien la dernière version ou pas de ce fichier, en envoyant le tag au serveur pour comparaison.

Ce n'est pas très clair quand ce truc est utilisé ou non. Si je fais un Ctrl+F5 mon navigateur envoie l'en-tête cache-control -> no-cache ce qui résulte toujours en une réponse 200 complète du serveur avec tout le fichier (enfin, sauf s'il est mort (le serveur)).

J'ai vu toutes sortes d'infos comme quoi les ressources étaient en cache pour 300s, mais vu les problèmes que j'ai eu avec mon blog en production, ça a l'air d'être caché pour beaucoup plus longtemps. J'ai trouvé d'autres sources qui parlent d'une durée illimitée. En pratique jusqu'à un nettoyage manuel du cache du nabigateur, ou à une purge rendue nécessaire par dépassement de l'espace disque maximal alloué au cache du navigateur (qui est énorme il me semble).

Je voulais être certain que l'on recharge plus ou moins tout en même temps.

Rappel tout pourri sur les en-têtes de cache

Les serveurs HTTP utilisent deux en-têtes pour le caching:

  • expires
  • cache-control

Le premier étant d'office une date, le second permettant davantage de flexibilité (standard HTTP 1.1 si je ne me trompe pas (comment ce mec prend genre, aucun risque quoi)). Les ressources mises en cache sont toujours conservées avec leurs informations d'expiration. Par défaut on a des éléments qui tiennent aussi longtemps qu'une conserve de cuisses de canard confites, mais il est possible de forcer d'autres dates d'expiration, y compris signifier de toujours demander la ressource.

Le module de caching de Nginx va gérer ces deux en-têtes serveur pour nous.

Configuration du caching

Sous Nginx les en-têtes de cache sont gérées par un module appelé ngx_http_headers_module. La bonne nouvelle c'est qu'il est compilé d'office avec toutes les distributions modernes de Nginx, et il ne faut pas l'activer dans la configuration. Ce paragraphe est au moins 98% inutile.

Sur Apache par contre il faut activer un module (ou vérifier qu'il soit bien activé): https://httpd.apache.org/docs/current/fr/mod/mod_expires.html

Déclaration des politiques de caching

Il s'agit de déclarer une map dans la configuration globale de Nginx, en dehors des clauses server {}.

Sauf que cette histoire de map ne fonctionne pas avec Nginx 1.6 que j'utilisais jusqu'à présent. J'ignore la version exacte qu'il faut, mais sur Debian j'ai dû utiliser les backports pour sortir une version 1.9.10. Je pense qu'on en est à au moins 1.10 là, mais 1.9 a l'air de faire l'affaire.

Si vous avez une version inférieure, ce que vous pouvez faire sera discuté plus tard mais ce que vous pouvez faire là maintenant c'est mettre à jour votre Nginx.

La déclaration de votre map peut-être faite dans nginx.conf. Personnellement j'utilise les releases compilées pour Debian qui ont une structure de configuration utilisant /etc/nginx/sites-available pour configurer des virtual hosts. Chaque virtual hosts correspondant à une section server {}. Vous pouvez ajouter votre map juste avant cette section server {}. J'ignore si elle est utilisable globalement dans ce cas, mais ce n'était pas mon but de toute manière.

Exemple de code pour la map:

map $sent_http_content_type $expires {
    default                    off;
    text/html                  epoch;
    text/css                   max;
    application/javascript     2d;
    ~image/                    max;
}

Avec, au niveau de la syntaxe, dans la colonne de gauche:

  • default - Comportement de caching par défaut, pour tout ce qui n'est pas défini ailleurs. Je ne sais pas si on est obligé de le mettre en premier. Mettez-le en premier.
  • Un type MIME. Genre text/html. Permet de fixer un comportement de caching pour tous les fichiers qui possèdent ce type MIME.
  • Un chemin ou une expression régulière. Ce n'est pas très bien documenté mais ce qui est certain c'est que ~image/ va appliquer le comportement de caching à tous les objets qui sont dans n'importe quel répertoire ou sous-répertoire appelé image, quelque soit le type MIME de la ressource.

Dans la colonne de droite:

  • off - Se comporte comme si vous n'aviez rien fait au niveau des en-têtes de cache. Ce qui veut dire qu'on ajoute ni expires ni cache-control. D'après mes expériences, ceci a pour résultat que la plupart des navigateurs vont mettre vos ressources en cache pour une durée indéterminée (mais longue comme le Pérou).
  • epoch - Place la date d'expiration sur le début de l'époque UNIX. C'était il y a au moins 1000 ans donc ça veut dire que cette ressource est immédiatement périmée et doit être redemandée au serveur chaque fois (de nouveau ce n'est pas clair si l'ETag est utilisé).
  • max - C'est genre 10 ans. Cela ressemble au comportement par défaut sans en-têtes d'expiration. Disons que là on marque vraiment l'intention et on est certains de ce que le navigateur marque comme date d'expiration. personnellement je préfères utiliser off que max.
  • Une information sur la durée.
    • Utiliser le préfixe '@' suivi d'un nombre d'heures (max 24) et du symbole 'h' (oui ça fait beaucoup de trucs) permet de dire au navigateur que cette ressources expire tous les jours à ce moment exact de la journée (vous pouvez même fournir les minutes). Ceci signifie en pratique que votre ressource expire tous les jours. Mais vous pouvez spécifier à quelle heure.
    • Utiliser une valeur de temps telle que 42d ou 2h.
    • Utiliser modified +X où X est quelque chose comme 2h ou 2d. Ceci informe le navigateur que la ressource expire après la date de modification du fichier sur le système + la valeur qui suit. Le comportement par défaut est en fait d'utiliser l'heure système + la valeur qui suit. Normalement les données de création/modification etc. de fichier ne sont pas du tout utilisées (c'est l'ETag qui est supposé éventuellement être utilisé).

Vous pouvez déclarer plusieurs de ces maps, il faut juste changer le nom qui est $expires dans l'exemple du dessus. Laissez simplement le dollar et mettez un autre nom.

La politique que j'utilise:

map $sent_http_content_type $blog_expires {
    default     off;
    text/html   2d;
    text/css    2d;
    application/javascript    2d;
}

En gros tout expire en deux jours chez moi sauf les images.

Activation du caching

Déclarer la map ne suffit pas, il faut l'activer dans une section server {} (ou dans une section location {} qui est dans une section server{}).

Trouvez votre section server {} qui peut être dans nginx.conf ou dans mon cas (Debian), dans mon fichier de virtual host dans /etc/nginx/sites-available et ajoutez cette ligne n'importe où dedans:

expires $blog_expires;

Où le nom après le dièse est le nom que vous avez déclaré pour votre map. Dans mon premier exemple il s'agissait de $expires.

Tester la config

Il est important maintenant de tester votre configuration pour identifier si vous avez un problème de version (ou un autre problème je suppose). Comme évoqué plus haut, les maps ne semblent pas supportées dans toutes les versions.

Pour tester ma config, la commande suivante suffit. Il est possible de spécifier un fichier de configuration manuellement également.

nginx -t

Si vous voulez garder votre vieille version de Nginx, vous pouvez utiliser des directives location {} et y définir des directives expires suivis d'une durée comme par exemple:

expires 2d;

Qui signifie que les ressources à cet emplacement expirent deux jours après leur téléchargement initial.

Dans mon cas j'ai désormais ceci comme en-têtes si je demande l'index:

HTTP/1.1 200 OK
Server: nginx/1.9.10
Date: Thu, 02 Feb 2017 17:33:09 GMT
Content-Type: text/html
Last-Modified: Mon, 30 Jan 2017 09:46:06 GMT
Transfer-Encoding: chunked
Connection: keep-alive
ETag: W/"588f0b5e-14d4"
Expires: Sat, 04 Feb 2017 17:33:09 GMT
Cache-Control: max-age=172800
Content-Encoding: gzip

On remarque qu'une date d'expiration et un max-age ont été ajoutés.

Références

Commentaires

Il faut JavaScript activé pour écrire des commentaires ici

Ajouter un commentaire

Votre commentaire a été ajouté
(enfin, je pense)