Récupération et expiration en HTTP1.1

Posted on sam. 19 novembre 2016 in web

Quand je me suis attaqué à JARR, ma première motivation était d’écrire un crawler qui ne soit pas non seulement rapide mais qui respecte aussi tout un panel de bonnes pratiques et autres RFCs.

Le crawler doit en l’occurence :

  • dans le but de limiter le traffic réseau et soulager en temps CPU les serveurs distants : ne récupérer une resource que si elle a expiré
  • dans le but de limiter la consomation de resources de mon crawler : vérifier qu’une resource a changé avant de la traiter

Exposé ainsi, je pense que ça peut s’appliquer à en fait n’importe quel crawler et pas seulement un qui récupère des flux RSS, et du coup plus largement n’importe quel client web. Pour expliquer comment réaliser ces deux fonctionnalités je vais m’appuyer sur différents mécanismes de HTTP que presque tout le monde implémente plus ou moins bien. Comme base de travail je vais bien sûr utiliser la RFC2616 qui traitre de HTTP1.1.

Expiration d’une ressource

Il y bien sûr la possibilité de simplement récupérer une ressource en boucle à intervalle de temps régulier. Ça marche plutôt bien et ça reste un fonctionnement par défaut très utile quand une ressource ne dispose d’aucun mécanisme d’expiration.

Comme beaucoup de choses en HTTP on va passer par des entêtes.

Cache-Control

L’entête Cache-Control, si il est placé dans la réponse, peut préciser différentes choses concernant le contrôle de cache sur un proxy ou par un client. La directive max-age) est celle qui nous intéresse et sert à préciser le délais après laquelle une ressource est considérée comme périmée.

Par exemple le header suivant Cache-Control: max-age=600 signifie que la resource expirera dix minutes après avoir reçu la requête (en gros).

Expires

On peut aussi utiliser l’entête Expires. Celui-ci, beaucoup plus simplement, précise directement la date à laquelle la ressource sera considéré comme périmée. La date doit être précisée dans le format spécifié par la RFC1123.

Exemple assez explicite : Expires: Sat, 19 Nov 2016 14:32:47 GMT.

Les deux pouvant être présent en même temps la RFC précise à la fin de cette section que la directive max-age de l’entête Cache-Control doit prévaloir.

Remarques

Si la date d’expiration de la ressource est inférieure à votre délais de rafraichissement, ce la vous aidera à garder la ressources plus à jours. Si elle est supérieure, cela vous évitera de la récupérer probablement inutilement.

Mais ce qui est important c’est de ne pas oublier que ces valeurs ne sont que des recommandations du serveur. Il n’est pas rare par exemple de voir des valeurs pour Expires plusieurs années dans le passés ou le futur ! C’est parfois dû à la cohabitation de max-age et de Expires mais la conclusion est la même : le serveur peut mentir ou se tromper. Je suggère par conséquent de borner la valeur que vous allez utiliser en y appliquant une limite basse et haute.

Maintenant que vous avez la date d’expiration, il ne vous reste plus qu’à ne planifier la récupération qu’après cette date.

Ensuite, ce n’est pas parce qu’elle a expirée qu’elle a changé.

Vérifier qu’une ressource a changée

Pour ce faire on peut tout simplement comparer la réponse obtenue avec le résultat en cache. Mais cette comparaison implique souvent un coût (disque, réseau, base de donnée, etc) qu’on peut éviter avec, encore une fois l’usage de deux entêtes. Si le serveur reconnaît ces entêtes, il répondra avec un code 304 qui en plus d’être vide (et donc d’économiser de la bande passante) indique qu’elle ne nécessite aucun traitement supplémentaire.

Last-Modified / If-Modified-Since

Vous pouvez placer l’entête If-Modified-Since avec comme valeur la date (toujours au format de la RFC1123) de la dernière fois que la ressource a été modifié.

Vous pouvez aussi tirer la valeur de n’importe quelle réponse du serveur contenant l’entête Last-Modified.

ETag / If-None-Match

Autrement vous pouvez aussi récupérer la valeur de l’entête ETag de n’importe quelle réponse qui le contient et le passer dans vos prochaines requêtes avec l’entête If-None-Match.

Remarques

Il se peut qu’une réponse avec un code autre que 304 qui contienne un ETag correspondant à celui transmit dans la requête. Je n’ai pas trouvé de RFC spécifiant le comportement à adopter dans ce cas. Pour ce qui est de JARR j’ai pour ma part décidé que cette réponse n’invalidait pas la ressource et ne procède donc à aucune action après cette réponse.

Si possible, il faut préciser If-None-Match et If-Modified-Since. Le serveur suivra différentes règles pour savoir comment traiter les deux entêtes.