requests und httpx, jetzt, wo du Bescheid weißt · Übung
Jede Funktion ist ein Mechanismus
In Lektion 1 hat eine Session ein wire.py oder in deinem Server. Hier stehen sie nebeneinander:
| requests | Was es tut | Deine Version |
|---|---|---|
response.history | die unterwegs gefolgten | follow und seine history, Lektion 6.4 |
allow_redirects=False | die erste 3xx zurückgeben, statt ihr zu folgen | http.client, das nie folgt, Lektion 3.1 |
auth=(user, password) | die Authorization: Basic-Zeile | basic_header, Lektion 7.5 |
session.cookies | der Cookie-Speicher: Set-Cookie behalten, Cookie senden | make_cookie_opener, Lektion 7.3 |
raise_for_status() | bei einem Status 4xx oder 5xx eine | classify und seine Klassen für Client- und Serverfehler, Kapitel 3 |
| der Verbindungspool der Session | mehrere Anfragen über eine offen gehaltene Verbindung | fetch_many, Lektion 9.4 |
.json() | den Body decodieren, gelesen bis zu der Länge, die die Header angegeben haben | read_body und json_body, Kapitel 1 und 3 |
timeout=(1.0, 2.0) | ein begrenzter Verbindungsaufbau, dann begrenzte Lesevorgänge | die Timeouts der Lektionen 3.1 und 3.5 |
Die Tabelle hat keine Zeile für einen Cache. Eine Antwort aufzubewahren, zu entscheiden, ob sie frisch ist, und If-None-Match zu senden, war dein cached_get in Lektion 8.5, und requests tut nichts davon. Mehr dazu weiter unten.
Nimm die ersten beiden Zeilen im Code. Nach response = session.get("http://books.test/v0/books/2") enthält response.history die 308-Antwort, und response.url ist die endgültige http://books.test/books/2. Jede Antwort in history hat ihren eigenen status_code und ihre eigene url, dieselben zwei Fakten, die dein follow in seinen Einträgen der History behalten hat. Mit allow_redirects=False verhält sich requests wie http.client: Die 308 selbst kommt zurück, response.is_redirect ist True, und response.next ist die Anfrage, die es als Nächstes gesendet hätte.
Die meisten Zeilen verhalten sich genau wie dein Code. Drei nicht: die 302-Regel, der Standard bei head() und die Codierung, die auth= benutzt. Die solltest du kennen, bevor du der Bibliothek vertraust.
Wo die Bibliothek sich von dir unterscheidet
Dein follow ändert die POST; nach einer 307 oder 308 behält es Methode und Body bei. requests stimmt bei 301, 307 und 308 zu, und bei 303 für jede Methode außer HEAD, die es beibehält. Bei 302 macht es etwas anderes: Es macht aus jeder Methode außer HEAD ein GET, also wird ein PUT, das mit 302 beantwortet wurde, als GET ohne Body weitergeschickt. Sein POST in ein GET zu ändern. httpx, in Lektion 3, hat dieselbe Browser-Regel.
Das ist ein Grund, warum dein Server für die alten /v0-Pfade 308 benutzt: Bei einer 308 behält jeder Client Methode und Body bei.
Ein Server beantwortet PUT /books/2 mit 302 und location: /books/2-new. Was sendet requests als Nächstes?
Der andere Unterschied ist ein Standardwert. session.get, post, put und delete folgen Weiterleitungen, außer du übergibst allow_redirects=False. session.head tut das Gegenteil: Es gibt eine 3xx so zurück, wie sie ist, außer du übergibst allow_redirects=True.
session.head(url) trifft auf eine 308. Was kommt zurück?
Zugangsdaten sind Bytes
auth=(user, password) baut denselben Header wie dein basic_header: user:password, dann basic_header hat den Text als
Für ASCII-Namen ändert das nichts, weil ASCII-Bytes in beiden gleich sind. Für zoë schon:
Was gibt das aus?
print("zoë".encode("latin-1"), "zoë".encode("utf-8"))
Das strenge UTF-8-Decodieren deines Servers kann das einzelne \xeb von Latin-1 nicht lesen, also bekommt ein Login als zoë trotz richtigem Passwort eine 401. Ein Zeichen, das Latin-1 gar nicht hat, etwa ✓, ist schlimmer: requests löst UnicodeEncodeError aus, bevor irgendetwas gesendet wird.
Die Lösung ist, requests Bytes zu geben, die es unverändert sendet:
credentials = (user.encode("utf-8"), password.encode("utf-8"))
response = session.post(base_url + "/login", auth=credentials, timeout=timeout)
httpx, in Lektion 3, codiert Zugangsdaten, die als Text vorliegen, als UTF-8 und braucht keinen solchen Schritt.
Kein Cache in requests
Eine Session behält Cookies, Header und Verbindungen. Antworten behält sie nicht. Zwei session.get(url)-Aufrufe im Abstand von einer Sekunde senden zwei vollständige Anfragen, auch wenn die erste Antwort cache-control: max-age=60 gesagt hat. Das Revalidieren bleibt deine Aufgabe: Du hältst das Tag fest und sendest es.
response = session.get(url, headers={"if-none-match": etag}, timeout=timeout)
Eine 304 bedeutet, dass deine Kopie noch aktuell ist; eine 200 bringt einen neuen Body und ein neues Tag.
Zwei session.get(url)-Aufrufe im Abstand von einer Sekunde; die erste Antwort hatte cache-control: max-age=60 und ein etag. Was sendet der zweite Aufruf?
Deine Aufgabe
In side_by_side.py:
redirect_trail(response)gibt eine Zeile,f"{r.status_code} {r.url}", für jede Antwort inresponse.historyzurück, die älteste zuerst, und dann eine fürresponseselbst. Ohne Weiterleitung ist das eine Zeile.revalidate(url, etag, *, session=None, timeout=TIMEOUT)sendet einGET urlmit dem Headerif-none-match: etagundtimeout=timeoutund gibt denzurück. Benutze client = requests if session is None else session, die Zeile aus Python in der Praxis, und lass requests jeder Weiterleitung folgen.Ändere
who_am_iso, dass es seine Zugangsdaten als UTF-8-Bytes übergibt.
Führe dann aus:
python pl_http.py mechanisms
pl_http fügt dem Zustand deines Servers einen Benutzer zoë mit dem Passwort pässwörd ✓ hinzu. Vor deiner Änderung endet der letzte Teil mit der Weigerung von requests:
--- your who_am_i() as zoë ---
raised UnicodeEncodeError: 'latin-1' codec can't encode character '\u2713' in position 9: ordinal not in range(256)
Danach zeigt die Spur die 308 des alten Pfads und das Buch, und das Tag, das du festhältst, entscheidet zwischen 200 und 304:
--- pl_http's session.get() of /v0/books/2?x=1, then your redirect_trail() ---
value: ['308 http://books.test/v0/books/2?x=1', '200 http://books.test/books/2?x=1']
--- requests and replies ---
request 1 GET /books/2 (If-None-Match: "stale") -> 200 OK (connection 1)
--- your revalidate() of /books/2 with the tag "stale" ---
value: 200
Der nächste Teil sendet das aktuelle Tag des Buchs und bekommt value: 304, und who_am_i als zoë gibt value: 'zoë' zurück. Klicke auf Check my work. Lektion 3 erledigt dieselben Aufgaben mit httpx, dessen Standardwerte sich in fünf Punkten von denen von requests unterscheiden.
Aufgabe
Lies eine Spur von
In side_by_side.py:
redirect_trail(response)gibt einevon zurück: ein f"{r.status_code} {r.url}"für jede Antwort inresponse.history, die älteste zuerst, dann eines fürresponseselbst.revalidate(url, etag, *, session=None, timeout=TIMEOUT)sendet einGET urlmit dem Headerif-none-match: etag, genau so wie übergeben, undtimeout=timeoutund gibt denstatus_codeder Antwort zurück. Benutzeclient = requests if session is None else session. Lass requests Weiterleitungen folgen, wie es das standardmäßig tut: Übergib nichtallow_redirects=False.who_am_iübergibt seine Zugangsdaten als UTF-8-Bytes:auth=(user.encode("utf-8"), password.encode("utf-8")). Alles andere darin bleibt wie in Lektion 1.
Im python pl_http.py mechanisms deinem Server den Benutzer zoë hinzu und führt dann dein redirect_trail mit einer Anfrage für einen alten /v0-Pfad aus, dein revalidate mit einem veralteten und mit dem aktuellen Tag und dein who_am_i als zoë.
Klicke dann auf Check my work. Es führt deine redirect_trail muss eine 308 und das Buch, zu dem sie führt, auflisten und die Reihenfolge einer längeren History beibehalten; revalidate muss über eine 308 hinweg für das aktuelle Tag 304 und für ein veraltetes 200 bekommen und dabei Tag und Timeout bei beiden Hops senden, mit Session und ohne. Nichts aus deinem Terminal wird bewertet.