Zustand und Identität · Übung
Lass den Client Cookies tragen
Dein Server gibt jetzt ein session hat das Hilfsprogramm das Cookie allerdings selbst aus der Zeile set-cookie in die nächste Anfrage kopiert. Dieses Kopieren ist die Hälfte der Arbeit, die dem Client zufällt, die Arbeit des Servers aus Lektion 2 umgekehrt: Set-Cookie aus einer Antwort lesen, das Cookie behalten und es in einem Header Cookie mit der nächsten Anfrage an denselben Server senden.
urllib, das du in Kapitel 6 für open_url verwendet hast, erledigt alle drei mit zwei Teilen:
http.cookiejar.CookieJar()ist der Cookie-Speicher (cookie jar): ein, das Cookies aufbewahrt. urllib.request.HTTPCookieProcessor(jar)ist ein Schritt für einen Opener. Bei jeder Antwort liest er jede ZeileSet-Cookiein den Speicher; bei jeder Anfrage fügt er einen HeaderCookiemit dem hinzu, was der Speicher für diesen Server enthält.
Steck den Processor in einen Opener, und jede Anfrage über diesen Opener trägt die Session, ohne eigenen Cookie-Code:
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
build_opener nimmt die Schritte, die du ihm übergibst, und fügt die üblichen hinzu, etwa das Herstellen der Verbindung und das Folgen von
Ein Opener, ein Speicher
Der Speicher lebt so lange wie der Opener, der ihn verwendet. Zwei Anfragen über denselben Opener teilen sich einen Speicher, also geht ein Cookie, das die erste Antwort gesetzt hat, mit der zweiten Anfrage hinaus. Ein zweiter Opener mit einem neuen CookieJar() fängt leer an. Eine Anmeldung und die Anfrage danach müssen also über einen Opener gehen: Wenn die zweite Anfrage einen frischen verwendet, sieht der Server kein Cookie und hat keine Ahnung, dass sich der Client gerade angemeldet hat.
login_and_me baut für die Anfrage an /me einen neuen Opener. Was meldet /me?
Ein POST ohne Body
Die Anmeldung ist ein POST, das nichts zu senden hat. Ein einfaches opener.open(url) sendet ein GET, also bau ein Anfrageobjekt, das etwas anderes sagt:
request = urllib.request.Request(base_url + "/login", data=b"", method="POST")
data=b"" ist ein leerer Body. Dafür schreibt urllib content-length: 0, genau das, was die 411-Regel deines Servers von jedem POST verlangt. Es fügt außerdem content-type: application/x-www-form-urlencoded hinzu, den /login liest keinen Body, also ignoriert es das. Sende die Anfrage mit opener.open(request, timeout=timeout) in einer with-Anweisung, wie es open_url getan hat, und behalte response.status. Öffne dann base_url + "/me" über denselben Opener, mit demselben Timeout, und dekodiere seinen Body mit json.loads(response.read().decode("utf-8")) innerhalb des with-Blocks.
Eine 4xx- oder 5xx-Antwort lässt opener.open urllib.error.HTTPError auslösen, wie Kapitel 6 gezeigt hat. open_url hat daraus einen Datensatz gemacht, weil sein Aufrufer jede Antwort wollte. login_and_me fängt es nicht ab: Wenn die Anmeldung fehlschlägt, gibt es keine Session zu lesen, und der Aufrufer soll vom Fehlschlag selbst erfahren. In Lektion 5 wird ein falsches Passwort genau diesen Fehler auslösen, mit dem Code 401, und /me wird nie gefragt.
Was der Speicher entscheidet
Ein Cookie-Speicher tut mehr, als das neueste Cookie zu behalten. Vor jeder Anfrage entscheidet er anhand der Attribute, die der Server gesetzt hat, welche seiner Cookies er sendet:
Domain. Ein Cookie geht nur an den Host zurück, der es gesetzt hat. Ein Cookie von
books.testwird nie anannex.testgesendet.Path.
Path=/sendet es mit jedem Pfad; ein Cookie mitPath=/bookswürde nicht mit/memitgehen.Max-Age. Der Speicher verwirft das Cookie, sobald so viele Sekunden vergangen sind.
Max-Age=0entfernt es sofort.Secure. Der Speicher behält ein
Secure-Cookie, sendet es aber nur überhttps://, nie überhttp://.
Deshalb hat Lektion 1 Secure beim Cookie dieses Kurses weggelassen: Dein Server spricht http://, und ein korrekter Speicher würde eine Secure-Session behalten und nie zurücksenden.
Der Speicher enthält ein Cookie, das als Secure markiert ist. Die nächste Anfrage geht an http://books.test/me. Wird es gesendet?
All das ist die Arbeit von urllib, und du schreibst nichts davon. Was du schreibst, ist die Verdrahtung: ein Speicher, ein Processor, ein Opener für beide Anfragen und ein Timeout für jede.
Deine Aufgabe
In wire.py:
Schreib
make_cookie_opener(jar): Gib einen Opener zurück, gebaut miturllib.request.build_openerund einemurllib.request.HTTPCookieProcessor(jar).Schreib
login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT). WennopenerNoneist, bau einen mitmake_cookie_opener(http.cookiejar.CookieJar()); wenn ein Opener übergeben wird, verwende ihn. Sende dasPOSTanbase_url + "/login", dann dasGETvonbase_url + "/me", beide über diesen Opener und beide mittimeout=timeout. Gib{"login": <the login status>, "me": <the decoded /me body>}zurück. Lassuserundpasswordbis Lektion 5 unbenutzt.
wire.py hat bereits import http.cookiejar und import urllib.request. Schreib die Namen über die urllib.request.Request und http.cookiejar.CookieJar.
Führ dann aus:
python pl_http.py cookies
Das Hilfsprogramm baut einen Opener mit deinem make_cookie_opener und einem eigenen Speicher und fügt diesem Opener dann einen eigenen Handler hinzu: Er schickt die Anfragen des Openers an deinen Server auf diesem Rechner, also wird der Name books.test nie nachgeschlagen. Es ruft dein login_and_me("http://books.test", ...) mit diesem Opener und dem ersten Benutzer und Passwort aus sample_state() auf, die deine
--- requests and replies ---
request 1 POST /login -> 200 OK
request 2 GET /me -> 200 OK
--- your client returned ---
login: 200
me: {'user': 'guest'}
--- requests and replies ---
--- the cookies in the jar pl_http gave your opener ---
session: 'domain books.test, path /, HttpOnly'
Zwei Anfragen haben deinen Server erreicht, und die zweite kam als guest zurück, also ist das Cookie mitgereist. Die letzte Zeile ist das Hilfsprogramm, das danach seinen Speicher liest: ein Cookie, session, für books.test aufbewahrt, mit dem Pfad / und dem Flag HttpOnly.
Drück dann Check my work. Es prüft, wie dein Code den Speicher und den Opener verdrahtet, nicht, was der Speicher innen tut; dieser Teil gehört urllib, und das Hilfsprogramm hat gerade gezeigt, dass er funktioniert. Allerdings kann sich noch jeder als guest anmelden, also bedeutet das, was die Session festhält, noch wenig. Bevor eine Anmeldung ein Passwort prüfen kann, braucht das Passwort einen Weg, in einem Header zu reisen, und das ist die nächste Lektion.
Aufgabe
Lass in wire.py einen urllib-Opener das
make_cookie_opener(jar)gibturllib.request.build_opener(...)zurück, aufgerufen mit einemurllib.request.HTTPCookieProcessor(jar).login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT):Wenn
openerNoneist, bau einen mitmake_cookie_opener(http.cookiejar.CookieJar()). Wenn ein Opener übergeben wird, verwende ihn und bau keinen.Sende ein
POSTanbase_url + "/login"mit leerem Body, zum Beispielurllib.request.Request(base_url + "/login", data=b"", method="POST"), über den Opener mittimeout=timeout, in einerwith-Anweisung, und behalte seinen Status.Öffne dann
base_url + "/me"über denselben Opener mittimeout=timeoutund dekodiere seinen Body innerhalb deswith-Blocks als- . Gib
{"login": <the login status>, "me": <the decoded /me body>}zurück.Fang
urllib.error.HTTPErrornicht ab: Eine fehlgeschlagene Anmeldung verlässtlogin_and_meals diese, und /mewird nicht angefragt.Ignoriere
userundpasswordbis Lektion 5. Schreib keinen HeaderCookieselbst: Der Speicher sendet ihn.
Behalte
import http.cookiejarundimport urllib.requestund schreib die Namen über die, ohne Zeile from urllib.request importoderfrom http.cookiejar import.
Führ python pl_http.py cookies im /me mit dem Cookie liest.
Drück dann Check my work. Es setzt über das Modul Ersatzobjekte für urllib.request.Request, HTTPCookieProcessor, build_opener und urllib.error.HTTPError ein, mit einer Basis-/me mit dem angemeldeten Benutzer oder null. Es prüft, dass ein Opener gebaut wird, wenn keiner übergeben wird, mit einem echten http.cookiejar.CookieJar; dass ein übergebener Opener verwendet und keiner gebaut wird; dass beide Anfragen über einen Opener gehen, die erste ein POST an /login mit leerem Body und die zweite ein GET von /me, jede mit dem Timeout; das zurückgegebene login_and_me verlässt, bevor /me gefragt wird. Nichts aus deinem Terminal wird bewertet.