0%

Der Standard-Client und die Statusklassen · Übung

Ein Timeout ist kein 504

Jede Antwort, die ankommt, hat jetzt eine Klasse und einen Body. In dieser Lektion geht es um die Anfrage, die keine Antwort bekommt. In Python in der Praxis haben ein Timeout und ein 504 Gateway Timeout vielleicht wie derselbe Fehlschlag ausgesehen: Etwas war langsam, und der Aufruf hatte keinen Erfolg. Es sind verschiedene Ereignisse, entschieden von verschiedenen Programmen, und ein Client, der das eine als das andere meldet, schickt jeden, der seine Ausgabe liest, an der falschen Stelle auf die Suche.

Zwei Szenarien, die sich Zeit lassen

Zwei Routen von practice-api warten, bevor sie antworten. /v1/scenarios/slow?delay_ms=250 wartet 250 Millisekunden und sendet dann ein 200. /v1/scenarios/timeout?delay_ms=750 wartet 750 Millisekunden und sendet dann ein 504 Gateway Timeout, den Status, den ein Server sendet, wenn etwas, auf das er gewartet hat, nicht rechtzeitig geantwortet hat. Hier spielt der Dienst diese Rolle absichtlich.

Probiere zuerst das langsame, mit einem Timeout von einer Sekunde:

python -c "import wire; print(wire.get_json('/v1/scenarios/slow?delay_ms=250', timeout=1.0)['status'])"

Es gibt nach einer kurzen Pause 200 aus. Eine Viertelsekunde passt in eine Sekunde, also war das Warten nichts weiter als ein Warten.

Jetzt das Timeout-Szenario, zweimal, mit zwei verschiedenen Timeouts:

python -c "import wire; print(wire.get_json('/v1/scenarios/timeout?delay_ms=750', timeout=2.0)['status'])"
python -c "import wire; print(wire.get_json('/v1/scenarios/timeout?delay_ms=750', timeout=0.5)['status'])"

Mit zwei Sekunden Wartezeit empfängt dein Client das 504 und gibt 504 aus. Mit einer halben Sekunde gibt er auf, bevor der Server antwortet: Der zweite Befehl endet mit einem , dessen letzte Zeile TimeoutError: timed out ist. Dieselbe Anfrage, derselbe Server; nur dein Timeout hat sich geändert. Wenn auch der erste Befehl TimeoutError auslöst, hat die Antwort länger als zwei Sekunden gebraucht, um zu deiner Sandbox zurückzukommen; der Punkt unten hängt nicht davon ab.

Wer hat entschieden

Die beiden Läufe sehen auf einer Zeitleiste so aus. Vergleiche, wo jede Linie endet und wer sie beendet hat:

Mit timeout=0.5 gibt der Client nach 0,5 Sekunden auf, und es gibt keine Antwort; mit timeout=2.0 kommt nach 0,75 Sekunden ein vollständiges 504 an.

Mit timeout=0.5 vergeht eine halbe Sekunde ohne Kopf auf der Verbindung, und http.client löst in getresponse TimeoutError aus. Es gibt keinen Status, keine Header und keinen Body; dein Client hat beschlossen aufzuhören. Mit timeout=2.0 kommt die Antwort des Servers nach einer Dreiviertelsekunde an. Sie ist eine vollständige Antwort wie jede andere, mit Statuszeile, Headern und einem -Body, und der Server hat sich entschieden, sie zu senden. Dein get_json gibt sie mit "class": "server error" zurück.

Die Frage “wer hat aufgegeben?” hat also in jedem Fall eine andere Antwort. Ein 504 bedeutet, dass ein Server bei etwas weiter hinten in der Kette aufgegeben und es dir gesagt hat. Ein TimeoutError bedeutet, dass dein Client beim Server aufgegeben hat, und der Server arbeitet vielleicht noch an der Anfrage. Das Erste ist eine Tatsache über den Server; das Zweite ist eine Tatsache über dein Timeout.

Mit timeout=0.5 löst ein GET von /v1/scenarios/timeout?delay_ms=750 TimeoutError aus. Wer hat aufgegeben?

report: sag, was davon passiert ist

