Caching en voorwaardelijke verzoeken · eindproject
Eindproject: Project: een leeslijst die de cache kent
Beide helften van max-age=60, en beantwoordt een tag die hij herkent met een 304 zonder body. Je client hergebruikt een verse kopie zonder te vragen, valideert een verlopen kopie opnieuw met If-None-Match, en start zijn timer opnieuw vanaf de 304. Dit project dicht het enige gat dat overblijft, en dat gaat over privacy in plaats van snelheid, en bewijst daarna dat de twee helften het met elkaar eens zijn.
Eisen
SERVICE_CONTRACT.md zegt het al allemaal, in de sectie “Caching” en in “The client in wire.py”:
Elke 200 op
GETofHEADop/books(met of zonder?limit=) en/books/{id}draagtetagencache-control: max-age=60, opHEADhetzelfde als opGET.Een overeenkomende
if-none-matchkrijgt 304 met dezelfdeetagencache-control, geen body, en geencontent-length.Elk antwoord voor
/login,/logouten/medraagtcache-control: no-store.cached_gethergebruikt een opgeslagen kopie zolangnowkleiner is dan het verlooptijdstip, valideert een verlopen kopie opnieuw, start de timer opnieuw vanaf een 304, en vervangt de entry bij een 200.Een antwoord dat als
no-storeis gemarkeerd, wordt nooit opgeslagen. Een antwoord dat alsno-cacheis gemarkeerd, wordt opgeslagen en elke keer opnieuw gevalideerd.
Nieuw zijn no-store op drie routes en de laatste regel; de rest is les 3 tot en met 5, opnieuw gecontroleerd.
Niet alles hoort bewaard te worden
GET /me antwoordt {"user": "ana"} of {"user": null}: wie er op dit moment op deze client is ingelogd. Dat verandert bij elke login en logout, en het hoort bij één persoon, dus er hoort nergens een kopie van te staan. Vandaag bewaart je client niets van /me alleen omdat het antwoord geen tag heeft: een bijwerking, geen beslissing. Een latere wijziging die elke 200 een tag gaf, zou je client stilletjes een login laten onthouden nadat de gebruiker was uitgelogd. Bij een gedeelde cache zou het erger zijn: de volgende gebruiker zou de naam van de vorige gebruiker te zien kunnen krijgen.
Daarom zegt de server het ronduit, met het directief uit les 1 dat “bewaar dit niet” betekent: cache-control: no-store, op elk antwoord voor /login, /logout en /me, de weigeringen inbegrepen. En de client gehoorzaamt eraan, wat het antwoord verder ook draagt.
De client leert tegelijk no-cache. Een server stuurt dat voor een antwoord dat mag worden bewaard maar vóór elk gebruik moet worden gecontroleerd, bijvoorbeeld een pagina die vaak verandert en weinig kost om opnieuw te valideren. In cache_rules is dat max_age 0: het verlooptijdstip van de entry is het moment waarop hij werd opgeslagen, dus de volgende aanroep vindt hem verlopen en vraagt met de tag. no-cache gaat boven elke max-age in hetzelfde antwoord.
Mijlpalen
Eerst cache_rules, fase 2. Een directief no-store zet "store" op False. Een directief no-cache zet "max_age" op 0, waar het ook tussen de directieven staat, vóór of na een max-age. Namen van directieven negeren nog steeds hoofdletters. Zorg er daarna voor dat cached_get een antwoord alleen opslaat als cache_rules "store": True zegt, naast het feit dat het een 200 met één etag is.
Dan de server. Zet in handle, nadat de route haar antwoord heeft gemaakt, cache-control op no-store op het antwoord als het pad /login, /logout of /me is. Het scenario who uit hoofdstuk 7 laat de nieuwe regel zien: voer python pl_http.py who uit, en onder elk antwoord voor /login, /me en /logout drukt het hulpprogramma cache-control: no-store af.
Kijk dan naar het keerpunt. Voer uit:
python pl_http.py cache
Dit is het hoofdstuk in drie aanroepen: op 0 brengt een GET de body, op 30 wordt er helemaal niets verstuurd, en op 61 brengt een GET met de tag een 304 zonder body terug, dus requests toont 1, 0 en 1. De laatste twee aanroepen zijn /me, en elk stuurt een verzoek:
--- your cached_get() at now 0 for /me ---
source: 'network'
Optioneel: test de cache zelf. fakes.py heeft scripted_response, en scripted_connections uit het project van hoofdstuk 6. Voeg scripted_connections toe aan de regel from fakes import van tests/test_wire.py, en een test zoals:
def test_a_fresh_copy_needs_no_request():
calls = []
reply = scripted_response(200, "OK", [["etag", '"v1"'], ["cache-control", "max-age=60"]], b"[]")
connections = scripted_connections([reply], calls)
cache = {}
wire.cached_get("http://books.test/books", cache, now=0, make_connection=connections)
record = wire.cached_get("http://books.test/books", cache, now=59, make_connection=connections)
assert record["source"] == "cache"
assert len(calls) == 1
De tweede aanroep maakt geen verbinding, dus één vooraf vastgelegd antwoord is genoeg. Een test van no-store volgt hetzelfde patroon: twee aanroepen, twee verbindingen. Voer daarna elke test uit vanuit de projectmap:
python -m pytest -q
Check my work voert je tests één keer uit, dus ze moeten allemaal slagen, en geen ervan mag een fakes.py.
Wat er bewezen wordt, en hoe
Check my work controleert elke helft op zichzelf met cached_get met je eigen reading_server.handle in één proces, zoals het project van hoofdstuk 6 deed. Elke verbinding die je client opent, bouwt de verzoek-http_adapter zou bouwen, voert je read_body en handle uit, en maakt van je antwoord-dict weer een antwoord. Hij bewaart ook elke antwoord-dict, zodat een 304 die een body droeg, wordt betrapt. Op 0, 30 en 61 moet je server twee verzoeken zien, het tweede met je tag en beantwoord met een 304 zonder body, en alle drie de resultaten moeten dezelfde body bevatten. Een PUT via je handle tussen twee aanroepen moet ervoor zorgen dat de volgende verlopen GET de nieuwe body brengt, en /me moet elke keer worden gevraagd en nooit worden opgeslagen. De run cache laat dezelfde overeenstemming zien over een echte verbinding; die is om naar te kijken, niet om te beoordelen.
Klik daarna op Check my work.
Elke uitwisseling tot nu toe opende een verbinding, stuurde één verzoek, las één antwoord en sloot de verbinding. Dat is de manier van HTTP/1.0, en je server spreekt die nog steeds. Hoofdstuk 9 zet de server om naar HTTP/1.1, waar één verbinding veel verzoeken draagt, en leert je client te bepalen waar elk antwoord eindigt.
Opdracht
Maak de leeslijst die de cache kent af: no-store en no-cache in wire.py, en no-store op drie routes in reading_server.py.
cache_rules(values): een directiefno-storezet"store"opFalse; een directiefno-cachezet"max_age"op 0, welkemax-agehet antwoord ook draagt. Namen van directieven negeren hoofdletters, en elke regel telt mee.cached_getslaat een antwoord alleen op als het een 200 is met precies éénetagencache_rules"store": Truezegt. Alles uit les 5 blijft.In
handledraagt elk antwoord voor/login,/logouten/mecache-control: no-store.Je tests in
tests/moeten slagen metpython -m pytest -q, en geen ervan magsocketimporteren.
Voer python pl_http.py cache en python pl_http.py who uit in de
Klik daarna op Check my work. Die controleert de server met een state die je nog niet hebt gezien: tags op GET en HEAD, een tag die na een PUT verandert, een 304 zonder body die etag en cache-control herhaalt, en no-store op POST /login, GET en HEAD /me, en POST /logout. Hij roept cache_rules aan met no-store, no-cache, max-age=60, max-age=60, no-store, private, max-age=5 en andere cached_get met vooraf vastgelegde antwoorden voor versheid, de grens op precies max-age, de timer die een 304 opnieuw start, no-store en no-cache. Daarna verbindt hij je cached_get met je eigen handle in één proces: op 0, 30 en 61 bereiken twee verzoeken je server en wordt het tweede beantwoord met 304 zonder body; een PUT tussen aanroepen brengt de nieuwe body; /me wordt elke keer gevraagd en nooit opgeslagen. Als laatste voert hij je tests één keer uit, en stopt hij een run die langer dan 3 seconden duurt. Niets uit je terminal wordt beoordeeld.