Date de publication du RFC : Juin 2014
Auteur(s) du RFC : R. Fielding (Adobe), J. Reschke
(greenbytes)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 14 juin 2014
Dans la longue série des RFC sur le protocole HTTP 1.1, ce document relativement court se consacre aux requêtes HTTP conditionnelles : quand un client dit à un serveur « envoie-moi le contenu de cette ressource mais seulement si telle ou telle condition est vraie ». La principale utilité de ces requêtes conditionnelles est d'économiser la capacité réseau si le client a déjà une copie du contenu. Une autre utilité est de permettre la mise à jour d'un document si et seulement si il n'a pas été modifié depuis que le client a commencé à travailler dessus, évitant ainsi le problème de la « mise à jour perdue ». Il est maintenant dépassé par le RFC 9110.
Pour indiquer ces conditions, le client ajoute un ou plusieurs en-têtes (RFC 7231) à sa requête. Quels sont les éléments dont dispose le client pour indiquer les conditions ? On les nomme les validateurs. Deux validateurs sont très répandus, la date de dernière modification d'une ressource et l'étiquette de ressource (entity tag ou ETag). D'autres peuvent être définis, par exemple par WebDAV (RFC 4918). Un validateur est fort ou faible. Un validateur fort est un validateur dont la valeur change à chaque changement, même trivial, du contenu d'une ressource (les étiquettes sont dans ce cas, comme le sont les identifiants de commits d'un VCS comme git ou Subversion). Avec du contenu Web statique, et s'il n'y a pas de négociation du contenu, une façon simple de générer un validateur fort est de condenser le contenu de la ressource, et d'utiliser le condensat comme étiquette. Les validateurs forts sont idéaux (ils identifient précisement un état d'une ressource) mais souvent difficiles, voire impossibles, à générer. Au contraire, les validateurs faibles sont moins précis (plusieurs états d'une ressource peuvent correspondre à la même valeur du validateur) mais simples à générer. Par exemple, une date de dernière modification est un validateur faible : si sa résolution est d'une seconde, et que deux changements de la ressource sont faits dans la même seconde, le validateur ne changera pas. HTTP permet les deux, chacun ayant son utilité. Mais le développeur qui se sert des validateurs pour son application a fortement intérêt à connaître la différence ! Le RFC donne l'exemple de rapports météorologiques : pour éviter qu'un rapport reste dans un cache Web (RFC 7234) alors qu'une version plus récente existe sur le serveur d'origine, le gérant de cette base de rapports doit veiller à ce que les validateurs changent quand le contenu change. D'une manière générale, les validateurs faibles sont acceptables quand on veut juste optimiser les coûts (cas des caches) mais lorsqu'on veut modifier un contenu et éviter l'effet « perte de mise à jour », il faut utiliser un validateur fort.
Comment un serveur HTTP communique-t-il un validateur au client ? Via les en-têtes de la réponse (section 2.2 du RFC). Il y a deux en-têtes possibles :
Last-Modified:
qui indique la date de
dernière modification de la ressource,ETag:
qui indique une étiquette identifiant
une version spécifique de la ressource.
Le RFC prescrit à tout serveur HTTP d'envoyer systématiquement un
Last-Modified:
dès lors qu'il peut déterminer la
date de modification (si la ressource est assemblée à partir de
plusieurs éléments, c'est le plus récent qui compte). Cela permettra d'utiliser plus souvent les
caches et donc de diminuer la consommation de capacité réseau. Un exemple de cet en-tête (la dernière ligne) :
% curl -v http://verite/ > GET / HTTP/1.1 ... < HTTP/1.1 200 OK < Server: nginx/1.6.0 < Date: Thu, 29 May 2014 19:45:17 GMT < Content-Type: text/html < Content-Length: 612 < Last-Modified: Thu, 24 Apr 2014 14:14:52 GMT
Dans la grande majorité des cas, le
Last-Modified:
est un validateur faible. (Ne
serait-ce que parce que la ressource peut changer deux fois en une
seconde, ce qui ne changera pas le
Last-Modified:
.)
Le ETag:
, lui, est un identificateur
opaque. Il est en général plus difficile à
générer pour le serveur mais bien plus fiable (il évite notamment le
problème des « deux changements dans la même seconde »). En général,
c'est un validateur fort. Un exemple :
% curl -v http://verite/toto.txt ... > GET /toto.txt HTTP/1.1 ... < HTTP/1.1 200 OK < Server: nginx/1.6.0 ... < ETag: "53878f56-5"
Comme c'est un identificateur opaque, seul le serveur sait comment il a été généré. Premier critère avant de choisir une méthode de génération : garantir que deux versions différentes d'une ressource auront deux ETag différents. Deux méthodes courantes pour le générer sont l'utilisation d'un numéro de révision interne (par exemple si les ressources sont stockées dans un VCS) ou bien l'utilisation d'une fonction de condensation. Là encore, le RFC demande que le serveur envoie une étiquette si possible, sauf s'il y a de bonnes raisons, par exemple de performance. Donc, un serveur qui suit le RFC de près enverra les deux validateurs, la date et l'étiquette, en réponse à chaque requête.
Comment le client utilise-t-il ce validateur (section 3) ? Il
dispose d'un choix d'en-têtes à mettre dans la requête permettant de
dire au serveur « agis sur la ressource si et seulement si telle
condition portant sur les validateurs est vraie ». Par exemple,
l'en-tête If-Match:
indique une condition portant
sur l'étiquette :
If-Match: "4149d-88-4b1795d0af140"
La condition ci-dessus, dans une requête GET
,
POST
ou autre, signifie au serveur de n'agir que
si la ressource correspond à l'étiquette indiquée. Pour une méthode
modifiant l'état de la ressource (comme POST
ou
PUT
), cela permet d'éviter le syndrome de la mise
à jour perdue. Voici un exemple, n'utilisant pas
les requêtes conditionnelles :
GET
une
ressource,PUT
. Les modifications de l'étape précédente sont
donc perdues.Avec les requêtes conditionnelles, on aurait eu :
GET
une
ressource, et obtient un validateur fort, l'étiquette e68bd4cc10e12c79ff830b0ec820ef6b
,PUT
en ajoutant un If-Match:
"e68bd4cc10e12c79ff830b0ec820ef6b"
. Le serveur calcule
l'étiquette (ici, c'est un condensat MD5), voit
qu'elle ne correspond pas, et refuse le PUT
avec
un code 412. Les modifications de l'étape précédente ne sont pas
perdues.If-Match:
est surtout utile dans les
opérations modifiant la ressource comme PUT
. Son
opposé If-None-Match:
sert plutôt pour les
GET
lorsqu'un cache dit à un serveur
« envoie-moi une copie si elle est différente de celle que j'ai
déjà ». Notez que If-None-Match:
peut prendre
comme valeur une liste de ETags.
Il y a aussi des pré-conditions qui portent sur la date de
modification et non plus sur l'étiquette. Ce sont
If-Modified-Since:
et
If-Unmodified-Since:
. Si on envoie :
If-Modified-Since: Mon, 26 May 2014 19:43:31 GMT
dans une requête GET
, on ne recevra le contenu de
la ressource que s'il est plus récent que le 26 mai 2014. Autrement,
on aura un 304, indiquant que le contenu n'a pas été changé
depuis. C'est ainsi qu'un cache peut s'assurer que la copie qu'il
détient est toujours valable, sans pour autant consommer de la
capacité réseau. C'est également utile pour un
crawler, par exemple celui
d'un moteur de recherche qui ne veut pas
récupérer et indexer un contenu qu'il connait déjà. Le cache qui a
reçu un Last-Modified:
au dernier
GET
conserve la valeur de ce
Last-Modified:
et la renvoie dans un
If-Modified-Since:
la fois
suivante. curl a des options pour cela. Ici, un
script de téléchargement qui trouve dans un fichier la date de
dernière modification, et ne télécharge que si le fichier est plus récent :
ltr_date=`head -n 1 ${LTR_LOCAL} | cut -d" " -f2` # Allow time to elapse. The date of the file at IANA is often the day after # the date written in the LTR. Heuristically, we add one day and a few hours. current_date=`date +"%Y%m%d %H:%M:%S" --date="${ltr_date} +1 day +4 hour"` ... curl --time-cond "${current_date}" ...
Ces en-têtes sont enregistrés à l'IANA. Leur usage est facultatif pour le serveur HTTP et, par exemple, nginx ignore ces en-têtes, limite signalée depuis pas mal de temps.
La section 4 liste les deux codes de retour HTTP en rapport avec
ces requêtes conditionnelles. 304 indique que le contenu n'a pas été modifié
depuis la date donnée et le serveur redirige (d'où le 3xx) le client
vers sa copie locale pré-existante. 412 indique qu'une pré-condition nécessaire n'est pas vraie. C'est
en général le résultat d'une requête avec
If-Match:
lorsque l'étiquette ne correspond plus
au contenu de la ressource. Ces deux codes sont dans le
registre IANA.
Les sections 5 et 6 précisent l'ordre d'évaluation des pré-conditions, entre elles, et par rapport aux autres critères de recherche. Notamment, les pré-conditions ne sont pas utilisées si la ressource n'existe pas ou si son accès est interdit. Si on a un 404 (ressource non trouvée) sans pré-conditions, on aura le même 404 avec pré-conditions, l'existence de la ressource est testée avant les pré-conditions.
Et les pré-conditions entre elles, puisqu'on peut en avoir plusieurs dans une requête ? Le serveur doit évaluer dans cet ordre (en supposant à chaque fois que l'en-tête en question soit présent ; sinon, on saute au test suivant) :
If-Match:
, car il faut avant tout
éviter la mise à jour perdue,If-Unmodified-Since:
(qui passe
après car les dates sont moins fiables que les étiquettes),If-None-Match:
(il sert à la
validation des caches, ce qui est moins crucial que d'empêcher la mise
à jour perdue),If-Modified-Since:
.Et, pour finir, quelques considérations de sécurité, en section
8. D'abord, les validateurs ne sont pas des mécanismes de contrôle
d'intégrité, comme peut l'être une signature
numérique (le serveur peut mentir, ou un tiers situé sur
le trajet a pu modifier les en-têtes en vol). Ensuite, les étiquettes,
les ETag peuvent poser des problèmes de protection
de la vie privée : un serveur méchant peut
générer une étiquette unique pour un client donné ce qui, lorsque le
client reviendra et enverra des If-Match:
ou
If-None-Match:
permettra de le reconnaître. Une
sorte de cookie caché,
donc. Un navigateur Web doit donc les traiter comme tel (oublier les
étiquettes lorsque l'utilisateur vide le stock des
cookies, par exemple).
L'annexe A dresse une liste des différences par rapport aux sections correspondantes du RFC 2616. Rien de fondamental mais la définition de la force et de la faiblesse des validateurs a été étendue et précisée, et une définition de l'ordre d'évaluation des pré-conditions a été ajoutée.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)