0%

Toestand en identiteit · oefening

Laat de client cookies meedragen

Je server deelt nu een uit en herkent die als hij terugkomt. In het scenario 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: een dat cookies bewaart.

  • urllib.request.HTTPCookieProcessor(jar) is een stap voor een opener. Bij elk antwoord leest hij elke regel Set-Cookie in de jar; bij elk verzoek voegt hij een header Cookie toe 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 volgen. In hoofdstuk 6 riep je hem zonder aan.

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.test wordt nooit naar annex.test gestuurd.

  • Path. Path=/ stuurt hem mee met elk pad; een cookie met Path=/books zou niet meegaan met /me.

  • Max-Age. De jar laat de cookie vallen zodra zoveel seconden voorbij zijn. Max-Age=0 haalt hem meteen weg.

  • Secure. De jar bewaart een Secure-cookie, maar stuurt hem alleen via https://, nooit via http://.

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:

  1. Schrijf make_cookie_opener(jar): geef een opener terug die is gebouwd met urllib.request.build_opener en één urllib.request.HTTPCookieProcessor(jar).

  2. Schrijf login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT). Als opener None is, bouw je er een met make_cookie_opener(http.cookiejar.CookieJar()); als er een opener is meegegeven, gebruik je die. Stuur de POST naar base_url + "/login", en daarna de GET van base_url + "/me", allebei via die opener en allebei met timeout=timeout. Geef {"login": <the login status>, "me": <the decoded /me body>} terug. Laat user en password ongebruikt tot les 5.

wire.py heeft al import http.cookiejar en import urllib.request. Schrijf de namen via de , als 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 voorlopig negeert. Tussen de eerste regel, die je server noemt, en de log van je server zou je dit moeten zien:

--- 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 meedragen.

  • make_cookie_opener(jar) geeft urllib.request.build_opener(...) terug, aangeroepen met één urllib.request.HTTPCookieProcessor(jar).

  • login_and_me(base_url, *, user=None, password=None, opener=None, timeout=TIMEOUT):

    • Als opener None is, bouw je er een met make_cookie_opener(http.cookiejar.CookieJar()). Als er een opener is meegegeven, gebruik je die en bouw je er geen.

    • Stuur een POST naar base_url + "/login" met een lege body, bijvoorbeeld urllib.request.Request(base_url + "/login", data=b"", method="POST"), via de opener met timeout=timeout, in een with-statement, en bewaar de status ervan.

    • Open daarna base_url + "/me" via dezelfde opener met timeout=timeout, en decodeer de body als - binnen het with-blok.

    • Geef {"login": <the login status>, "me": <the decoded /me body>} terug.

    • Vang urllib.error.HTTPError niet op: een mislukte login verlaat login_and_me als die , en /me wordt niet opgevraagd.

    • Negeer user en password tot les 5. Schrijf zelf geen header Cookie: de jar stuurt die.

  • Laat import http.cookiejar en import urllib.request staan, en schrijf de namen via de , zonder regel from urllib.request import of from http.cookiejar import.

Voer python pl_http.py cookies uit in de om te zien hoe je client inlogt op je eigen server en /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- die je nog niet hebt gezien. De vervangende opener bewaart alleen een cookie als hij met een cookieprocessor is gebouwd, en beantwoordt /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 ; en dat een fout uit de login login_and_me verlaat voordat /me wordt gevraagd. Niets uit je terminal wordt beoordeeld.