0%

Caching en voorwaardelijke verzoeken · eindproject

Eindproject: Project: een leeslijst die de cache kent

Beide helften van -caching zijn op hun plaats. Je server zet een tag op elke leesactie, zegt 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 GET of HEAD op /books (met of zonder ?limit=) en /books/{id} draagt etag en cache-control: max-age=60, op HEAD hetzelfde als op GET.

  • Een overeenkomende if-none-match krijgt 304 met dezelfde etag en cache-control, geen body, en geen content-length.

  • Elk antwoord voor /login, /logout en /me draagt cache-control: no-store.

  • cached_get hergebruikt een opgeslagen kopie zolang now kleiner 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-store is gemarkeerd, wordt nooit opgeslagen. Een antwoord dat als no-cache is 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 nodig hebben: gebruik de vervangers in fakes.py.

Wat er bewezen wordt, en hoe

Check my work controleert elke helft op zichzelf met die je nog niet hebt gezien, en verbindt daarna je 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- die 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 directief no-store zet "store" op False; een directief no-cache zet "max_age" op 0, welke max-age het antwoord ook draagt. Namen van directieven negeren hoofdletters, en elke regel telt mee.

  • cached_get slaat een antwoord alleen op als het een 200 is met precies één etag en cache_rules "store": True zegt. Alles uit les 5 blijft.

  • In handle draagt elk antwoord voor /login, /logout en /me cache-control: no-store.

  • Je tests in tests/ moeten slagen met python -m pytest -q, en geen ervan mag socket importeren.

Voer python pl_http.py cache en python pl_http.py who uit in de om beide helften op een echte verbinding aan het werk te zien.

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 , en 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.