Hoofdstuk 10 · oefening
requests en httpx, nu je het weet
Wat een sessie onthoudt
In Python in Practice stuurde je één requests.get met een time-out, riep je raise_for_status() aan en decodeerde je de /me die daarvan wist. Je bouwde make_cookie_opener, een opener met een cookiejar, en stuurde in login_and_me beide verzoeken daardoorheen. In hoofdstuk 9 schreef je fetch_many, zodat meerdere verzoeken één verbinding konden delen.
Een requests.Session is die opener, maar dan goed gedaan. Het is één requests, en later httpx, nu je het mechanisme achter elke
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 en session.get nemen dezelfde requests.post en requests.get. Er is ook session.request("POST", url, ...), dat de auth=(user, password) schrijft de regel Authorization: Basic voor je; les 2 bekijkt hoe.
Wat een sessie bewaart
Drie dingen die een sessie bewaart doen hier ter zake, en elk ervan gaat mee van de ene aanroep naar de volgende.
session.cookies is de cookiejar. Zijn klasse, RequestsCookieJar, is een subklasse van de http.cookiejar.CookieJar die je in hoofdstuk 7 gebruikte, en hij volgt dezelfde regels: hij leest elke Set-Cookie-regel in een antwoord en stuurt bij elk later verzoek de cookies mee die bij de host en het pad passen. Je kopieert nooit zelf een cookie.
Standaardheaders. session.headers bevat de headers die met elk verzoek meegaan. Een nieuwe sessie heeft er al vier, die requests ongevraagd toevoegt:
User-Agent: python-requests/VERSION
Accept-Encoding: gzip, deflate, zstd
Accept: */*
Connection: keep-alive
VERSION staat voor de versie van requests die hier geïnstalleerd is. De zstd staat erbij omdat de Python waarop deze lessen zijn gecontroleerd, 3.14, die compressie kan decoderen; op een oudere Python eindigt de regel bij deflate. Je voegt er iets aan toe zoals bij een session.headers["accept"] = "application/json". De namen negeren hoofdletters, zoals headernamen dat doen, dus die regel vervangt Accept: */*. Les 5 vertelt waar de vier voor dienen. Wijs je een nieuwe dict toe aan session.headers, dan gooi je de vier weg; verander de dict die je al hebt.
Verbindingen. Een sessie houdt een pool van open verbindingen bij, één per server. Je server spreekt
Welke van deze dingen draagt een requests.Session mee van de ene aanroep naar de volgende?
Wat een sessie niet bewaart
Een time-out. Session heeft helemaal geen time-outinstelling, en de voor de hand liggende gok mislukt stilletjes: session.timeout = 2 slaat een attribuut op dat requests nooit leest, en de volgende aanroep kan zonder grens wachten. De enige manier om een aanroep te begrenzen is timeout= op die aanroep, elke keer.
Deze cursus geeft TIMEOUT = (1.0, 2.0) mee, een paar, zoals in Python in Practice: hoogstens 1 seconde om te verbinden, daarna hoogstens 2 seconden voor elke leesactie. Beide bestanden van de leerling definiëren het bovenaan. Een functie die een argument timeout neemt, geeft het door met timeout=timeout.
session.timeout = 2, daarna session.get(url). Hoe lang kan de aanroep wachten?
Sluit hem
Een sessie houdt verbindingen vast, dus sluit hem als je klaar bent, met hetzelfde with-statement dat je voor bestanden en with make_session() as session: sluit de sessie wanneer het blok eindigt.
Eén detail van de bibliotheekversies die deze cursus draait: het sluiten van de sessie beëindigt een verbinding pas wanneer je programma geen antwoord meer vasthoudt dat over die verbinding binnenkwam. Een antwoord dat in een HTTPError die er een meedraagt, houdt zijn verbinding open tot het weg is, of tot de inactiviteitsgrens van 2 seconden van je server uit hoofdstuk 9 haar beëindigt. pl_http sluit de verbindingen van zijn sessies zelf, dus zijn runs wachten daar nooit op.
Je opdracht
Twee functies, in twee bestanden.
In
reading_client.pygeeftmake_session()een nieuwerequests.Session()terug waarvan de standaardheaders ookaccept: application/jsonzeggen. Les 6 bouwt daarop de commandoregelclient.In
side_by_side.pylogtwho_am_i(base_url, user, password, *, session, timeout=TIMEOUT)in en vraagt wie je bent, op de sessie die hij krijgt:POST base_url + "/login"metauth=(user, password)entimeout=timeout;response.raise_for_status(), zodat een geweigerde loginHTTPErroropwerpt voordat er iets anders wordt verstuurd;GET base_url + "/me"mettimeout=timeout;geef
response.json()["user"]terug.
Gebruik de sessie die je krijgt voor beide verzoeken. Maak geen andere en roep requests.post niet rechtstreeks aan: het cookie van de login leeft in de cookiejar van die sessie, en alleen die sessie stuurt het terug.
Voer daarna de helper uit:
python pl_http.py requests-session
pl_http start je server en geeft who_am_i een eigen sessie. Die sessie stuurt naar je server op deze machine via een handler die pl_http erop koppelt, dus de naam books.test wordt nooit opgezocht. Voordat je iets schrijft, eindigt elke aanroep in raised NotImplementedError: not written yet: Lesson 1. Daarna:
--- 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'
Lees het van boven naar beneden. Het verzoek voor /me droeg een cookieregel die je nooit hebt geschreven; de cookiejar toont het cookie dat je server zette, met de attributen uit hoofdstuk 7. Beide verzoeken van de eerste sessie gingen over verbinding 1. De tweede sessie, met een verkeerd wachtwoord, opende verbinding 2, kreeg 401, en raise_for_status() maakte daar een HTTPError van, dus /me werd nooit gevraagd. Het resultaatblok eindigt met serve() returned after 2 connections; the server is closed.
In het transcript staat bij POST /login en bij GET /me allebei (connection 1). Waarom één verbinding voor twee verzoeken?
Klik op Check my work. Les 2 koppelt de rest van de functies van requests aan de code die je met de hand schreef, en vindt één plek waar auth= en je eigen Basic-header het oneens zijn.
Opdracht
Maak een sessie die om
In
reading_client.pygeeftmake_session()een nieuwerequests.Session()terug metaccept: application/jsontoegevoegd aan zijn standaardheaders. Voeg het toe aan de headers die hij al heeft (session.headers["accept"] = "application/json"); vervang ze niet door een nieuwe. In
side_by_side.pydoetwho_am_i(base_url, user, password, *, session, timeout=TIMEOUT)het volgende:hij stuurt
POST base_url + "/login"metauth=(user, password)entimeout=timeout;hij roept
raise_for_status()aan op dat antwoord, zodat een geweigerde loginHTTPErroropwerpt en er niets meer wordt verstuurd;hij stuurt
GET base_url + "/me"mettimeout=timeout;hij geeft de
useruit de JSON van dat antwoord terug.
Stuur beide verzoeken op session, met session.post, session.get of session.request. Maak geen eigen sessie, roep requests.post of requests.get niet aan en geef zelf geen import requests staan zoals het is.
In de python pl_http.py requests-session je who_am_i op een sessie die pl_http op je server koppelt, daarna op een tweede sessie met een verkeerd wachtwoord, en drukt de cookies in de cookiejar van de eerste sessie af.
Klik daarna op Check my work. Die roept make_session() aan en leest de headers van de sessie. Hij draait who_am_i met een gebruiker, een wachtwoord, een basis-/me moet het cookie meedragen, elk verzoek moet de time-out meedragen die je kreeg, en een verkeerd wachtwoord moet HTTPError opwerpen voordat /me gevraagd wordt. Hij draait who_am_i ook op een sessie die alleen elke aanroep opschrijft, om te zien dat de inloggegevens in auth= gaan en dat er geen cookie met de hand wordt meegegeven. Niets uit je terminal wordt beoordeeld.