report(target, *, make_connection, timeout) gibt eine Zeile zurück, die sagt, was mit einem GET von target passiert ist:

  • wenn get_json einen Datensatz zurückgibt, den Status und seine Klasse, wie in 504 server error oder 200 success;

  • wenn get_json TimeoutError auslöst, eine Zeile, die sagt, dass beim Client das Timeout abgelaufen ist und wie lange er gewartet hat, und die keinen Status nennt, weil keiner angekommen ist.

Das try steht um den Aufruf von get_json, und das except nennt nur TimeoutError. Das ist die enge Grenze aus Python in der Praxis: Fang nur die , deren Bedeutung du kennst und auf die du eine Antwort hast. Zwei weitere Möglichkeiten sind verlockend und hier falsch:

  • except OSError fängt auch ConnectionRefusedError, weil beide Arten von OSError sind. Eine abgelehnte Verbindung würde dann als Timeout gemeldet, und jemand würde nach einem langsamen Server suchen, wo gar kein Server gelauscht hat.

  • except Exception fängt auch den ValueError, den get_json für einen als JSON ausgezeichneten Body auslöst, der kein JSON ist, und würde eine kaputte Antwort als Timeout melden.

Beide Fehlschläge sollten report als Ausnahmen verlassen, damit jeder, der es aufgerufen hat, sieht, was wirklich passiert ist.

get_json selbst braucht keine Änderung. Seit Lektion 1 verlässt TimeoutError es durch den Block with contextlib.closing(...), der die Verbindung auf dem Weg hinaus schließt, und report ist die einzige Stelle, die daraus eine Zeile macht. Fang es nicht innerhalb von get_json: Es gibt keine Antwort, die in einen Datensatz käme.

Deine Aufgabe

  1. Schreibe in wire.py die report(target, *, make_connection, timeout) wie oben beschrieben. Reiche make_connection und timeout an get_json weiter.

  2. Führe es auf dem Timeout-Szenario mit beiden Timeouts aus:

python -c "import wire; print(wire.report('/v1/scenarios/timeout?delay_ms=750', timeout=0.5))"
python -c "import wire; print(wire.report('/v1/scenarios/timeout?delay_ms=750'))"

Die erste Zeile sagt in deinen eigenen Worten, dass beim Client nach 0,5 Sekunden das Timeout abgelaufen ist; die zweite lautet 504 server error. Wenn auch die zweite sagt, dass das Timeout deines Clients abgelaufen ist, hat das 504 länger als zwei Sekunden gebraucht, um deine Sandbox zu erreichen, wie im früheren Lauf; report hat beide Male die Wahrheit gesagt.

Drücke dann Check my work. Dein Client hat jetzt eine Antwort auf jede Antwort und auf keine Antwort. Die nächste Lektion prüft all das mit deinen eigenen Tests.

Aufgabe

Schreibe report(target, *, make_connection, timeout) in wire.py. Die ruft get_json(target, make_connection=make_connection, timeout=timeout) auf und gibt eine Textzeile zurück:

  • wenn eine Antwort ankommt, den Status und seine Klasse, beginnend mit dem Status, wie in 504 server error;

  • wenn get_json TimeoutError auslöst, eine Zeile mit den Wörtern timed out und dem Timeout, das gewartet wurde, etwa 0.5, und ohne .

Fang nur TimeoutError. Ein ConnectionRefusedError oder der aus einem als ausgezeichneten Body, der kein JSON ist, muss report als Ausnahme verlassen. get_json lässt TimeoutError weiterhin durch, mit geschlossener Verbindung.

Führe report auf /v1/scenarios/timeout?delay_ms=750 mit timeout=0.5 und mit dem Standard-Timeout aus, wie die Lektion es zeigt.

Drücke dann Check my work. Die Prüfung ruft report und get_json mit einem Ersatz für make_connection auf, der ein geskriptetes 504 oder 200 zurückgibt, für einen stillen Server in getresponse() TimeoutError auslöst, in request() ConnectionRefusedError auslöst oder einen als JSON ausgezeichneten Body zurückgibt, der kein JSON ist. Sie prüft jede Zeile, dass das Timeout bei der Verbindung angekommen ist, dass die Verbindung nach dem Timeout geschlossen wurde und dass die anderen beiden Fehlschläge nicht gefangen wurden. Deine Formulierung um timed out herum ist deine Sache. Nichts aus deinem wird bewertet, und keine Prüfung misst Zeit.