Toestand en identiteit · oefening
Laat de client cookies meedragen
Je server deelt nu een session kopieerde het hulpprogramma de cookie echter zelf uit de regel set-cookie naar het volgende verzoek. Dat kopiëren is de helft van de taak die bij de client hoort, het werk van de server uit les 2 andersom: Set-Cookie uit een antwoord lezen, de cookie bewaren, en hem in een header Cookie meesturen met het volgende verzoek aan dezelfde server.
urllib, dat je in hoofdstuk 6 voor open_url gebruikte, doet alle drie met twee onderdelen:
http.cookiejar.CookieJar()is de jar: eendat cookies bewaart. urllib.request.HTTPCookieProcessor(jar)is een stap voor een opener. Bij elk antwoord leest hij elke regelSet-Cookiein de jar; bij elk verzoek voegt hij een headerCookietoe met wat de jar voor die server bevat.
Zet de processor in een opener en elk verzoek via die opener draagt de sessie, zonder eigen cookiecode:
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
build_opener neemt de stappen die je hem geeft en voegt de gebruikelijke eromheen toe, zoals de verbinding maken en
Eén opener, één jar
De jar leeft zolang de opener die hem gebruikt. Twee verzoeken die via dezelfde opener worden verstuurd, delen één jar, dus een cookie die door het eerste antwoord is gezet, gaat mee met het tweede verzoek. Een tweede opener die met een nieuwe CookieJar() is gebouwd, begint leeg. Een login en het verzoek erna moeten dus via één opener gaan: als het tweede verzoek een verse opener gebruikt, ziet de server geen cookie en heeft hij geen idee dat de client net heeft ingelogd.
login_and_me bouwt een nieuwe opener voor het verzoek /me. Wat meldt /me?
Een POST zonder body
De login is een POST zonder iets te versturen. Een gewone opener.open(url) stuurt een GET, dus bouw een request-object dat iets anders zegt:
request = urllib.request.Request(base_url + "/login", data=b"", method="POST")
data=b"" is een lege body. Daarvoor schrijft urllib content-length: 0, precies wat de 411-regel van je server van elke POST vraagt. Het voegt ook content-type: application/x-www-form-urlencoded toe, het type van een formulier; /login leest geen body, dus negeert dat. Stuur het verzoek met opener.open(request, timeout=timeout) in een with-statement, zoals open_url deed, en bewaar response.status. Open daarna base_url + "/me" via dezelfde opener, met dezelfde time-out, en decodeer de body met json.loads(response.read().decode("utf-8")), binnen het with-blok.
Een 4xx- of 5xx-antwoord laat opener.open urllib.error.HTTPError opwerpen, zoals hoofdstuk 6 liet zien. open_url maakte daar een record van, omdat de aanroeper elk antwoord wilde. login_and_me vangt hem niet op: als de login mislukt, is er geen sessie om te lezen, en moet de aanroeper zelf van de mislukking horen. In les 5 zal een verkeerd wachtwoord precies die fout opwerpen, met code 401, en wordt /me nooit gevraagd.
Wat de jar beslist
Een jar doet meer dan de laatste cookie bewaren. Vóór elk verzoek beslist hij welke van zijn cookies hij stuurt, op basis van de attributen die de server heeft gezet:
Domain. Een cookie gaat alleen terug naar de host die hem zette. Een cookie van
books.testwordt nooit naarannex.testgestuurd.Path.
Path=/stuurt hem mee met elk pad; een cookie metPath=/bookszou niet meegaan met/me.Max-Age. De jar laat de cookie vallen zodra zoveel seconden voorbij zijn.
Max-Age=0haalt hem meteen weg.Secure. De jar bewaart een
Secure-cookie, maar stuurt hem alleen viahttps://, nooit viahttp://.
Daarom liet les 1 Secure weg van de cookie van deze cursus: je server spreekt http://, en een correcte jar zou een Secure-sessie bewaren en hem nooit terugsturen.
De jar bevat een cookie die als Secure is gemarkeerd. Het volgende verzoek gaat naar http://books.test/me. Wordt hij verstuurd?
Dit is allemaal werk van urllib, en je schrijft er niets van. Wat jij schrijft, is de bedrading: één jar, één processor, één opener voor beide verzoeken, en een time-out op elk.
Jouw opdracht
In wire.py:
Schrijf
make_cookie_opener(jar): geef een opener terug die is gebouwd meturllib.request.build_openeren éénurllib.request.HTTPCookieProcessor(jar).Schrijf
login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT). AlsopenerNoneis, bouw je er een metmake_cookie_opener(http.cookiejar.CookieJar()); als er een opener is meegegeven, gebruik je die. Stuur dePOSTnaarbase_url + "/login", en daarna deGETvanbase_url + "/me", allebei via die opener en allebei mettimeout=timeout. Geef{"login": <the login status>, "me": <the decoded /me body>}terug. Laatuserenpasswordongebruikt tot les 5.
wire.py heeft al import http.cookiejar en import urllib.request. Schrijf de namen via de urllib.request.Request en http.cookiejar.CookieJar.
Voer daarna uit:
python pl_http.py cookies
Het hulpprogramma bouwt een opener met je make_cookie_opener en een eigen jar, en voegt daarna één eigen handler aan die opener toe: die stuurt de verzoeken van de opener naar je server op deze machine, dus de naam books.test wordt nooit opgezocht. Het roept je login_and_me("http://books.test", ...) aan met die opener en de eerste gebruiker en het eerste wachtwoord uit sample_state(), die je
--- 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'
Er bereikten twee verzoeken je server, en het tweede kwam terug als guest, dus de cookie reisde mee. De laatste regel is het hulpprogramma dat daarna zijn jar leest: één cookie, session, bewaard voor books.test met pad / en de vlag HttpOnly.
Klik daarna op Check my work. Die controleert hoe je code de jar en de opener bedraadt, niet wat de jar vanbinnen doet; dat deel is van urllib, en het hulpprogramma liet net zien dat het werkt. Iedereen kan echter nog als guest inloggen, dus wat de sessie vastlegt, betekent nog weinig. Voordat een login een wachtwoord kan controleren, heeft het wachtwoord een manier nodig om in een header te reizen, en dat is de volgende les.
Opdracht
Laat in wire.py een urllib-opener de
make_cookie_opener(jar)geefturllib.request.build_opener(...)terug, aangeroepen met éénurllib.request.HTTPCookieProcessor(jar).login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT):Als
openerNoneis, bouw je er een metmake_cookie_opener(http.cookiejar.CookieJar()). Als er een opener is meegegeven, gebruik je die en bouw je er geen.Stuur een
POSTnaarbase_url + "/login"met een lege body, bijvoorbeeldurllib.request.Request(base_url + "/login", data=b"", method="POST"), via de opener mettimeout=timeout, in eenwith-statement, en bewaar de status ervan.Open daarna
base_url + "/me"via dezelfde opener mettimeout=timeout, en decodeer de body als- binnen het with-blok.Geef
{"login": <the login status>, "me": <the decoded /me body>}terug.Vang
urllib.error.HTTPErrorniet op: een mislukte login verlaatlogin_and_meals die, en /mewordt niet opgevraagd.Negeer
userenpasswordtot les 5. Schrijf zelf geen headerCookie: de jar stuurt die.
Laat
import http.cookiejarenimport urllib.requeststaan, en schrijf de namen via de, zonder regel from urllib.request importoffrom http.cookiejar import.
Voer python pl_http.py cookies uit in de /me leest met de cookie.
Klik daarna op Check my work. Die zet via de module vervangers voor urllib.request.Request, HTTPCookieProcessor, build_opener en urllib.error.HTTPError op hun plaats, met een basis-/me met de ingelogde gebruiker of null. Hij controleert dat er één opener wordt gebouwd als er geen is meegegeven, met een echte http.cookiejar.CookieJar; dat een opener die je krijgt, wordt gebruikt en dat er dan geen wordt gebouwd; dat beide verzoeken via één opener gaan, het eerste een POST naar /login met een lege body en het tweede een GET van /me, elk met de time-out; de teruggegeven login_and_me verlaat voordat /me wordt gevraagd. Niets uit je terminal wordt beoordeeld.