Caching en voorwaardelijke verzoeken · oefening
Een vingerafdruk voor een body
Een client die de boekenlijst van gisteren heeft bewaard, wil één ding weten: is de
Die waarde is een vingerafdruk van de body, en Python berekent er een met een
De module hashlib
hashlib maakt van willekeurige bytes een digest: een korte waarde van vaste grootte, berekend uit elke byte. hashlib.sha256(body) leest de bytes in body en geeft een hash-
.hexdigest()geeft eenstrvan 64 hexadecimale tekens terug, de cijfers0tot en met9en de lettersatot en metf. Zulke tekst kan zoals hij is in een header..digest()geeft dezelfde waarde terug als 32 ruwe bytes. Elke byte wordt in de andere vorm als twee hex-tekens geschreven, en daarom worden 32 bytes 64 tekens.
SHA-256 is de naam van het algoritme. Wat de grootte van de invoer ook is, een lege body of een hele bibliotheek, de digest heeft dezelfde grootte. Twee eigenschappen maken hem hier nuttig. Dezelfde bytes geven altijd dezelfde digest, op elke machine en in elke run van Python. En een wijziging van één byte geeft een digest die zichtbaar niets gemeen heeft met de oude. De digest identificeert alleen de bytes: niets in zijn 64 tekens laat iemand de body terughalen. Hier zijn twee bodies die één spatie verschillen:
import hashlib
for body in [b'{"books": []}', b'{"books": [ ]}']:
print(hashlib.sha256(body).hexdigest())
e72c4626ea5e221c238f01d50feff3d7007992fb3447610eaee3ee661310f6af
2d0190243f5b7e60660f91e4aace542f2e2a0de3c2dc923a01d05263c7333624
Een
hashlib.sha256 neemt bytes, nooit een str. De bodies van je server zijn al bytes, gebouwd door json_response met .encode("utf-8"), dus ze kunnen er direct in.
Wat drukt dit af?
import hashlib
print(len(hashlib.sha256(b"x").hexdigest()), len(hashlib.sha256(b"x").digest()))
Een tag staat tussen aanhalingstekens
In ETag. De waarde heeft een vaste vorm (RFC 9110, sectie 8.8.3): de tekens van de tag tussen dubbele aanhalingstekens, en de aanhalingstekens horen bij de waarde. Een server waarvan de digest e72c...f6af is, stuurt:
etag: "e72c4626ea5e221c238f01d50feff3d7007992fb3447610eaee3ee661310f6af"
Een tag die precies zo is geschreven, is een sterke tag: hij belooft dat twee antwoorden met dezelfde tag dezelfde bytes hebben. Een tag kan ook met W/ beginnen, zoals in W/"e72c...". Dat markeert een zwakke tag, die minder belooft: de twee antwoorden betekenen hetzelfde, hoewel hun bytes kunnen verschillen, bijvoorbeeld een pagina waarvan de enige wijziging een tijdstempel in een commentaar is. De tag van je server komt uit de exacte bytes, dus hij is sterk, en hij stuurt nooit W/. In les 4 leert je server W/ te lezen als een client het terugstuurt.
Welke is een sterke ETag, geschreven als headerwaarde?
Niet Pythons hash()
Python heeft ook een ingebouwde hash(), en die is hier verleidelijk. Die geeft een geheel getal terug voor een waarde, en gelijke waarden in één programma krijgen gelijke getallen. Maar voor str en bytes verandert het getal van de ene run van Python naar de volgende. Voer python -c 'print(hash(b"abc"))' twee keer uit in je PYTHONHASHSEED op een vaste waarde, wat het getal vastzet. Dat is bewust: het maakt het moeilijker voor wie dan ook om invoer te bouwen die de
Een tag moet langer meegaan dan één run. Een client kan een tag uren bewaren terwijl de server herstart, en een oude tag moet nog steeds dezelfde bytes aanduiden als hij terugkomt. Een SHA-256-digest doet dat, omdat die van niets anders afhangt dan de bytes.
Twee GET /books-antwoorden met een uur ertussen hebben byte voor byte identieke bodies. Hoe zit het met hun tags?
Jouw opdracht
Schrijf in reading_server.py make_etag(body). Die neemt de bytes van de body en geeft de sterke tag terug als str: een dubbel aanhalingsteken, de SHA-256-hexdigest van de body, en een dubbel aanhalingsteken. reading_server.py importeert hashlib al. Eén regel is genoeg.
Nog niets roept make_etag aan; les 3 zet de tag op elk antwoord. Je kunt hem nu al uitproberen. Start Python in de terminal, in de projectmap:
python
Importeer daarna je module en roep de
>>> import reading_server
>>> reading_server.make_etag(b"Kindred")
'"213c649df845274f0590aae4a97f84eb72a8935662c6fb10c7a63bda2e69549f"'
>>> reading_server.make_etag(b"Kindred!")
'"fc29d1d0143aae9c48c2ab38f15c87e99a4345712343322b8d0d8924a63ce456"'
De buitenste enkele aanhalingstekens zijn hoe Python een str toont; de dubbele aanhalingstekens erbinnen horen bij de tag. Eén toegevoegde byte geeft een totaal andere tag, en elke tag is 66 tekens met zijn aanhalingstekens. Typ exit() om Python te verlaten, en klik daarna op Check my work.
Dezelfde gereedschappen komen terug in een latere cursus in deze reeks, WebSockets with Python, die gepland is. De openingshandshake daarvan hasht een sleutel met hashlib en stuurt de digest()-bytes in
Opdracht
Schrijf make_etag(body) in reading_server.py.
bodyis de body als bytes.Geef een
strterug: een dubbel aanhalingsteken,hashlib.sha256(body).hexdigest(), en een dubbel aanhalingsteken. De aanhalingstekens horen bij devan de tag. De tag is sterk: geen
W/ervoor.
Probeer het uit in de python, doe daarna import reading_server en roep reading_server.make_etag(b"Kindred") aan.
Klik daarna op Check my work. Die roept make_etag aan met bodies die je nog niet hebt gezien: een lege body, een str is die met een dubbel aanhalingsteken begint en eindigt en de SHA-256-hexdigest van precies die bytes bevat (hex-cijfers in hoofdletters worden geaccepteerd), dat gelijke bytes gelijke tags geven, en dat het veranderen van één byte of het toevoegen van één spatie de tag verandert. Niets uit je terminal wordt beoordeeld.