Doorverwijzingen · oefening
Volg doorverwijzingen met een hoplimiet
Je server beantwoordt GET /v0/books/2?x=1 nu met 308 Permanent Redirect en location: /books/2?x=1, en http.client geeft dat antwoord terug en stopt. Niets in follow, in wire.py, opgebouwd uit drie kleine
Eén verzoek
request_once(method, url, *, body, headers, make_connection, timeout) stuurt één verzoek en geeft één record terug. Je hebt hem al eens geschreven: het is get_json uit hoofdstuk 3, les 1, met elke methode, elke GET naar practice-api.
url_parts(url)uit les 2 geeft de host, deen het target. with contextlib.closing(make_connection(host, port, timeout=timeout)) as conn:maakt het verbindingsobject en sluit het op elk pad, ook alsgetresponse()eenTimeoutErroropwerpt.conn.request(method, target, body=body, headers=headers)stuurt het verzoek.bodyisNonevoor een verzoek zonder body.Bouw binnen het blok de header-
uit response.getheaders()met namen in kleine letters en, precies zoals get_jsondoet, en lees de body.
Het record heeft "status", "reason", "headers", "body", en één nieuwe sleutel, "url": de URL waar dit verzoek naartoe ging. Een volger heeft die nodig, omdat na een
De methode na een doorverwijzing
Les 1 deelde de vijf doorverwijsstatussen in naar wat ze met de methode doen. next_method(status, method) maakt van die tabel code en geeft de methode voor het volgende verzoek terug:
| Status | Volgende methode |
|---|---|
| 303 | GET, wat de methode ook was (RFC 9110 staat ook toe dat een HEAD behouden blijft) |
| 301, 302 | GET als de methode POST was; anders dezelfde methode |
| 307, 308 | dezelfde methode |
Als de methode verandert, gaat de body ook weg: een GET draagt er geen. Hetzelfde geldt voor de header content-type van het verzoek, die een body beschreef die niet meer wordt verstuurd. Als de methode blijft, gaan de body en elke header ongewijzigd naar het nieuwe adres. Dat is precies waar 307 en 308 voor bestaan.
follow("PUT", "http://books.test/v0/books/2", body=...) krijgt een 308 met location: /books/2. Wat stuurt hij daarna?
Stoppen
Een server kan een client in een cirkel sturen: /a verwijst door naar /b, en /b terug naar /a. Een volger die elke location vertrouwt, stuurt verzoeken tot er iets breekt. Daarom telt follow. Hij krijgt max_hops, het maximale aantal doorverwijzingen dat hij volgt, standaard 5 (MAX_HOPS). Een hop is één gevolgde doorverwijzing. Als er al max_hops doorverwijzingen zijn gevolgd en het antwoord is weer een doorverwijzing, werpt follow max_hops=3 tegen de cirkel stuurt hij vier verzoeken en werpt hij na het vierde een exception op:
| Verzoek | Verstuurd | Antwoord | Hops |
|---|---|---|---|
| 1 | GET /a | 307, location /b | hop 1 |
| 2 | GET /b | 307, location /a | hop 2 |
| 3 | GET /a | 307, location /b | hop 3 |
| 4 | GET /b | 307, location /a | een vierde hop zou over de limiet gaan: ValueError |
Wat drukt dit af?
moves = {"/a": "/b", "/b": "/a"}
path = "/a"
requests = 1
while path in moves and requests < 4:
path = moves[path]
requests = requests + 1
print(path, requests)
Alleen een antwoord dat een doorverwijzing is, telt. REDIRECTS, bovenaan wire.py, somt de vijf statussen op die follow volgt. Een antwoord met een andere status is definitief, en een 3xx zonder header location ook, want die geeft de client geen plek om heen te gaan. 304 Not Modified is ook een 3xx, maar staat niet in de lijst: het is een antwoord, geen verwijzing.
De sleutel history
follow geeft het laatste record terug met nog één sleutel, "history": een lijst met één dict per gevolgde doorverwijzing, elk met de "status" van de doorverwijzing en de "url" die hem stuurde. Een lege lijst betekent dat er geen doorverwijzing was. requests bewaart dezelfde informatie, als een lijst van de doorverwijsantwoorden, in response.history, wat hoofdstuk 10 laat zien.
Hier is de
Stuur het verzoek met
request_once.Als de status niet in
REDIRECTSstaat, of er geenlocationis, stop dan: dit record is het antwoord.Als
len(history)al gelijk is aanmax_hops, werp jeValueErrorop.Voeg
{"status": ..., "url": ...}voor dit antwoord toe aanhistory.Bepaal de methode met
next_method. Als die veranderd is, zet je de body opNoneen laat je decontent-typevan het verzoek weg uit de headers.Los de
locationop ten opzichte van de huidige URL metresolve_location, maak dat de huidige URL, en ga terug naar stap 1.
Een while True:-lus die bij stap 2 met break eindigt, past bij deze vorm. Oplossen ten opzichte van de huidige URL, niet de eerste, is wat de derde URL in de quiz van les 2 /books/3 maakte.
Echte clients doen nog één ding: ze laten inloggegevens zoals een header Authorization vallen, zodat een login die voor de ene server bedoeld is, nooit aan een andere wordt gegeven. RFC 9110 (sectie 15.4) vraagt meer dan bibliotheken meestal doen: het vraagt een client om Authorization en Cookie bij elke automatische doorverwijzing te laten vallen, terwijl bibliotheken zoals requests ze alleen laten vallen als de host verandert. Hoofdstuk 7 komt hierop terug.
Jouw opdracht
Schrijf in wire.py next_method, request_once en follow. follow maakt van headers=None een {} vóór het eerste verzoek, sluit elke verbinding via request_once, en geeft timeout aan elke verbinding mee. Voer daarna het hulpprogramma uit:
python pl_http.py follow
Het roept wire.follow("GET", "http://books.test/v0/books/2", make_connection=...) aan, waarbij de verbinding die het meegeeft naar je eigen server op deze machine gaat, dus books.test wordt nooit opgezocht. Elk verzoek en het record dat je follow teruggaf, worden afgedrukt:
request 1 GET /v0/books/2 -> 308 Permanent Redirect
request 2 GET /books/2 -> 200 OK
--- your client returned ---
status: 200
...
url: 'http://books.test/books/2'
history: [{'status': 308, 'url': 'http://books.test/v0/books/2'}]
De weggelaten regels zijn de reason, de headers en de body van boek 2.
Klik daarna op Check my work. Je client en je server zijn het nu eens over één doorverwijzing. De volgende les geeft dezelfde taak aan een bibliotheek en vergelijkt de regels daarvan met de jouwe.
Opdracht
Schrijf next_method, request_once en follow in wire.py.
next_method(status, method)geeft"GET"terug na een 303 voor elke, en na een 301 of 302 voor een POST; anders geeft hijmethodongewijzigd terug.request_once(method, url, *, body, headers, make_connection, timeout)splitsturlmeturl_parts, opent één verbinding metmake_connection(host, port, timeout=timeout)binnenwith contextlib.closing(...) as conn:, stuurtconn.request(method, target, body=body, headers=headers), en geeft eenterug met "status","reason","headers"(namen in kleine letters, elk met een), "body"(gelezen binnen het blok) en"url"(dedie hij kreeg). follow(method, url, *, body, headers, make_connection, max_hops, timeout)stuurt het verzoek metrequest_onceen doet, zolang de status van het antwoord inREDIRECTSstaat en het een headerlocationheeft, het volgende: werptop als hij al max_hopsheeft gevolgd; voegt {"status": ..., "url": ...}voor het antwoord toe aan een geschiedenislijst; gebruiktnext_methodvoor de volgende methode, en zet bij een veranderde methode de body opNoneen laat de headercontent-typevan het verzoek weg; lost delocationop ten opzichte van de huidige URL metresolve_location. Hij geeft het laatste record terug met"history"erbij.headers=Nonewordt{}.
Voer python pl_http.py follow uit in de follow tegen je eigen server aan het werk te zien.
Klik daarna op Check my work. Die roept next_method rechtstreeks aan, en daarna request_once en follow met een vervanger voor make_connection waarvan de verbindingen met vooraf vastgelegde antwoorden reageren, op hosts en PUT, een 303 en een 301 na een POST, een 301 na een PUT, een 307 na een POST, twee doorverwijzingen achter elkaar waarvan de tweede location relatief is aan de tweede URL, een absolute location op een andere host, precies max_hops doorverwijzingen, één meer dan dat, een location, een 304, een https-location, en een TimeoutError halverwege. Hij controleert bij elk verzoek de host, poort, time-out, methode, target, body en content-type, het record en de geschiedenis ervan, dat ValueError na precies max_hops + 1 verzoeken komt, en dat elke verbinding werd gesloten. Hij stopt een volger die meer dan 20 verbindingen opent. Niets uit je terminal wordt beoordeeld.