Caching und bedingte Anfragen · Übung
Ein Fingerabdruck für einen Body
Ein Client, der die Bücherliste von gestern aufbewahrt hat, will eine Sache wissen: Ist die
Dieser Wert ist ein Fingerabdruck des Bodys, und Python berechnet einen mit einem
Das Modul hashlib
hashlib macht aus beliebigen Bytes einen Digest: einen kurzen Wert fester Größe, berechnet aus jedem Byte. hashlib.sha256(body) liest die Bytes in body und gibt ein Hash-
.hexdigest()gibt einenstraus 64 hexadezimalen Zeichen zurück, den Ziffern0bis9und den Buchstabenabisf. Solcher Text kann so, wie er ist, in einen Header..digest()gibt denselben Wert als 32 rohe Bytes zurück. Jedes Byte wird in der anderen Form als zwei Hex-Zeichen geschrieben, und deshalb werden aus 32 Bytes 64 Zeichen.
SHA-256 ist der Name des Algorithmus. Egal wie groß die Eingabe ist, ein leerer Body oder eine ganze Bibliothek, der Digest hat dieselbe Größe. Zwei Eigenschaften machen ihn hier nützlich. Dieselben Bytes ergeben immer denselben Digest, auf jedem Rechner und in jedem Python-Lauf. Und eine Änderung eines einzigen Bytes ergibt einen Digest, der mit dem alten sichtbar nichts gemeinsam hat. Der Digest identifiziert die Bytes nur: Nichts in seinen 64 Zeichen lässt jemanden den Body wiederherstellen. Hier sind zwei Bodys, die sich um ein Leerzeichen unterscheiden:
import hashlib
for body in [b'{"books": []}', b'{"books": [ ]}']:
print(hashlib.sha256(body).hexdigest())
e72c4626ea5e221c238f01d50feff3d7007992fb3447610eaee3ee661310f6af
2d0190243f5b7e60660f91e4aace542f2e2a0de3c2dc923a01d05263c7333624
Ein
hashlib.sha256 nimmt Bytes, nie einen str. Die Bodys deines Servers sind bereits Bytes, gebaut von json_response mit .encode("utf-8"), also können sie direkt hinein.
Was gibt das aus?
import hashlib
print(len(hashlib.sha256(b"x").hexdigest()), len(hashlib.sha256(b"x").digest()))
Ein Tag steht in Anführungszeichen
In ETag gesendet. Sein Wert hat eine feste Form (RFC 9110, Abschnitt 8.8.3): die Zeichen des Tags zwischen doppelten Anführungszeichen, und die Anführungszeichen gehören zum Wert. Ein Server, dessen Digest e72c...f6af ist, sendet:
etag: "e72c4626ea5e221c238f01d50feff3d7007992fb3447610eaee3ee661310f6af"
Ein genau so geschriebenes Tag ist ein starkes Tag: Es verspricht, dass zwei Antworten mit demselben Tag dieselben Bytes haben. Ein Tag kann auch mit W/ beginnen, wie in W/"e72c...". Das kennzeichnet ein schwaches Tag, das weniger verspricht: Die beiden Antworten bedeuten dasselbe, auch wenn sich ihre Bytes unterscheiden können, zum Beispiel bei einer Seite, deren einzige Änderung ein Zeitstempel in einem Kommentar ist. Das Tag deines Servers kommt aus den genauen Bytes, also ist es stark, und er sendet nie W/. In Lektion 4 lernt dein Server, W/ zu lesen, wenn ein Client es zurücksendet.
Welches ist ein starkes ETag, als Headerwert geschrieben?
Nicht Pythons hash()
Python hat auch ein eingebautes hash(), und das ist hier verlockend. Es gibt für einen Wert eine ganze Zahl zurück, und gleiche Werte in einem Programm bekommen gleiche Zahlen. Aber bei str und bytes ändert sich die Zahl von einem Python-Lauf zum nächsten. Führ python -c 'print(hash(b"abc"))' zweimal in deinem PYTHONHASHSEED auf einen festen Wert gestartet, was die Zahl festlegt. Das ist Absicht: Es macht es schwerer, Eingaben zu bauen, die Pythons
Ein Tag muss einen Lauf überdauern. Ein Client kann ein Tag stundenlang behalten, während der Server neu startet, und ein altes Tag muss, wenn es zurückkommt, immer noch dieselben Bytes benennen. Ein SHA-256-Digest tut das, weil er von nichts als den Bytes abhängt.
Zwei Antworten auf GET /books im Abstand von einer Stunde haben Byte für Byte identische Bodys. Was ist mit ihren Tags?
Deine Aufgabe
Schreib in reading_server.py make_etag(body). Es nimmt die Bytes des Bodys und gibt das starke Tag als str zurück: ein doppeltes Anführungszeichen, den SHA-256-Hex-Digest des Bodys und ein doppeltes Anführungszeichen. reading_server.py importiert hashlib bereits. Eine Zeile reicht.
Noch ruft nichts make_etag auf; Lektion 3 setzt sein Tag auf jede Antwort. Du kannst es jetzt ausprobieren. Starte Python im Terminal, im Projektordner:
python
Importiere dann dein Modul und ruf die
>>> import reading_server
>>> reading_server.make_etag(b"Kindred")
'"213c649df845274f0590aae4a97f84eb72a8935662c6fb10c7a63bda2e69549f"'
>>> reading_server.make_etag(b"Kindred!")
'"fc29d1d0143aae9c48c2ab38f15c87e99a4345712343322b8d0d8924a63ce456"'
Die äußeren einfachen Anführungszeichen sind die Art, wie Python einen str anzeigt; die doppelten Anführungszeichen darin sind Teil des Tags. Ein hinzugefügtes Byte ergibt ein völlig anderes Tag, und jedes Tag ist mit seinen Anführungszeichen 66 Zeichen lang. Gib exit() ein, um Python zu verlassen, und drück dann Check my work.
Dieselben Werkzeuge kommen in einem späteren Kurs dieser Reihe wieder, WebSockets with Python, der geplant ist. Sein eröffnender Handshake hasht einen Schlüssel mit hashlib und sendet die digest()-Bytes in
Aufgabe
Schreib make_etag(body) in reading_server.py.
bodyist der Body als Bytes.Gib einen
strzurück: ein doppeltes Anführungszeichen,hashlib.sha256(body).hexdigest()und ein doppeltes Anführungszeichen. Die Anführungszeichen sind Teil des Tag-. Das Tag ist stark: kein
W/davor.
Probier es im python, dann import reading_server, und ruf reading_server.make_etag(b"Kindred") auf.
Drück dann Check my work. Es ruft make_etag mit Bodys auf, die du noch nicht gesehen hast: einem leeren Body, einem str ist, der mit einem doppelten Anführungszeichen beginnt und endet und den SHA-256-Hex-Digest genau dieser Bytes enthält (Hex-Ziffern in Großbuchstaben werden akzeptiert), dass gleiche Bytes gleiche Tags ergeben und dass die Änderung eines Bytes oder ein zusätzliches Leerzeichen das Tag ändert. Nichts aus deinem Terminal wird bewertet.