0%

requests en httpx, nu je het weet · oefening

Elke functie is een mechanisme

In les 1 droeg een sessie een voor je mee. Elke andere van requests die deze cursus gebruikt, is ook iets wat je al met de hand hebt geschreven, in wire.py of in je server. Hier staan ze naast elkaar:

requestsWat het doetJouw versie
response.historyde die onderweg gevolgd zijn, oudste eerstfollow en zijn history, les 6.4
allow_redirects=Falsegeef de eerste 3xx terug in plaats van hem te volgenhttp.client, dat nooit volgt, les 3.1
auth=(user, password)de regel Authorization: Basicbasic_header, les 7.5
session.cookiesde cookiejar: bewaar Set-Cookie, stuur Cookiemake_cookie_opener, les 7.3
raise_for_status()werp een op bij een status 4xx of 5xxclassify en zijn statusklassen voor client- en serverfouten, hoofdstuk 3
de verbindingspool van de sessiemeerdere verzoeken op één open gehouden verbindingfetch_many, les 9.4
.json()decodeer de body, gelezen tot de lengte die de headers gavenread_body en json_body, hoofdstukken 1 en 3
timeout=(1.0, 2.0)een begrensd verbinden, daarna begrensde leesactiesde time-outs van les 3.1 en 3.5

De tabel heeft geen rij voor een cache. Een antwoord bewaren, beslissen of het vers is en If-None-Match sturen deed je cached_get in les 8.5, en requests doet daar niets van. Daarover hieronder meer.

Neem de eerste twee rijen in code. Na response = session.get("http://books.test/v0/books/2") bevat response.history het 308-antwoord, en response.url is de uiteindelijke , http://books.test/books/2. Elk antwoord in history heeft zijn eigen status_code en url, dezelfde twee feiten die je follow in zijn history-items bewaarde. Met allow_redirects=False gedraagt requests zich als http.client: de 308 zelf komt terug, response.is_redirect is True, en response.next is het verzoek dat hij als volgende zou hebben verstuurd.

De meeste rijen gedragen zich precies zoals jouw code deed. Drie niet: de regel voor 302, de standaardwaarde van head() en de codering die auth= gebruikt. Die zijn het weten waard voordat je de bibliotheek vertrouwt.

Waar de bibliotheek van jou verschilt

Je follow verandert de alleen na een 303, of na een 301 of 302 voor een POST; na een 307 of 308 houdt hij de methode en de body. requests is het eens over 301, 307 en 308, en over 303 voor elke methode behalve HEAD, die hij houdt. Bij 302 doet hij iets anders: hij maakt van elke methode behalve HEAD een GET, dus een PUT die met 302 beantwoord wordt, gaat verder als een GET zonder body. De zegt in wezen waarom: browsers doen het, ondanks de standaard. RFC 9110 staat een client toe om na een 301 of 302 alleen een POST in GET te veranderen. httpx, in les 3, heeft dezelfde browserregel.

Dat is een van de redenen waarom je server 308 gebruikt voor de oude /v0-paden: onder een 308 houdt elke client de methode en de body.

Een server beantwoordt PUT /books/2 met 302 en location: /books/2-new. Wat stuurt requests daarna?

Het andere verschil is een standaardwaarde. session.get, post, put en delete volgen doorverwijzingen, tenzij je allow_redirects=False meegeeft. session.head doet het omgekeerde: hij geeft een 3xx terug zoals die is, tenzij je allow_redirects=True meegeeft.

session.head(url) krijgt een 308. Wat komt er terug?

Inloggegevens zijn bytes

auth=(user, password) bouwt dezelfde header als je basic_header: user:password, daarna . Het verschil zit in de stap daartussen. Je basic_header codeerde de tekst als , en je server decodeert hem als UTF-8. requests codeert inloggegevens die als tekst gegeven zijn als Latin-1, de codering van 256 tekens die vroeger aannamen.

Voor ASCII-namen verandert dat niets, omdat ASCII-bytes in beide hetzelfde zijn. Voor zoë wel:

Wat drukt dit af?

print("zoë".encode("latin-1"), "zoë".encode("utf-8"))

De strikte UTF-8-decodering van je server kan de losse \xeb van Latin-1 niet lezen, dus een login als zoë krijgt 401 met het juiste wachtwoord. Een teken dat Latin-1 helemaal niet heeft, zoals ✓, is erger: requests werpt UnicodeEncodeError op voordat er iets wordt verstuurd.

