Doorverwijzingen · oefening
Laat urllib voor je volgen
follow beslist alles wat een location naar wijst, welke urllib.request. Deze les doet wat hoofdstuk 3 met http.client deed: hij neemt code die je met de hand hebt geschreven en geeft de taak aan een bibliotheek, zodat je precies kunt zien welke van je beslissingen die neemt, en waar hij anders beslist.
Een opener
urllib.request stuurt verzoeken via een opener, een urllib.request.build_opener() geeft er een terug met de gebruikelijke stappen op hun plaats, en opener.open(url, timeout=...) stuurt een GET en volgt eventuele doorverwijzingen voordat hij terugkeert:
import urllib.request
opener = urllib.request.build_opener()
with opener.open("http://books.test/v0/books/2", timeout=2.0) as response:
print(response.status, response.geturl())
body = response.read()
Het antwoord werkt in een with-statement, dat het sluit als het blok eindigt; lees de body binnen het blok. response.status is de uiteindelijke status, en response.geturl() is de uiteindelijke urllib.request.urlopen(url): dat doet hetzelfde via een opener die de open_url neemt opener=None en bouwt er alleen een als er geen is meegegeven, zoals session=None in Python in Practice werkte, zodat een test er een vervanger aan kan geven.
Wat urllib volgt
Hier staan je regels uit les 4 naast die van urllib:
| Verzoek en doorverwijzing | Jouw follow | urllib |
|---|---|---|
GET, elk van de vijf | volgt met GET | volgt met GET |
HEAD, elk van de vijf | volgt met HEAD, behalve GET na 303 | volgt met HEAD |
POST na 301, 302 of 303 | volgt met GET, geen body | volgt met GET, geen body |
POST na 307 of 308 | volgt met POST en de body | werpt HTTPError op |
PUT na elk van de vijf | behoudt PUT en de body, behalve GET na 303 | werpt HTTPError op |
| een doorverwijslus | ValueError na max_hops doorverwijzingen | HTTPError, na 9 verzoeken in een |
Bij HEAD na een 303 laat urllib een HEAD een HEAD blijven, wat RFC 9110 toestaat; je volger stuurt GET en leest een body waar de aanroeper niet om vroeg. Voor een GET zijn de twee het eens: beide lossen een relatieve location op, en beide behouden de querystring. De limieten van urllib komen uit zijn eigen tellers: hij geeft op na tien doorverwijzingen, of als dezelfde URL te vaak terugkomt. Het verschil zit in schrijfacties. urllib volgt een verzoek met een body alleen waar de geschiedenis het toestaat er een GET van te maken. Overal waar PUT door een 308 moet sturen, zoals oude leeslijstclients doen, kan daarvoor de volger van urllib niet gebruiken. Je follow kan dat wel.
opener.open stuurt PUT /v0/books/2 en krijgt een 308. Wat gebeurt er?
Fouten zijn antwoorden
opener.open werpt urllib.error.HTTPError op bij elk 4xx- of 5xx-antwoord. De naam zegt fout, maar het antwoord is aangekomen: de exception draagt alles wat get_json in hoofdstuk 3 bewaarde. exc.code is de status, exc.geturl() de URL die antwoordde, en exc.read() de body, die je leest en daarna vrijgeeft met exc.close(). Dus vangt open_url HTTPError op en geeft hij hetzelfde soort record terug als bij een succes. Over een 404 beslist de aanroeper, zoals get_json je leerde.
Vang precies die klasse op, geschreven via de module als urllib.error.HTTPError. Het is verleidelijk om iets breders op te vangen, en dat is fout. HTTPError is een soort urllib.error.URLError, en dat is een soort OSError. Als de client helemaal geen verbinding kan maken, werpt open URLError op, met de oorspronkelijke fout, zoals ConnectionRefusedError, in zijn .reason. Als de verbinding opengaat en er niet op tijd een antwoord komt, werpt open een kale TimeoutError op, die ook een OSError is. Geen van beide is een antwoord, en geen van beide heeft een status. Een except OSError of except Exception zou van beide records maken alsof een server had geantwoord, dezelfde fout als een time-out melden als een 504 in hoofdstuk 3. open_url laat beide door.
open_url("http://books.test/v0/books/2") wordt beantwoord met een 308 en daarna een 200. Wat geeft response.geturl() terug?
Jouw opdracht
Schrijf open_url(url, *, opener=None, timeout=TIMEOUT) in wire.py:
Als
openerNoneis, zet je hem opurllib.request.build_opener().with opener.open(url, timeout=timeout) as response:geef je eenterug met "status"(response.status),"url"(response.geturl()) en"body"(response.read()).except urllib.error.HTTPError as exc:lees je de body metexc.read(), roep jeexc.close()aan, en geef je dezelfde drie sleutels terug uitexc.code,exc.geturl()en de body.
wire.py heeft al import urllib.error en import urllib.request. Laat dat zo, en schrijf de namen via de module: een regel from urllib.error import HTTPError zou verhinderen dat Check my work je code een vervangende fout geeft.
Voer daarna het hulpprogramma uit:
python pl_http.py urllib
Het roept je open_url twee keer aan met een opener waarvan de verbindingen naar je eigen server op deze machine gaan, dus er wordt geen naam zoals books.test opgezocht. /v0/books/2 toont twee verzoeken, 308 Permanent Redirect en daarna 200 OK, en je record met url: 'http://books.test/books/2'. /books/99 geeft je record terug met status: 404 en de foutbody. Daarna stuurt het hulpprogramma zijn eigen urllib-POST /v0/books, en het laatste blok luidt:
--- pl_http's own urllib POST /v0/books ---
raised HTTPError: HTTP Error 308: Permanent Redirect
Dat is urllib die weigert een body opnieuw door een 308 te sturen. Klik daarna op Check my work. De volgende les stuurt schrijfacties in plaats daarvan door je eigen volger, en bewijst dat die en je server het eens zijn.
Opdracht
Schrijf open_url(url, *, opener=None, timeout=TIMEOUT) in wire.py.
Als
openerNoneis, bouw je er een meturllib.request.build_opener(). Als er een opener is meegegeven, gebruik je die en bouw je er geen.Stuur het verzoek met
opener.open(url, timeout=timeout)in eenwith-statement, en geef eenterug met "status"(response.status),"url"(response.geturl(), de uiteindelijkena ) en "body"(response.read(), gelezen binnen het blok).Vang
urllib.error.HTTPErrorop, en alleen die: lees de body ervan metexc.read(), roepexc.close()aan, en geef dezelfde drie sleutels terug uitexc.code,exc.geturl()en de body.Laat elke andere
door, zoals een URLErrorvoor een geweigerde verbinding of eenTimeoutError.Laat
import urllib.errorenimport urllib.requestzoals ze zijn, en schrijfurllib.error.HTTPErrorenurllib.request.build_openervia de, zonder regel from urllib.error importoffrom urllib.request import.
Voer python pl_http.py urllib uit in de POST opnieuw door een 308 te sturen.
Klik daarna op Check my work. Die geeft open_url een vervangende opener en zet via de module vervangers voor urllib.request.build_opener en urllib.error op hun plaats. Hij controleert dat een gevolgde GET de uiteindelijke status, URL en body teruggeeft en dat het antwoord werd gesloten; dat een 404 en een 503 terugkomen als records met hun bodies en worden gesloten; dat een TimeoutError, een URLError en een andere OSError allemaal uit open_url naar buiten komen; dat de time-out, standaard 2.0, open bereikt; en dat build_opener alleen wordt aangeroepen als er geen opener is meegegeven. Niets uit je terminal wordt beoordeeld.