Kapitel 10 · Übung
requests und httpx, jetzt, wo du Bescheid weißt
Was eine Session sich merkt
In Python in der Praxis hast du ein einzelnes requests.get mit Timeout gesendet, raise_for_status() aufgerufen und das /me, das davon wusste. Du hast make_cookie_opener gebaut, einen Opener mit login_and_me beide Anfragen durch ihn gesendet. In Kapitel 9 hast du fetch_many geschrieben, damit sich mehrere Anfragen eine Verbindung teilen können.
Eine requests.Session ist dieser Opener, richtig gemacht. Sie ist ein einziges requests zurück und später zu httpx, jetzt, wo du den Mechanismus hinter jeder
import requests
with requests.Session() as session:
session.post("http://books.test/login", auth=("ana", "correct horse"), timeout=(1.0, 2.0))
reply = session.get("http://books.test/me", timeout=(1.0, 2.0))
session.post und session.get nehmen dieselben requests.post und requests.get. Außerdem gibt es session.request("POST", url, ...), das die auth=(user, password) schreibt die Authorization: Basic-Zeile für dich; Lektion 2 sieht sich an, wie.
Was eine Session behält
Drei Dinge, die eine Session behält, sind hier wichtig, und jedes wird von einem Aufruf zum nächsten mitgenommen.
Cookies. session.cookies ist der Cookie-Speicher. Seine Klasse, RequestsCookieJar, ist eine Unterklasse des http.cookiejar.CookieJar, den du in Kapitel 7 benutzt hast, und sie folgt denselben Regeln: Sie liest jede Set-Cookie-Zeile in einer Antwort und sendet die Cookies, die zu Host und Pfad passen, mit jeder späteren Anfrage. Du kopierst nie ein Cookie von Hand.
Standard-Header. session.headers enthält die Header, die mit jeder Anfrage gesendet werden. Eine neue Session hat schon vier, die requests ungefragt hinzufügt:
User-Agent: python-requests/VERSION
Accept-Encoding: gzip, deflate, zstd
Accept: */*
Connection: keep-alive
VERSION steht für die hier installierte Version von requests. Das zstd ist da, weil das Python, mit dem diese Lektionen geprüft wurden, 3.14, diese Kompression decodieren kann. Auf einem älteren Python endet die Zeile bei deflate. Du ergänzt sie wie ein session.headers["accept"] = "application/json". Die Namen ignorieren Groß- und Kleinschreibung, wie Header-Namen es tun, also ersetzt diese Zeile Accept: */*. Lektion 5 sagt, wofür die vier da sind. Wenn du session.headers ein neues Dictionary zuweist, wirfst du die vier weg; ändere das, das du hast.
Verbindungen. Eine Session hält einen Pool offener Verbindungen, eine pro Server. Dein Server spricht
Was davon nimmt eine requests.Session von einem Aufruf zum nächsten mit?
Was eine Session nicht behält
Einen Timeout. Session hat überhaupt keine Timeout-Einstellung, und die naheliegende Vermutung scheitert lautlos: session.timeout = 2 speichert ein Attribut, das requests nie liest, und der nächste Aufruf kann unbegrenzt warten. Der einzige Weg, einen Aufruf zu begrenzen, ist timeout= bei genau diesem Aufruf, jedes Mal.
Dieser Kurs übergibt TIMEOUT = (1.0, 2.0), ein Paar, wie in Python in der Praxis: höchstens 1 Sekunde für den Verbindungsaufbau, dann höchstens 2 Sekunden für jeden Lesevorgang. Beide Dateien, die du bearbeitest, definieren es ganz oben. Eine Funktion, die ein Argument timeout nimmt, gibt es mit timeout=timeout weiter.
session.timeout = 2, dann session.get(url). Wie lange kann der Aufruf warten?
Schließe sie
Eine Session hält Verbindungen, also schließe sie, wenn du fertig bist, mit derselben with-Anweisung, die du für Dateien und with make_session() as session: schließt die Session, wenn der Block endet.
Ein Detail der Bibliotheksversionen, mit denen dieser Kurs läuft: Das Schließen der Session beendet eine Verbindung erst, wenn dein Programm keine Antwort mehr festhält, die über sie gekommen ist. Eine Antwort, die in einer HTTPError, der eine enthält, hält ihre Verbindung offen, bis sie weg ist oder bis das Leerlauflimit deines Servers von 2 Sekunden aus Kapitel 9 sie beendet. pl_http schließt die Verbindungen seiner Sessions selbst, sodass seine Läufe nie darauf warten.
Deine Aufgabe
Zwei Funktionen in zwei Dateien.
In
reading_client.pygibtmake_session()eine neuerequests.Session()zurück, deren Standard-Header zusätzlichaccept: application/jsonsagen. Lektion 6 baut den Kommandozeilen-Client darauf auf.In
side_by_side.pymeldet sichwho_am_i(base_url, user, password, *, session, timeout=TIMEOUT)an und fragt, wer du bist, auf der Session, die es bekommt:POST base_url + "/login"mitauth=(user, password)undtimeout=timeout;response.raise_for_status(), damit ein abgelehntes LoginHTTPErrorauslöst, bevor irgendetwas anderes gesendet wird;GET base_url + "/me"mittimeout=timeout;gib
response.json()["user"]zurück.
Benutze für beide Anfragen die Session, die du bekommst. Erstelle keine weitere und rufe requests.post nicht direkt auf: Das Cookie vom Login lebt im Cookie-Speicher dieser Session, und nur diese Session sendet es zurück.
Führe dann das Hilfsprogramm aus:
python pl_http.py requests-session
pl_http startet deinen Server und gibt who_am_i eine eigene Session. Diese Session sendet an deinen Server auf diesem Rechner, über einen Handler, den pl_http auf ihr einhängt, sodass der Name books.test nie nachgeschlagen wird. Bevor du etwas schreibst, endet jeder Aufruf in raised NotImplementedError: not written yet: Lesson 1. Danach:
--- requests and replies ---
request 1 POST /login -> 200 OK (connection 1)
request 2 GET /me (with a cookie line) -> 200 OK (connection 1)
--- your who_am_i() on pl_http's session ---
value: 'ana'
--- requests and replies ---
request 1 POST /login -> 401 Unauthorized (connection 2)
--- your who_am_i() on a second session, with a wrong password ---
raised HTTPError: 401 Client Error: Unauthorized for url: http://books.test/login
--- requests and replies ---
--- the cookies in the first session's jar ---
session: 'domain books.test, path /, HttpOnly'
Lies es von oben nach unten. Die /me-Anfrage enthielt eine Cookie-Zeile, die du nie geschrieben hast; der Cookie-Speicher zeigt das Cookie, das dein Server gesetzt hat, mit den Attributen aus Kapitel 7. Beide Anfragen der ersten Session gingen über Verbindung 1 hinaus. Die zweite Session, mit einem falschen Passwort, hat Verbindung 2 geöffnet, eine 401 bekommen, und raise_for_status() hat daraus einen HTTPError gemacht, sodass /me nie angefragt wurde. Der Ergebnisblock endet mit serve() returned after 2 connections; the server is closed.
Im Protokoll sagen POST /login und GET /me beide (connection 1). Warum eine Verbindung für zwei Anfragen?
Klicke auf Check my work. Lektion 2 ordnet die übrigen Funktionen von requests dem Code zu, den du von Hand geschrieben hast, und findet eine Stelle, an der auth= und dein eigener Basic-Header nicht übereinstimmen.
Aufgabe
Erstelle eine Session, die nach
In
reading_client.pygibtmake_session()eine neuerequests.Session()zurück, deren Standard-Header umaccept: application/jsonergänzt sind. Ergänze die Header, die sie schon hat (session.headers["accept"] = "application/json"); ersetze sie nicht durch ein neues. In
side_by_side.pymachtwho_am_i(base_url, user, password, *, session, timeout=TIMEOUT)Folgendes:es sendet
POST base_url + "/login"mitauth=(user, password)undtimeout=timeout;es ruft
raise_for_status()auf dieser Antwort auf, damit ein abgelehntes LoginHTTPErrorauslöst und nichts weiter gesendet wird;es sendet
GET base_url + "/me"mittimeout=timeout;es gibt
useraus dem JSON dieser Antwort zurück.
Sende beide Anfragen über session, mit session.post, session.get oder session.request. Erstelle keine eigene Session, rufe weder requests.post noch requests.get auf und übergib kein import requests so, wie es ist.
Im python pl_http.py requests-session dein who_am_i auf einer Session aus, die pl_http auf deinem Server einhängt, dann auf einer zweiten Session mit falschem Passwort, und gibt die Cookies im Cookie-Speicher der ersten Session aus.
Klicke dann auf Check my work. Es ruft make_session() auf und liest die Header der Session. Es führt who_am_i mit einem Benutzer, einem Passwort, einer Basis-/me muss das Cookie enthalten, jede Anfrage muss den Timeout tragen, den du bekommen hast, und ein falsches Passwort muss HTTPError auslösen, bevor /me angefragt wird. Außerdem führt es who_am_i auf einer Session aus, die nur jeden Aufruf aufschreibt, um zu sehen, dass die Zugangsdaten in auth= stehen und kein Cookie von Hand übergeben wird. Nichts aus deinem Terminal wird bewertet.