0%

De standaardclient en statusklassen · oefening

Een time-out is geen 504

Elk antwoord dat aankomt, heeft nu een klasse en een body. Deze les gaat over het verzoek dat geen antwoord krijgt. In Python in Practice leken een time-out en een 504 Gateway Timeout misschien dezelfde mislukking: iets was traag, en de aanroep slaagde niet. Het zijn verschillende gebeurtenissen, beslist door verschillende programma’s, en een client die de ene meldt als de andere, stuurt wie zijn uitvoer leest op de verkeerde plek zoeken.

Twee scenario’s die de tijd nemen

Twee routes van practice-api wachten voordat ze antwoorden. /v1/scenarios/slow?delay_ms=250 wacht 250 milliseconden en stuurt dan een 200. /v1/scenarios/timeout?delay_ms=750 wacht 750 milliseconden en stuurt dan een 504 Gateway Timeout, de status die een server stuurt als iets waarop hij wachtte niet op tijd antwoordde. Hier speelt de dienst die rol met opzet.

Probeer eerst de trage, met een time-out van één seconde:

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

Het drukt na een korte pauze 200 af. Een kwart seconde past binnen één seconde, dus het wachten was niets meer dan wachten.

Nu het time-outscenario, twee keer, met twee verschillende time-outs:

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'])"

Met twee seconden wachttijd ontvangt je client de 504 en drukt hij 504 af. Met een halve seconde geeft hij het op voordat de server antwoordt: het tweede commando eindigt met een waarvan de laatste regel TimeoutError: timed out is. Hetzelfde verzoek, dezelfde server; alleen je time-out veranderde. Werpt het eerste commando ook TimeoutError op, dan duurde het langer dan twee seconden voordat het antwoord terug was in je sandbox; het punt hieronder hangt daar niet van af.

Wie besliste

Op een tijdlijn zien de twee uitvoeringen er zo uit. Vergelijk waar elke lijn eindigt, en wie haar beëindigde:

Met timeout=0.5 geeft de client het op na 0,5 seconde en bestaat er geen antwoord; met timeout=2.0 komt er na 0,75 seconde een complete 504 aan.

Met timeout=0.5 gaat er een halve seconde voorbij zonder kop op de verbinding, en werpt http.client TimeoutError op vanuit getresponse. Er bestaan geen status, geen headers en geen body; je client besloot te stoppen. Met timeout=2.0 komt het antwoord van de server na driekwart seconde aan. Het is een compleet antwoord als elk ander, met een statusregel, headers en een -body, en de server koos ervoor het te sturen. Je get_json geeft het terug met "class": "server error".

De vraag “wie gaf het op?” heeft dus in elk geval een ander antwoord. Een 504 betekent dat een server het opgaf met iets verderop en je dat vertelde. Een TimeoutError betekent dat je client het opgaf met de server, en de server kan nog steeds met het verzoek bezig zijn. Het eerste is een feit over de server; het tweede is een feit over je time-out.

Met timeout=0.5 werpt een GET van /v1/scenarios/timeout?delay_ms=750 TimeoutError op. Wie gaf het op?

report: zeg welke van de twee er gebeurde

report(target, *, make_connection, timeout) geeft één regel terug die zegt wat er gebeurde met een GET van target:

  • als get_json een record teruggeeft, de status en zijn klasse, zoals in 504 server error of 200 success;

  • als get_json TimeoutError opwerpt, een regel die zegt dat de client een time-out kreeg en hoe lang hij wachtte, en die geen status noemt, omdat er geen aankwam.

De try staat rond de aanroep van get_json, en de except noemt alleen TimeoutError. Dat is de smalle grens uit Python in Practice: vang alleen de waarvan je de betekenis kent en waarop je een antwoord hebt. Twee bredere keuzes zijn verleidelijk en hier fout:

  • except OSError vangt ook ConnectionRefusedError, omdat beide soorten OSError zijn. Een geweigerde verbinding zou dan als time-out worden gemeld, en dan gaat iemand op zoek naar een trage server terwijl er geen server luisterde.

  • except Exception vangt ook de ValueError die get_json opwerpt voor een body die als JSON gelabeld is maar geen JSON is, en zou een kapot antwoord als time-out melden.

Beide mislukkingen moeten report als exception verlaten, zodat wie het aanriep ziet wat er echt gebeurde.

get_json zelf hoeft niet te veranderen. Sinds les 1 verlaat TimeoutError het via het blok with contextlib.closing(...), dat de verbinding onderweg naar buiten sluit, en report is de enige plek die er een regel van maakt. Vang hem niet binnen get_json: er is geen antwoord om in een record te zetten.

Je opdracht

  1. Schrijf in wire.py de report(target, *, make_connection, timeout) zoals hierboven beschreven. Geef make_connection en timeout door aan get_json.

  2. Voer het uit op het time-outscenario met beide time-outs:

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'))"

De eerste regel zegt in je eigen woorden dat je client na 0,5 seconde een time-out kreeg; de tweede luidt 504 server error. Zegt ook de tweede dat je client een time-out kreeg, dan duurde het langer dan twee seconden voordat de 504 je sandbox bereikte, zoals bij de eerdere uitvoering; report sprak beide keren de waarheid.

Druk daarna op Check my work. Je client heeft nu een antwoord op elk antwoord en op geen antwoord. De volgende les bewijst het allemaal met je eigen tests.

Opdracht

Schrijf report(target, *, make_connection, timeout) in wire.py. Het roept get_json(target, make_connection=make_connection, timeout=timeout) aan en geeft één regel tekst terug:

  • als er een antwoord aankomt, de status en zijn klasse, beginnend met de status, zoals in 504 server error;

  • als get_json TimeoutError opwerpt, een regel met de woorden timed out en de time-out die het wachtte, zoals 0.5, en zonder .

Vang alleen TimeoutError. Een ConnectionRefusedError, of de van een body die als gelabeld is maar geen JSON is, moet report als exception verlaten. get_json blijft TimeoutError doorlaten, met de verbinding gesloten.

Voer report uit op /v1/scenarios/timeout?delay_ms=750 met timeout=0.5 en met de standaardtime-out, zoals de les laat zien.

Druk daarna op Check my work. Die roept report en get_json aan met een vervanger voor make_connection die een gescripte 504 of 200 teruggeeft, TimeoutError opwerpt vanuit getresponse() voor een stille server, ConnectionRefusedError opwerpt vanuit request(), of een body teruggeeft die als JSON gelabeld is maar geen JSON is. Ze controleert elke regel, dat de time-out de verbinding bereikte, dat de verbinding na de time-out gesloten werd, en dat de andere twee mislukkingen niet gevangen werden. De formulering rond timed out bepaal je zelf. Niets uit je wordt beoordeeld, en geen enkele controle meet tijd.