Implémentez GET conditionnel en envoyant des validateurs stables avec chaque document, en vérifiant les validateurs enregistrés par le client et en renvoyant soit le document actuel, soit une réponse 304 Not Modified sans corps. Ce tutoriel pour Mac illustre le processus avec une page de texte synthétique.
L’objectif est que les requêtes répétées d’un document public ne transfèrent son contenu que lorsque c’est nécessaire. Vous allez exécuter un petit serveur Python local, le tester depuis Terminal et simuler une mise à jour du document. L’exemple reste local ; sa publication est une tâche distincte.
Comment le serveur doit-il choisir entre 200 et 304 ?
Une requête initiale reçoit 200 OK, le corps du document et ses validateurs. Une requête conditionnelle ultérieure renvoie les validateurs enregistrés. Si le document n’a pas changé, le serveur renvoie 304 Not Modified, ce qui permet au client de réutiliser le corps stocké. Un contenu modifié reçoit une nouvelle réponse 200. Requêtes HTTP conditionnelles.
Pour un document existant qui renverrait normalement 200, appliquez la séquence de décision suivante. If-None-Match a priorité sur If-Modified-Since ; il ne s’agit pas de deux vérifications indépendantes. RFC 9110, champs de requête conditionnelle.
| Condition de la requête | Réponse |
|---|---|
| Aucun en-tête conditionnel | 200 avec le document |
If-None-Match correspond au tag actuel |
304, sans corps |
If-None-Match est présent mais ne correspond pas |
200, même si la date fournie indique l’absence de changement |
Seul un If-Modified-Since valide est fourni et le document n’est pas plus récent |
304, sans corps |
Seul un If-Modified-Since valide est fourni et le document est plus récent |
200 avec le document |
Évaluez ces conditions après avoir sélectionné le document et établi sa réponse normale. Un document manquant doit toujours produire la réponse normale de ressource introuvable. Une réponse 304 ne peut contenir ni contenu ni champs de fin. Elle doit inclure les métadonnées de cache applicables, notamment les champs Date, ETag, Vary, Cache-Control, Expires et Content-Location qui accompagneraient sa réponse 200. RFC 9110, évaluation des préconditions et exigences relatives à 304.
Choisir des validateurs qui décrivent le document publié
Un ETag est un identifiant de version entre guillemets, par exemple "doc-v1". Sa méthode de génération est définie par l’implémentation : un identifiant de révision ou un hachage du contenu peuvent convenir. Il doit rester stable pour une même représentation et changer lorsque celle-ci change. L’exemple utilise deux révisions explicites dont le contenu est fixe. En-tête ETag.
Un tag fort identifie une représentation précise ; un tag faible porte le préfixe W/ et peut décrire un contenu sémantiquement équivalent dont les octets diffèrent. Les comparaisons GET conditionnelles avec If-None-Match utilisent une comparaison faible : W/"doc-v1" correspond donc à "doc-v1". Les requêtes peuvent également contenir une liste de tags ou le joker autonome *. RFC 9110, If-None-Match.
Last-Modified indique quand le document a changé. Utilisez une date de modification déterminée de façon cohérente, tenant compte des composants pertinents du document, plutôt que l’heure de chaque requête. Elle ne doit pas être postérieure à la date de la réponse (Date). RFC 9110, génération de Last-Modified.
L’exemple attribue des dates de publication délibérément illustratives à ses deux révisions synthétiques : midi GMT les 1er et 2 janvier 2024. Ces dates servent de données de test. Dans un véritable processus de publication, utilisez des métadonnées de publication maintenues à jour. Last-Modified est moins précis qu’un ETag pour détecter les différences de contenu, mais constitue une solution de repli utile. En-tête Last-Modified.
Choisir séparément une politique de cache
Les validateurs indiquent si une copie stockée correspond toujours à l’original. La politique de fraîcheur détermine quand le cache doit vérifier. Ce tutoriel envoie Cache-Control: no-cache, ce qui autorise le stockage, mais exige une validation réussie avant toute réutilisation. Le comportement des requêtes répétées est ainsi facile à observer. no-store interdit le stockage, tandis que max-age autorise la réutilisation tant qu’une réponse est fraîche. RFC 9111, directives de cache des réponses.
Pour un document public qui peut tolérer un court délai avant qu’une mise à jour soit visible, une politique telle que max-age=60 peut convenir. Il s’agit d’un choix éditorial donné à titre d’exemple : déterminez l’intervalle en fonction des exigences de mise à jour du document. Une réponse en cache encore fraîche peut être réutilisée sans contacter le serveur ; chaque visite répétée ne génère donc pas nécessairement une réponse 304. RFC 9111, fraîcheur.
Exécuter un exemple local complet sur votre Mac
Ouvrez Terminal et vérifiez les prérequis avec les commandes suivantes. Utilisez Python 3.9 ou une version ultérieure, car le programme utilise str.removeprefix. Si python3 n’est pas disponible, installez Python 3 avant de continuer. Aucun paquet Python tiers n’est nécessaire.
python3 --version
curl --version
Dans un nouveau dossier de travail, enregistrez le programme complet suivant sous le nom server.py à l’aide d’un éditeur de texte brut. Il ne sert que /page.txt, implémente GET et HEAD, et sélectionne une révision du document à partir de son argument de ligne de commande. BaseHTTPRequestHandler de Python distribue les requêtes aux méthodes correspondantes et fournit des fonctions d’écriture de réponse. Documentation du serveur HTTP Python.
import argparse
import re
from datetime import datetime, timezone
from email.utils import format_datetime, parsedate_to_datetime
from http.server import BaseHTTPRequestHandler, HTTPServer
parser = argparse.ArgumentParser()
parser.add_argument("version", type=int, choices=(1, 2))
args = parser.parse_args()
BODY = {
1: b"Paper Shapes\nA circle and a square.\n",
2: b"Paper Shapes\nA circle, a square, and a triangle.\n",
}[args.version]
ETAG = '"doc-v%d"' % args.version
MODIFIED = datetime(2024, 1, args.version, 12, tzinfo=timezone.utc)
LAST_MODIFIED = format_datetime(MODIFIED, usegmt=True)
TOKEN = r'(?:W/)?"[\x21\x23-\x7e\x80-\xff]*"'
TAG_LIST = re.compile(rf'[ \t]*(?:{TOKEN})?(?:[ \t]*,[ \t]*(?:{TOKEN})?)*[ \t]*')
def tag_matches(value):
if value.strip() == "*":
return True
if not TAG_LIST.fullmatch(value):
return False
return any(tag.removeprefix("W/") == ETAG
for tag in re.findall(TOKEN, value))
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.serve_document()
def do_HEAD(self):
self.serve_document()
def serve_document(self):
if self.path != "/page.txt":
self.send_error(404)
return
not_modified = False
tags = self.headers.get_all("If-None-Match")
if tags is not None:
not_modified = tag_matches(",".join(tags))
else:
dates = self.headers.get_all("If-Modified-Since", [])
if len(dates) == 1:
try:
since = parsedate_to_datetime(dates[0])
if since.tzinfo is None:
since = since.replace(tzinfo=timezone.utc)
not_modified = MODIFIED <= since
except (TypeError, ValueError, OverflowError):
pass
self.send_response(304 if not_modified else 200)
self.send_header("ETag", ETAG)
self.send_header("Cache-Control", "no-cache")
if not not_modified:
self.send_header("Last-Modified", LAST_MODIFIED)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(BODY)))
self.end_headers()
if not not_modified and self.command == "GET":
self.wfile.write(BODY)
if __name__ == "__main__":
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
Il s’agit d’une démonstration locale des deux validateurs nommés, et non d’une pile complète de gestion HTTP. La conversion des dates utilise l’analyseur permissif de dates des e-mails de Python ; une implémentation déployée devrait utiliser une validation des dates propre à HTTP et gérer les requêtes conditionnelles. La documentation Python décrit les entrées acceptées par cet analyseur et ses exceptions. Utilitaires de dates Python pour les e-mails.
Dans Terminal, placez-vous dans le dossier contenant server.py et démarrez la révision 1. Laissez cette fenêtre ouverte. Si le port 8000 est déjà utilisé, choisissez un autre port disponible dans le programme et utilisez le même port dans toutes les URL de test.
python3 server.py 1
Tester le contenu inchangé, le contenu modifié et la priorité
Ouvrez une autre fenêtre Terminal. Les requêtes suivantes affichent les en-têtes de réponse et tout corps renvoyé. L’option -i inclut les en-têtes, -H fournit un en-tête de requête et --max-time limite la durée de chaque transfert. Manuel curl.
Établir la réponse initiale
curl --max-time 5 -sS -i http://127.0.0.1:8000/page.txt
Attendez-vous à recevoir 200 OK, le texte de la page synthétique, ETag: "doc-v1" et Last-Modified: Mon, 01 Jan 2024 12:00:00 GMT. Lorsque vous appliquez cette procédure à un autre document, notez les valeurs effectivement renvoyées : les validateurs sont propres à la réponse concernée.
Revalider avec chaque validateur
curl --max-time 5 -sS -i \
-H 'If-None-Match: "doc-v1"' \
http://127.0.0.1:8000/page.txt
curl --max-time 5 -sS -i \
-H 'If-Modified-Since: Mon, 01 Jan 2024 12:00:00 GMT' \
http://127.0.0.1:8000/page.txt
Les deux requêtes doivent renvoyer 304 Not Modified sans le texte de la page. La réponse conserve ETag et Cache-Control ; la fonction de réponse de Python ajoute Date. Répéter la première requête avec W/"doc-v1" doit également renvoyer 304. L’exemple analyse les tags entre guillemets au lieu de les séparer aveuglément par des virgules, car une virgule peut figurer à l’intérieur d’un tag.
Vérifier que ETag a priorité
curl --max-time 5 -sS -i \
-H 'If-None-Match: "older-revision"' \
-H 'If-Modified-Since: Wed, 03 Jan 2024 12:00:00 GMT' \
http://127.0.0.1:8000/page.txt
Attendez-vous à recevoir 200 OK avec le document. Le tag ne correspond pas ; la date ultérieure ne doit donc pas déclencher une réponse 304. Cette requête volontairement contradictoire permet de détecter une implémentation qui combine à tort les deux vérifications.
Simuler la publication de la révision suivante
Dans la fenêtre du serveur, appuyez sur Control-C, puis démarrez la révision 2. Gardez la même URL de requête.
python3 server.py 2
Répétez les deux requêtes de revalidation précédentes avec les validateurs de la révision 1. Elles doivent désormais recevoir 200, la phrase mise à jour qui mentionne un triangle, ETag: "doc-v2" et la date de modification du 2 janvier. Répéter une requête avec If-None-Match: "doc-v2" doit renvoyer 304.
Ces résultats constituent une séquence de validation : téléchargement initial, validation sans changement, validateurs contradictoires, document modifié, puis validation sans changement de la nouvelle révision. L’exemple a été testé au moyen de 24 vérifications locales couvrant les deux révisions, notamment les tags faibles, les listes de tags, le joker, HEAD et les chemins inexistants.
Pourquoi les requêtes des navigateurs et de Google peuvent sembler différentes
Les navigateurs gèrent normalement les validateurs HTTP au moyen de leur cache. L’algorithme Fetch du WHATWG ajoute les validateurs enregistrés lors d’une revalidation et, après une réponse 304 réussie, met à jour les en-têtes de la réponse stockée et renvoie la réponse stockée. Par conséquent, JavaScript côté application peut recevoir une réponse 200 exploitable avec le corps du document, même si la revalidation réseau a reçu une réponse 304. Fetch Standard, récupération HTTP depuis le réseau ou le cache.
Pour diagnostiquer le comportement du serveur, commencez par les requêtes curl explicites ci-dessus. Fournir manuellement des en-têtes conditionnels en JavaScript dans le navigateur modifie le test : Fetch fait passer une requête en mode de cache par défaut contenant ces en-têtes en mode no-store. Laissez le navigateur gérer son cache lorsque vous testez une revalidation ordinaire dans le navigateur. Fetch Standard, mode de cache des requêtes.
L’infrastructure d’exploration de Google prend en charge les deux paires de validateurs et recommande les ETags, qui évitent les problèmes de formatage des dates. Sa documentation indique également que cette infrastructure ne prend pas en charge les autres directives de cache HTTP. Choisissez la politique de cache de votre navigateur indépendamment de vos attentes concernant le comportement de réexploration de Google. Présentation des robots d’exploration et des récupérateurs Google.
Google n’envoie pas d’en-têtes conditionnels à chaque tentative d’exploration. Sa documentation Search explique qu’un 304 permet de réutiliser du contenu exploré auparavant, tandis qu’un contenu plus récent peut recevoir un 200 accompagné du corps mis à jour. Continuez à prendre en charge les requêtes ordinaires sans condition ; une requête de robot sans validateurs ne prouve pas que votre implémentation a échoué. Résoudre les erreurs d’exploration de Google Search.
Avant d’appliquer ce modèle à un document publié, répétez la séquence de validation sur son URL réelle. Vérifiez que les révisions inchangées conservent leurs validateurs, que les modifications les mettent à jour et qu’une réponse 304 ne contient aucun corps de document. Le résultat recherché est une réutilisation correcte du document, tout en laissant le contenu modifié accessible.
Sources
- RFC 9110 : sémantique HTTP — IETF / RFC Editor.
- RFC 9111 : mise en cache HTTP — IETF / RFC Editor.
- Fetch Standard — WHATWG.
- Requêtes HTTP conditionnelles — MDN Web Docs.
- En-tête ETag — MDN Web Docs.
- En-tête Last-Modified — MDN Web Docs.
- http.server — serveurs HTTP — Python Software Foundation.
- email.utils : utilitaires divers — Python Software Foundation.
- curl — guide d’utilisation — projet curl.
- Présentation des robots d’exploration et des récupérateurs Google (agents utilisateur) — Google.
- Résoudre les erreurs d’exploration de Google Search — Google Search Central.