0%

Met een API communiceren · oefening

Begrens elke wachttijd

Deze manier van mislukken verrast mensen vaak de eerste keer: een programma dat niet crasht, geen foutmelding geeft en niet klaar is. Het doet gewoon niets meer. Iets aan de andere kant van het netwerk stopte met antwoorden en niets vertelde je code dat het moest ophouden met wachten.

Een time-out is de instructie om te stoppen met wachten. Het is geen geavanceerde optie die je pas gebruikt nadat het een keer misging. Die hoort bij elk verzoek dat je ooit schrijft.

Twee wachttijden, niet één

Requests wil een paar en de twee delen betekenen iets anders:

DEFAULT_TIMEOUT = (1.0, 1.0)

Het eerste getal begrenst de connect-fase: hoe lang je wacht tot de dienst überhaupt de verbinding aanneemt. Het tweede begrenst de read-fase: hoe lang je tussen delen van het antwoord wacht zodra het gesprek is begonnen.

Die scheiding is nuttig omdat de twee fouten iets anders betekenen. Een dienst die de verbinding nooit aanneemt, is waarschijnlijk uitgevallen of onbereikbaar. Een dienst die de verbinding aanneemt en daarna stilvalt, draait waarschijnlijk wel maar heeft het moeilijk.

Zie hoe dezelfde vertraging twee uitkomsten oplevert

De oefen-API heeft een route die bewust de tijd neemt. Vraag die om 250 milliseconden te wachten, met een leesgrens van een volle seconde:

request_json(
    "/scenarios/slow",
    params={"delay_ms": 250},
    timeout=(1.0, 1.0),
)

Het lukt. De vertraging valt binnen de grens, dus er gebeurt niets bijzonders.

De tweede route is /scenarios/timeout met params={"delay_ms": 750}, zoals vastgelegd in API_CONTRACT.md. Voer die één keer uit met elk van de onderstaande time-outs. De exceptie geeft aan welke kant als eerste ophoudt met wachten:

import requests
from api_catalog import request_json

for timeout in [(1.0, 1.5), (1.0, 0.2)]:
    try:
        request_json(
            "/scenarios/timeout",
            params={"delay_ms": 750},
            timeout=timeout,
        )
    except requests.HTTPError as exc:
        print("HTTP response:", exc.response.status_code)
    except requests.ReadTimeout:
        print("Client stopped waiting; no response arrived.")

Dit zijn twee afzonderlijke onderzoeken, geen beleid voor opnieuw proberen. Houd de exceptieafhandeling in deze onderzoekscode; request_json vangt nog steeds niets op.

Met een ruime leesgrens van (1.0, 1.5) komt de server ver genoeg om zelf een antwoord te sturen: een bewuste HTTP 504. Dat is een antwoord. Het is aangekomen. raise_for_status() maakt er een HTTPError van. Je kunt exc.response.status_code onderzoeken en er 504 uit lezen.

Met een krappe leesgrens van (1.0, 0.2) geeft je client als eerste op. Requests werpt requests.ReadTimeout op. Er is geen antwoordobject, geen statuscode, niets om te onderzoeken. Je programma stopte met luisteren voordat er iets terugkwam.

Dezelfde trage dienst, twee wezenlijk verschillende gebeurtenissen:

Wat gebeurde er?ExceptieIs er een status?
De server antwoordde langzaam met een foutrequests.HTTPErrorJa, 504
De client stopte als eerste met wachtenrequests.ReadTimeoutNee

Het onderscheid is belangrijk omdat de twee verschillende vervolgstappen suggereren. In dit gecontroleerde scenario betekent een 504 dat een HTTP-foutantwoord via de serverkant is aangekomen. Een ReadTimeout betekent dat de client als eerste stopte met wachten en zegt niets met zekerheid over wat de server uiteindelijk deed.

Leid het niet af uit de klok

Er zijn twee regels voor request_json, allebei over terughoudendheid.

Vang hier niets op. Deze functie doet een verzoek en geeft een gedecodeerde inhoud terug. Ze weet niet of de aanroeper een melding wil afdrukken, met een status wil afsluiten of op een cache wil terugvallen. Dat hier beslissen zou het beleid aan elke toekomstige aanroeper opleggen.

Leid de uitkomst bij de grens niet af uit de verstreken tijd. Het is verleidelijk om te meten hoe lang de aanroep duurde en terug te redeneren. De exceptieklasse onderscheidt een ontvangen HTTP-foutantwoord van een time-out aan de clientkant. Alleen de verstreken kloktijd kan dat op een trage computer of een drukke dag niet betrouwbaar doen.

Geef de time-outtuple van de aanroeper ongewijzigd door en laat de exceptie de betekenis overbrengen.

Wat vertelt een ReadTimeout aan de clientkant je?

Waarom stuurt request_json bij elke aanroep een time-out mee in plaats van alleen bij routes waarvan bekend is dat ze traag zijn?

Opdracht

Houd DEFAULT_TIMEOUT precies op (1.0, 1.0) en zorg dat elk verzoek zijn time-out expliciet doorgeeft. Behoud een meegegeven tuple (1.0, 1.5) of (1.0, 0.2) ongewijzigd, houd het bij één aanroep en laat HTTP- en time-outexcepties verdergaan.