Weiterleitungen · Übung
Lass urllib für dich folgen
follow entscheidet alles, was eine location zeigt, welche urllib.request. Diese Lektion tut, was Kapitel 3 mit http.client getan hat: Sie nimmt Code, den du von Hand geschrieben hast, und übergibt die Aufgabe an eine Bibliothek, damit du genau sehen kannst, welche deiner Entscheidungen sie trifft und wo sie anders entscheidet.
Ein Opener
urllib.request sendet Anfragen über einen Opener, ein urllib.request.build_opener() gibt einen mit den üblichen Schritten zurück, und opener.open(url, timeout=...) sendet ein GET und folgt allen Weiterleitungen, bevor es zurückkehrt:
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()
Die Antwort funktioniert in einer with-Anweisung, die sie schließt, wenn der Block endet; lies den Body innerhalb des Blocks. response.status ist der endgültige Status, und response.geturl() ist die endgültige urllib.request.urlopen(url): Es tut dasselbe über einen Opener, den das open_url nimmt opener=None und baut nur dann einen, wenn keiner übergeben wird, so wie session=None in Python in der Praxis funktioniert hat, damit ein Test ihm einen Ersatz übergeben kann.
Wem urllib folgt
Hier sind deine Regeln aus Lektion 4 neben denen von urllib:
| Anfrage und Weiterleitung | Dein follow | urllib |
|---|---|---|
GET, einer der fünf | folgt mit GET | folgt mit GET |
HEAD, einer der fünf | folgt mit HEAD, außer GET nach 303 | folgt mit HEAD |
POST nach 301, 302 oder 303 | folgt mit GET, ohne Body | folgt mit GET, ohne Body |
POST nach 307 oder 308 | folgt mit POST und dem Body | löst HTTPError aus |
PUT nach einem der fünf | behält PUT und den Body, außer GET nach 303 | löst HTTPError aus |
| eine Weiterleitungsschleife | ValueError nach max_hops Weiterleitungen | HTTPError, nach 9 Anfragen in einer |
Bei HEAD nach einer 303 behält urllib ein HEAD als HEAD bei, was RFC 9110 erlaubt; dein Code sendet GET und liest einen Body, nach dem der Aufrufer nicht gefragt hat. Bei einem GET sind sich die beiden einig: Beide lösen ein relatives location auf, und beide behalten die Query. Die Grenzen von urllib kommen aus eigenen Zählern: Es gibt nach zehn Weiterleitungen auf oder wenn dieselbe URL zu oft zurückkommt. Der Unterschied liegt bei Schreibzugriffen. urllib folgt einer Anfrage mit Body nur dort, wo die Geschichte erlaubt, dass sie zu einem GET wird. Überall dort, wo PUT durch eine 308 senden muss, wie es alte Leseliste-Clients tun, kann dafür nicht das Folgen von urllib verwenden. Dein follow kann es.
opener.open sendet PUT /v0/books/2 und bekommt eine 308. Was passiert?
Fehler sind Antworten
opener.open löst bei jeder 4xx- oder 5xx-Antwort urllib.error.HTTPError aus. Sein Name sagt Fehler, aber die Antwort ist angekommen: Die Ausnahme trägt alles, was get_json in Kapitel 3 behalten hat. exc.code ist der Status, exc.geturl() die URL, die geantwortet hat, und exc.read() der Body, den du liest und dann mit exc.close() freigibst. Also fängt open_url HTTPError ab und gibt dieselbe Art von Datensatz zurück wie bei einem Erfolg. Über eine 404 entscheidet der Aufrufer, wie dir get_json beigebracht hat.
Fang genau diese Klasse ab, über das Modul geschrieben als urllib.error.HTTPError. Es ist verlockend, etwas Breiteres abzufangen, und es ist falsch. HTTPError ist eine Art von urllib.error.URLError, das eine Art von OSError ist. Wenn sich der Client überhaupt nicht verbinden kann, löst open URLError aus, mit dem ursprünglichen Fehler, etwa ConnectionRefusedError, in seinem .reason. Wenn die Verbindung aufgeht und keine Antwort rechtzeitig kommt, löst open ein bloßes TimeoutError aus, das ebenfalls ein OSError ist. Keins von beiden ist eine Antwort, und keins hat einen Status. Ein except OSError oder except Exception würde beide in Datensätze verwandeln, als hätte ein Server geantwortet, derselbe Fehler, wie in Kapitel 3 einen Timeout als 504 zu melden. open_url lässt beide durch.
open_url("http://books.test/v0/books/2") wird mit einer 308 und dann einer 200 beantwortet. Was gibt response.geturl() zurück?
Deine Aufgabe
Schreib open_url(url, *, opener=None, timeout=TIMEOUT) in wire.py:
Wenn
openerNoneist, setz ihn aufurllib.request.build_opener().with opener.open(url, timeout=timeout) as response:gib einmit "status"(response.status),"url"(response.geturl()) und"body"(response.read()) zurück.except urllib.error.HTTPError as exc:lies den Body mitexc.read(), rufexc.close()auf und gib dieselben drei Schlüssel ausexc.code,exc.geturl()und dem Body zurück.
wire.py hat bereits import urllib.error und import urllib.request. Lass das so und schreib die Namen über das Modul: Eine Zeile from urllib.error import HTTPError würde Check my work daran hindern, deinem Code einen Ersatzfehler zu übergeben.
Starte dann das Hilfsprogramm:
python pl_http.py urllib
Es ruft dein open_url zweimal mit einem Opener auf, dessen Verbindungen zu deinem eigenen Server auf diesem Rechner gehen, also wird kein Name wie books.test nachgeschlagen. /v0/books/2 zeigt zwei Anfragen, 308 Permanent Redirect und dann 200 OK, und deinen Datensatz mit url: 'http://books.test/books/2'. /books/99 gibt deinen Datensatz mit status: 404 und dem Fehler-Body zurück. Dann sendet das Hilfsprogramm sein eigenes urllib-POST /v0/books, und der letzte Block lautet:
--- pl_http's own urllib POST /v0/books ---
raised HTTPError: HTTP Error 308: Permanent Redirect
Das ist urllib, das sich weigert, einen Body durch eine 308 erneut zu senden. Drück dann Check my work. Die nächste Lektion sendet Schreibzugriffe stattdessen über deine eigene Folgefunktion und beweist, dass sie und dein Server sich einig sind.
Aufgabe
Schreib open_url(url, *, opener=None, timeout=TIMEOUT) in wire.py.
Wenn
openerNoneist, bau einen miturllib.request.build_opener(). Wenn ein Opener übergeben wird, verwende ihn und bau keinen.Sende die Anfrage mit
opener.open(url, timeout=timeout)in einerwith-Anweisung und gib einmit "status"(response.status),"url"(response.geturl(), die endgültigenach ) und "body"(response.read(), innerhalb des Blocks gelesen) zurück.Fang
urllib.error.HTTPErrorab, und nur das: Lies seinen Body mitexc.read(), rufexc.close()auf und gib dieselben drei Schlüssel ausexc.code,exc.geturl()und dem Body zurück.Lass jede andere
durch, etwa ein URLErrorfür eine abgelehnte Verbindung oder einTimeoutError.Lass
import urllib.errorundimport urllib.request, wie sie sind, und schreiburllib.error.HTTPErrorundurllib.request.build_openerüber das, ohne Zeile from urllib.error importoderfrom urllib.request import.
Führ python pl_http.py urllib im POST durch eine 308 erneut zu senden.
Drück dann Check my work. Es übergibt open_url einen Ersatz-Opener und setzt über das Modul Ersatzobjekte für urllib.request.build_opener und urllib.error ein. Es prüft, dass ein gefolgtes GET den endgültigen Status, die URL und den Body zurückgibt und dass die Antwort geschlossen wurde; dass eine 404 und eine 503 als Datensätze mit ihren Bodys zurückkommen und geschlossen werden; dass ein TimeoutError, ein URLError und ein anderer OSError alle open_url verlassen; dass der Timeout, standardmäßig 2.0, open erreicht; und dass build_opener nur aufgerufen wird, wenn kein Opener übergeben wird. Nichts aus deinem Terminal wird bewertet.