0%

Doorverwijzingen · oefening

Laat urllib voor je volgen

follow beslist alles wat een openlaat: waar de location naar wijst, welke en body daarheen gaan, en wanneer je stopt. De standaardbibliotheek van Python heeft een client die dezelfde beslissingen voor je neemt, 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 dat de stappen bevat die een verzoek doorloopt: de verbinding maken, doorverwijzingen volgen, foutstatussen omzetten in . 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 , na elke doorverwijzing. Misschien ken je urllib.request.urlopen(url): dat doet hetzelfde via een opener die de één keer bouwt en bewaart. Een eigen opener meegeven is de naad (seam) die je sinds hoofdstuk 1 gebruikt. 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 doorverwijzingJouw followurllib
GET, elk van de vijfvolgt met GETvolgt met GET
HEAD, elk van de vijfvolgt met HEAD, behalve GET na 303volgt met HEAD
POST na 301, 302 of 303volgt met GET, geen bodyvolgt met GET, geen body
POST na 307 of 308volgt met POST en de bodywerpt HTTPError op
PUT na elk van de vijfbehoudt PUT en de body, behalve GET na 303werpt HTTPError op
een doorverwijslusValueError na max_hops doorverwijzingenHTTPError, na 9 verzoeken in een van twee paden

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 zegt dezelfde methode en body opnieuw te sturen, en daar bestaan 307 en 308 precies voor, weigert urllib en werpt hij in plaats daarvan een fout op. Dat is een veilige keuze voor een algemene bibliotheek, maar een client die een 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:

  1. Als opener None is, zet je hem op urllib.request.build_opener().

  2. with opener.open(url, timeout=timeout) as response: geef je een terug met "status" (response.status), "url" (response.geturl()) en "body" (response.read()).

  3. except urllib.error.HTTPError as exc: lees je de body met exc.read(), roep je exc.close() aan, en geef je dezelfde drie sleutels terug uit exc.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 opener None is, bouw je er een met urllib.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 een with-statement, en geef een terug met "status" (response.status), "url" (response.geturl(), de uiteindelijke na ) en "body" (response.read(), gelezen binnen het blok).

  • Vang urllib.error.HTTPError op, en alleen die: lees de body ervan met exc.read(), roep exc.close() aan, en geef dezelfde drie sleutels terug uit exc.code, exc.geturl() en de body.

  • Laat elke andere door, zoals een URLError voor een geweigerde verbinding of een TimeoutError.

  • Laat import urllib.error en import urllib.request zoals ze zijn, en schrijf urllib.error.HTTPError en urllib.request.build_opener via de , zonder regel from urllib.error import of from urllib.request import.

Voer python pl_http.py urllib uit in de om te zien hoe urllib een doorverwijzing van je server volgt, een 404 als record teruggeeft, en weigert een 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.