Implementa GET condizionali inviando validator stabili con ogni documento, verificando i validator salvati dal client e restituendo il documento corrente oppure un 304 Not Modified senza corpo. Questo tutorial per Mac illustra il processo con una pagina di testo sintetica.
L’obiettivo è fare in modo che le richieste ripetute di un documento pubblico ne trasferiscano il contenuto solo quando necessario. Avvierai un piccolo server Python locale, lo testerai da Terminal e simulerai un aggiornamento del documento. L’esempio resta in locale; la pubblicazione è un’attività separata.
Come deve scegliere il server tra 200 e 304?
Una richiesta iniziale riceve 200 OK, il corpo del documento e i relativi validator. Una successiva richiesta condizionale invia di nuovo i validator salvati. Se il documento non è cambiato, il server restituisce 304 Not Modified, consentendo al client di riutilizzare il corpo memorizzato. Se il contenuto è cambiato, viene restituita una nuova risposta 200. Richieste HTTP condizionali.
Per un documento esistente che altrimenti restituirebbe 200, applica la seguente sequenza decisionale. If-None-Match ha la precedenza su If-Modified-Since; non sono due verifiche indipendenti. RFC 9110, campi delle richieste condizionali.
| Condizione della richiesta | Risposta |
|---|---|
| Nessuna intestazione condizionale | 200 con il documento |
If-None-Match corrisponde al tag corrente |
304, senza corpo |
If-None-Match è presente ma non corrisponde |
200, anche se una data fornita indica che non ci sono modifiche |
È fornito solo un If-Modified-Since valido e il documento non è più recente |
304, senza corpo |
È fornito solo un If-Modified-Since valido e il documento è più recente |
200 con il documento |
Valuta queste condizioni dopo aver selezionato il documento e stabilito la normale risposta. Un documento mancante deve comunque generare la normale risposta di risorsa inesistente. Una risposta 304 non può contenere contenuti né trailer. Deve includere i metadati di cache applicabili, compresi i campi Date, ETag, Vary, Cache-Control, Expires e Content-Location che accompagnerebbero la risposta 200. RFC 9110, valutazione delle precondizioni e requisiti del 304.
Scegli validator che descrivano il documento pubblicato
Un ETag è un identificatore di versione tra virgolette, ad esempio "doc-v1". Il metodo di generazione è definito dall’implementazione: possono funzionare un identificatore di revisione o un hash del contenuto. Mantienilo stabile per la stessa rappresentazione e cambialo quando la rappresentazione cambia. L’esempio usa due revisioni esplicite con contenuti fissi. Intestazione ETag.
Un tag forte identifica una rappresentazione specifica; un tag debole ha il prefisso W/ e può descrivere contenuti semanticamente equivalenti i cui byte differiscono. I confronti condizionali GET con If-None-Match usano il confronto debole, quindi W/"doc-v1" corrisponde a "doc-v1". Le richieste possono contenere anche un elenco di tag oppure il carattere jolly autonomo *. RFC 9110, If-None-Match.
Last-Modified indica quando è cambiato il documento. Usa un orario di modifica determinato in modo coerente e che tenga conto dei componenti rilevanti del documento, anziché l’orario di ogni richiesta. Non deve essere successivo al Date della risposta. RFC 9110, generazione di Last-Modified.
L’esempio assegna orari di pubblicazione volutamente illustrativi alle due revisioni sintetiche: mezzogiorno GMT del 1º e del 2 gennaio 2024. Queste date sono dati di test. In un flusso di pubblicazione reale, usa metadati di pubblicazione gestiti. Last-Modified è meno preciso di un ETag nel rilevare le differenze tra contenuti e costituisce un utile ripiego. Intestazione Last-Modified.
Scegli separatamente una politica di cache
I validator indicano se una copia memorizzata corrisponde ancora. La politica di freschezza stabilisce quando la cache deve chiedere. Questo tutorial invia Cache-Control: no-cache, che consente la memorizzazione ma richiede una convalida riuscita prima del riutilizzo. In questo modo è facile osservare il comportamento delle richieste ripetute. no-store vieta la memorizzazione, mentre max-age consente il riutilizzo finché una risposta resta fresca. RFC 9111, direttive di risposta della cache.
Per un documento pubblico che può tollerare un breve ritardo prima che un aggiornamento diventi visibile, può essere adatta una politica come max-age=60. È una scelta editoriale illustrativa: scegli l’intervallo in base ai requisiti di aggiornamento del documento. Una risposta memorizzata ancora fresca può essere riutilizzata senza contattare il server, quindi ogni visita ripetuta non deve necessariamente generare un 304. RFC 9111, freschezza.
Esegui un esempio locale completo sul tuo Mac
Apri Terminal e verifica i prerequisiti con i seguenti comandi. Usa Python 3.9 o successivo, perché il programma usa str.removeprefix. Se python3 non è disponibile, installa Python 3 prima di continuare. Non servono pacchetti Python di terze parti.
python3 --version
curl --version
In una nuova cartella di lavoro, salva il seguente programma completo come server.py usando un editor di codice in testo semplice. Serve solo /page.txt, implementa GET e HEAD e seleziona una revisione del documento dall’argomento della riga di comando. BaseHTTPRequestHandler di Python inoltra le richieste ai metodi corrispondenti e fornisce funzioni ausiliarie per scrivere le risposte. Documentazione del server 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()
Questa è una dimostrazione locale dei due validator indicati, non uno stack completo per la gestione HTTP. La conversione della data usa il permissivo parser di date delle email di Python; un’implementazione distribuita dovrebbe usare una convalida delle date specifica per HTTP e la gestione delle richieste condizionali. La documentazione Python descrive gli input accettati dal parser e le relative eccezioni. Utility per le date delle email in Python.
In Terminal, spostati nella cartella che contiene server.py e avvia la revisione 1. Lascia aperta quella finestra. Se la porta 8000 è già occupata, scegline un’altra disponibile nel programma e usa la stessa porta in tutti gli URL di test.
python3 server.py 1
Verifica contenuti invariati, contenuti modificati e precedenza
Apri un’altra finestra di Terminal. Le richieste seguenti mostrano le intestazioni di risposta e l’eventuale corpo restituito. L’opzione -i include le intestazioni, -H specifica un’intestazione della richiesta e --max-time limita la durata di ogni trasferimento. Manuale di curl.
Ottieni la risposta iniziale
curl --max-time 5 -sS -i http://127.0.0.1:8000/page.txt
Aspettati 200 OK, il testo sintetico della pagina, ETag: "doc-v1" e Last-Modified: Mon, 01 Jan 2024 12:00:00 GMT. Quando applichi questa procedura a un altro documento, annota i valori effettivamente restituiti: i validator appartengono a quella specifica risposta.
Riconvalida con ciascun validator
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
Entrambe le richieste dovrebbero restituire 304 Not Modified senza il testo della pagina. La risposta conserva ETag e Cache-Control; l’helper di risposta di Python aggiunge Date. Anche ripetendo la prima richiesta con W/"doc-v1" dovrebbe essere restituito 304. L’esempio analizza i tag tra virgolette invece di dividerli alla cieca sulle virgole, perché un tag può contenere una virgola.
Verifica che ETag abbia la precedenza
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
Aspettati 200 OK con il documento. Il tag non corrisponde, quindi la data successiva non deve attivare un 304. Questa richiesta volutamente conflittuale rileva un’implementazione che combina erroneamente le due verifiche.
Simula la pubblicazione della revisione successiva
Nella finestra del server, premi Control-C, poi avvia la revisione 2. Lascia invariato l’URL della richiesta.
python3 server.py 2
Ripeti le due richieste di riconvalida precedenti contenenti i validator della revisione 1. Entrambe dovrebbero ricevere 200, la frase aggiornata che menziona un triangolo, ETag: "doc-v2" e la data di modifica del 2 gennaio. Ripetendo una richiesta con If-None-Match: "doc-v2" dovrebbe essere restituito 304.
Questi risultati costituiscono una sequenza di accettazione: download iniziale, convalida senza modifiche, validator in conflitto, documento modificato e quindi convalida senza modifiche della nuova revisione. L’esempio è stato verificato con 24 controlli locali su entrambe le revisioni, inclusi tag deboli, elenchi di tag, corrispondenza con il carattere jolly, HEAD e percorsi inesistenti.
Perché le richieste dei browser e di Google possono sembrare diverse
I browser gestiscono normalmente i validator HTTP tramite la propria cache. L’algoritmo Fetch del WHATWG aggiunge i validator salvati durante la riconvalida e, dopo un 304 riuscito, aggiorna le intestazioni della risposta memorizzata e restituisce la risposta memorizzata. Di conseguenza, JavaScript dell’applicazione può ricevere una risposta 200 utilizzabile e il corpo del documento anche se la riconvalida di rete ha ricevuto 304. Fetch Standard, recupero tramite rete HTTP o cache.
Per diagnosticare il comportamento del server, inizia con le richieste curl esplicite qui sopra. Fornire manualmente intestazioni condizionali nel JavaScript del browser cambia il test: Fetch imposta no-store per una richiesta con modalità cache predefinita che contiene tali intestazioni. Lascia che sia il browser a gestire la propria cache quando verifichi la normale riconvalida. Fetch Standard, modalità cache della richiesta.
L’infrastruttura di scansione di Google supporta entrambe le coppie di validator e consiglia gli ETag perché evitano problemi di formattazione delle date. La documentazione afferma anche che tale infrastruttura non supporta le altre direttive di cache HTTP. Scegli la politica di cache del browser separatamente dalle aspettative sul comportamento di nuova scansione di Google. Panoramica dei crawler e dei fetcher di Google.
Google non invia intestazioni condizionali a ogni tentativo di scansione. La documentazione di Ricerca Google spiega che un 304 consente di riutilizzare contenuti acquisiti in precedenza, mentre i contenuti più recenti possono ricevere un 200 con il corpo aggiornato. Continua a supportare le normali richieste incondizionate: una richiesta di un crawler senza validator non dimostra che l’implementazione non funzioni. Risolvere gli errori di scansione di Ricerca Google.
Prima di applicare questo schema a un documento pubblicato, ripeti la sequenza di accettazione sul relativo URL di servizio. Verifica che le revisioni invariate mantengano i propri validator, che le modifiche li aggiornino e che una risposta 304 non contenga il corpo del documento. Il risultato utile è il riutilizzo corretto del documento, mantenendo accessibile il contenuto modificato.
Fonti
- RFC 9110: semantica HTTP — IETF / RFC Editor.
- RFC 9111: caching HTTP — IETF / RFC Editor.
- Fetch Standard — WHATWG.
- Richieste HTTP condizionali — MDN Web Docs.
- Intestazione ETag — MDN Web Docs.
- Intestazione Last-Modified — MDN Web Docs.
- http.server — Server HTTP — Python Software Foundation.
- email.utils: utility varie — Python Software Foundation.
- curl — come si usa — progetto curl.
- Panoramica dei crawler e dei fetcher di Google (user agent) — Google.
- Risolvere gli errori di scansione di Ricerca Google — Google Search Central.