De oplossing is requests bytes te geven, die hij verstuurt zoals ze zijn:

credentials = (user.encode("utf-8"), password.encode("utf-8"))
response = session.post(base_url + "/login", auth=credentials, timeout=timeout)

httpx, in les 3, codeert inloggegevens die als tekst gegeven zijn als UTF-8 en heeft zo’n stap niet nodig.

Geen cache in requests

Een sessie bewaart cookies, headers en verbindingen. Antwoorden bewaart hij niet. Twee aanroepen van session.get(url) met een seconde ertussen sturen twee volledige verzoeken, ook als het eerste antwoord cache-control: max-age=60 zei. Hervalideren blijft jouw werk: jij houdt de tag vast en stuurt hem.

response = session.get(url, headers={"if-none-match": etag}, timeout=timeout)

Een 304 betekent dat je kopie nog actueel is; een 200 brengt een nieuwe body en een nieuwe tag.

Twee aanroepen van session.get(url) met een seconde ertussen; het eerste antwoord had cache-control: max-age=60 en een etag. Wat stuurt de tweede aanroep?

Je opdracht

In side_by_side.py:

  1. redirect_trail(response) geeft één regel, f"{r.status_code} {r.url}", terug voor elk antwoord in response.history, oudste eerst, en daarna één voor response zelf. Zonder doorverwijzing is dat één regel.

  2. revalidate(url, etag, *, session=None, timeout=TIMEOUT) stuurt één GET url met de header if-none-match: etag en timeout=timeout, en geeft de terug. Gebruik client = requests if session is None else session, de regel uit Python in Practice, en laat requests elke doorverwijzing volgen.

  3. Verander who_am_i zo dat hij zijn inloggegevens als UTF-8-bytes meegeeft.

Voer daarna uit:

python pl_http.py mechanisms

pl_http voegt een gebruiker, zoë, met het wachtwoord pässwörd ✓, toe aan de state van je server. Vóór je wijziging eindigt het laatste deel in de weigering van 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)

Daarna toont het spoor de 308 van het oude pad en het boek, en de tag die je vasthoudt beslist tussen 200 en 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

Het volgende deel stuurt de huidige tag van het boek en krijgt value: 304, en who_am_i als zoë geeft value: 'zoë' terug. Klik op Check my work. Les 3 doet dezelfde klussen met httpx, waarvan de standaardinstellingen op vijf punten verschillen van die van requests.

Opdracht

Lees een spoor van , hervalideer met de hand, en stuur inloggegevens als .

In side_by_side.py:

  1. redirect_trail(response) geeft een terug: één f"{r.status_code} {r.url}" voor elk antwoord in response.history, oudste eerst, daarna één voor response zelf.

  2. revalidate(url, etag, *, session=None, timeout=TIMEOUT) stuurt één GET url met de header if-none-match: etag, precies zoals gegeven, en timeout=timeout, en geeft de status_code van het antwoord terug. Gebruik client = requests if session is None else session. Laat requests doorverwijzingen volgen, zoals hij standaard doet: geef geen allow_redirects=False mee.

  3. who_am_i geeft zijn inloggegevens mee als UTF-8-bytes: auth=(user.encode("utf-8"), password.encode("utf-8")). Al het andere erin blijft zoals in les 1.

In de voegt python pl_http.py mechanisms de gebruiker zoë toe aan je server, en draait daarna je redirect_trail op een verzoek voor een oud /v0-pad, je revalidate met een verouderde tag en met de huidige, en je who_am_i als zoë.

Klik daarna op Check my work. Die draait je op een echte requests-sessie waarvan de verzoeken in het geheugen beantwoord worden door een kleine server binnen de controle, met gebruikers, wachtwoorden, een basis-, een boek en tags die je niet hebt gezien: twee gebruikers wier namen of wachtwoorden geen ASCII zijn, moeten inloggen; redirect_trail moet een 308 en het boek waar die naartoe leidt opsommen, en de volgorde van een langere history behouden; revalidate moet via een 308 een 304 krijgen voor de huidige tag en een 200 voor een verouderde, en op beide hops de tag en de time-out sturen, met een sessie en zonder. Niets uit je terminal wordt beoordeeld.