Caching und bedingte Anfragen · Übung
Versieh jede Repräsentation mit einem Tag
make_etag kann jeden Body benennen, und noch verwendet es keine Antwort. Starte das Szenario etag des Hilfsprogramms, um zu sehen, was ein Client heute bekommt:
python pl_http.py etag
Es sendet GET /books/1, dann ein PUT /books/1 mit dem Beispiel-Token des Schreibers und einem neuen Titel, dann noch einmal GET /books/1, und am Ende zeigt es den Header etag der ersten und der letzten Antwort:
--- the two etag lines ---
before the PUT: None
after the PUT: None
Keine der Antworten trägt ein Tag, und keine sagt, wie lange sie wiederverwendet werden darf. Diese Lektion fügt beides jeder Antwort hinzu, die es haben sollte: ein etag und cache-control: max-age=60.
Welche Antworten ein Tag bekommen
Ein Tag benennt einen Body, den ein Client behalten und nach dem er später fragen darf. Das ist ein erfolgreicher Lesezugriff: eine 200 auf ein GET oder ein HEAD auf /books oder auf /books/{id}. Nichts anderes bekommt eins. Einen Fehler-Body wie {"error": "no book has that id"} behält ein Client nicht. Die Antwort auf einen Schreibzugriff meldet, was gerade passiert ist, und erst der nächste Lesezugriff ist es /login und /me bleiben, wie sie sind. SERVICE_CONTRACT.md hat einen neuen Abschnitt, „Caching“, der dasselbe in einer Tabelle sagt.
Jede /books, /books?limit=1 und /books/2 sind drei verschiedene Repräsentationen mit drei verschiedenen Tags, obwohl sie Bücher gemeinsam haben. Ein Client, der nach /books?limit=1 fragt, sendet das Tag, das er von dieser URL bekommen hat.
add_validators(response) fügt einem Antwort-
response["headers"]["etag"]aufmake_etag(response["body"])setzen;response["headers"]["cache-control"]aufmax-age=60setzen, gebaut aus der KonstanteMAX_AGEoben inreading_server.py, zum Beispiel mit demf"max-age={MAX_AGE}".
handle ruft sie an genau zwei Stellen auf, den zwei Zweigen, die eine 200 für einen Lesezugriff bauen. Der Listenzweig wird zu response = add_validators(json_response(200, {"books": books})), und der Zweig für ein Buch tut dasselbe mit json_response(200, state["books"][book_id]).
HEAD bekommt das Tag von GET
Kapitel 4 hat deinem Server beigebracht, HEAD mit genau den Headern zu beantworten, die GET senden würde, und ohne Body, und die letzten Zeilen von handle tun das, indem sie den Body leeren, wenn die HEAD ist. Die Reihenfolge dieser beiden Schritte zählt jetzt. Würde das Tag berechnet, nachdem der Body geleert wurde, trüge eine HEAD-Antwort das Tag eines leeren Bodys, "e3b0c442...b855", einen Namen für Bytes, die GET nie sendet. Ein Client, der eine Ressource mit HEAD prüft und das Tag mit seinem vergleicht, sähe immer einen Unterschied.
add_validators innerhalb der Lesezweige aufzurufen, setzt es vor die HEAD-Regel, weil die Regel zuletzt läuft, nach jedem Zweig. GET und HEAD teilen sich diese Zweige, also bekommen beide das Tag des vollen Bodys, und erst dann verliert HEAD den Body.
Welches etag trägt HEAD /books?
Ein Schreibzugriff ändert das Tag von selbst
Sieh dir an, woraus das Tag berechnet wird: aus dem Body, in dem Moment, in dem ein Lesezugriff ihn baut. Nichts speichert das Tag, also muss auch nichts daran denken, es zu ändern. Ein PUT, das Buch 2 umbenennt, ändert die Bytes von GET /books/2, also bekommt der nächste Lesezugriff darauf ein neues Tag. Dasselbe PUT ändert die /books ein neues Tag, ebenso jede ?limit=-Seite, die Buch 2 enthält. Die Bytes von Buch 1 haben sich nicht geändert, und sein Tag auch nicht.
Ein Tag, das auf andere Weise gebaut wird, muss von Hand im Gleichschritt gehalten werden, und früher oder später ist es das nicht. Ein Tag, das aus der ID des Buchs gemacht wird, bleibt nach einem PUT gleich, und ein Client würde weiter den alten Titel anzeigen, während der Server „unverändert“ sagt.
Welche Tags ändern sich nach PUT /books/2?
Jeden Body bei jedem Lesezugriff zu hashen, kostet etwas Arbeit. Ein viel genutzter echter Dienst berechnet ein Tag oft einmal, wenn sich die Daten ändern, und speichert es neben den Daten, was schneller ist und eine Sache mehr, die im Gleichschritt gehalten werden muss. Ein Tag, das bei jedem Lesezugriff berechnet wird, ist nie veraltet, und dieser Server hält es so.
Deine Aufgabe
Schreib in reading_server.py add_validators(response) und ruf es wie oben in den zwei Lesezweigen von handle auf. Fehler, Schreibzugriffe und jede andere Route bleiben unberührt. Starte dann das Szenario noch einmal:
python pl_http.py etag
Unter jeder 200 für einen Lesezugriff gibt das Hilfsprogramm jetzt die zwei Header aus, und die Tags der beiden Lesezugriffe unterscheiden sich, weil das PUT dazwischen die Bytes von Buch 1 geändert hat:
--- the two etag lines ---
before the PUT: '"ecfe550f34c7f26be888feb41dbb0bdc5dfff7e77c57a8e049eb0c721b08049b"'
after the PUT: '"929daebfa038d3a896d78c3e8b93fdac7fece4faed0d92572e8a88a4733bb5f4"'
Die Antwort auf das PUT selbst trägt keinen der beiden Header. Drück dann Check my work.
Jeder Lesezugriff hat jetzt ein Tag, und Clients laden trotzdem jeden Body vollständig herunter, weil keiner von ihnen ein Tag zurücksendet. Die nächste Lektion bringt deinem Server bei, einem Client zu antworten, der das tut: Eine Anfrage, die sagt „ich habe dieses Tag“, bekommt eine 304 ohne Body, wenn das Tag noch aktuell ist.
Aufgabe
Schreib add_validators(response) in reading_server.py und ruf es in handle auf.
add_validatorssetztresponse["headers"]["etag"]aufmake_etag(response["body"])undresponse["headers"]["cache-control"]aufmax-age=60(ausMAX_AGE) und gibt dannresponsezurück.Ruf es in
handleauf der 200 im Zweig auf, der Bücher auflistet, und auf der 200 im Zweig, der ein Buch zurückgibt:response = add_validators(json_response(200, ...)). Diese Zweige laufen vor derHEAD-Regel am Ende vonhandle, also trägt eineHEAD-Antwort das Tag des Bodys, denGETsendet.Keine andere Antwort bekommt einen der beiden Header: Fehler,
, die Antworten auf POST,PUTundDELETE,/login,/logoutund/mebleiben, wie sie sind.
Führ python pl_http.py etag im PUT zu sehen.
Drück dann Check my work. Es ruft add_validators direkt auf und dann handle mit einem Zustand aus Büchern, IDs und Tokens, die du noch nicht gesehen hast. Es prüft, dass GET auf /books, /books?limit=1 und /books/{id} ein etag trägt, das dem SHA-256-Digest des gesendeten Bodys in Anführungszeichen entspricht, und ein cache-control, das max-age=60 enthält; dass HEAD dasselbe Tag, dasselbe cache-control und content-length trägt und keinen Body; dass sich nach einem PUT die Tags dieses Buchs und von /books ändern, während sich das Tag eines anderen Buchs nicht ändert; dass ein POST und ein DELETE das Tag von /books ändern; und dass 404, 405, 400, 308, 401, 403, 201, 204, die PUT-Antwort und /me kein etag tragen. Nichts aus deinem Terminal wird bewertet.