requests und httpx, jetzt, wo du Bescheid weißt · Übung
Was jeder Client ungefragt sendet
In Kapitel 2 hast du jede Zeile einer Anfrage selbst geschrieben, und wire.py sendet immer noch genau das, was du ihm sagst: die Anfragezeile, eine Host-Zeile aus host_header, die Header, die der Aufrufer übergibt, und Content-Length, wenn es einen Body gibt. Sonst nichts. Die Bibliotheken sind großzügiger. Jede fügt Zeilen hinzu, um die du nie gebeten hast, und jetzt, wo du weißt, was jeder Header tut, kannst du sie lesen.
Vier Clients, ein Echo
Um diese Zeilen zu sehen, brauchst du einen Server, der meldet, was er empfangen hat. /v1/headers von practice-api meldet nur X-Practice-Name, wie du in Lektion 2.4 gesehen hast, also bringt pl_http sein eigenes mit: ein kleines Echo auf 127.0.0.1, das GET /echo mit jeder Header-Zeile der Anfrage beantwortet, als
Öffne side_by_side.py und lies seine letzten drei fetch_echoes(url) sendet mit jedem von vier Clients ein GET an das Echo, mit wire.fetch, einer bloßen http.client.HTTPConnection, einer requests-Session und einem httpx-Client, und jeder fragt nach genau einem Header, X-Asked: yes. echo_headers macht aus jeder Antwort ein main(argv) nimmt die fetch_echoes auf und gibt für jeden Client die Namen aus, die dein unasked findet. pl_http übergibt die URL, weil das Echo nur innerhalb seines Laufs existiert:
python pl_http.py echo
Bevor du unasked schreibst, zeigt der Lauf trotzdem die vier Anfragen so, wie das Echo sie empfangen hat, und hält dann dort an, wo main deine Funktion aufruft: raised NotImplementedError: not written yet: Lesson 5. pl_http schreibt PORT dort, wo die
request 1 GET /echo (connection 1)
Host: 127.0.0.1:PORT
X-Asked: yes
request 2 GET /echo (connection 2)
Host: 127.0.0.1:PORT
Accept-Encoding: identity
X-Asked: yes
request 3 GET /echo (connection 3)
Host: 127.0.0.1:PORT
User-Agent: python-requests/VERSION
Accept-Encoding: gzip, deflate, zstd
Accept: */*
Connection: keep-alive
X-Asked: yes
request 4 GET /echo (connection 4)
Host: 127.0.0.1:PORT
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: python-httpx/VERSION
X-Asked: yes
VERSION steht für die Version der hier installierten Bibliothek. Anfrage 1 ist wire.py, Anfrage 2 ist http.client, Anfrage 3 ist requests und Anfrage 4 ist httpx. wire.py hat eine Zeile hinzugefügt, http.client zwei und jede Bibliothek fünf, jede in ihrer eigenen Reihenfolge.
Wofür jede Zeile da ist
Host. Jeder Client sendet ihn, weil wire.py schreibt ihn mit host_header; http.client schreibt ihn aus dem Host und Port, die es bekommen hat, und requests überlässt ihn dem http.client darunter; httpx schreibt ihn selbst.
User-Agent. Name und Version des Clients. Ein Server kann ihn loggen, zählen, welche Clients ihn aufrufen, oder einen bekannten Fehler in einer Version umgehen. Dein Server ignoriert ihn. Nur die beiden Bibliotheken senden einen; http.client und wire.py lassen ihn weg.
Warum enthält User-Agent eine Versionsnummer?
Accept. Welche Medientypen der Client annimmt. */* bedeutet jeden. Dein make_session aus Lektion 1 hat ihn für den Leselisten-Client durch application/json ersetzt.
Accept-Encoding. Ein Angebot: “Du darfst den Body mit einem dieser Formate komprimieren, und ich mache es rückgängig.” gzip und deflate sind verbreitete Kompressionsformate; zstd ist ein neueres, das requests unter Python 3.14 anbietet, weil dieses Python es decodieren kann (auf einem älteren Python endet die deflate). Ein Server, der das Angebot annimmt, sendet einen komprimierten Body mit einem content-encoding-Header, der das Format nennt, und requests oder httpx dekomprimieren ihn, bevor .content oder .json() ihn dir gibt. http.client bietet identity an, was “bitte keine Kompression” bedeutet, weil es nicht dekomprimiert. wire.py sendet überhaupt keine Accept-Encoding-Zeile. HTTP liest das als “jede Codierung ist in Ordnung”, also könnte ein Server trotzdem komprimieren, und wire.py würde dir die komprimierten Bytes geben; Server komprimieren aber meist nur für einen Client, der es angeboten hat. Dein Server komprimiert nie, also ändert nichts davon seine Antworten.
requests sendet accept-encoding: gzip, deflate, zstd. Was erlaubt das dem Server?
Connection. Ob der Client die Verbindung für eine weitere Anfrage behalten möchte, die Frage aus Kapitel 9. Eine requests-Session und ein httpx-Client halten einen Pool, also sagen sie keep-alive laut. http.client und wire.py sagen nichts, und unter HTTP/1.1 bedeutet Schweigen dasselbe: Die Verbindung darf offen bleiben. In diesem Lauf öffnet jeder Client eine eigene Verbindung, und das Echo schließt jede nach seiner Antwort, also zeigt jede Anfrage eine neue Verbindungsnummer.
http.client sendet keine Connection-Zeile; requests sendet Connection: keep-alive. Was darf der Server über die Verbindung der http.client-Anfrage annehmen?
Header-Namen ignorieren Groß- und Kleinschreibung, also sind Accept und accept derselbe Header, und ein Client darf sie in beliebiger Reihenfolge senden. Um zu vergleichen, was jeder Client hinzugefügt hat, vergleichst du Namen also in einer einheitlichen Schreibweise und ignorierst ihre Reihenfolge. Das ist die Funktion, die du schreibst.
Deine Aufgabe
Schreibe in side_by_side.py unasked(received, asked). received ist etwas, dessen echo_headers zurückgibt; asked ist eine Liste der Namen, die du selbst gesendet hast. Gib eine Liste der Namen in received zurück, die nicht in asked stehen, jeweils kleingeschrieben, in der Reihenfolge, in der received sie liefert. Vergleiche in einer einheitlichen Schreibweise: Schreibe jeden Namen in asked und jeden Namen in received klein, bevor du vergleichst. Ein Name in asked, der nie angekommen ist, ist nichts, was der Client hinzugefügt hat, also erscheint er nie im Ergebnis.
Eine Schleife über asked, die eine Liste kleingeschriebener Namen baut, und dann eine Schleife über received, die die behält, die nicht darin stehen, genügt. Führe dann noch einmal python pl_http.py echo aus. Nach denselben vier Anfragen gibt main aus:
standard output:
wire.py added: host
http.client added: host, accept-encoding
requests added: host, user-agent, accept-encoding, accept, connection
httpx added: host, accept, accept-encoding, connection, user-agent
Der Ergebnisblock endet mit the echo answered 4 requests, one per connection, and is closed.
Klicke auf Check my work. Lektion 6 lässt requests in einem echten Programm arbeiten: dem Leselisten-Client.
Aufgabe
Finde die Header-Namen, die ein Client von sich aus hinzugefügt hat.
Schreibe in side_by_side.py unasked(received, asked):
receivedliefert Header-Namen, wenn du darüber iterierst, etwa das, das echo_headerszurückgibt.askedist eineder Namen, die du selbst gesendet hast. Gib eine Liste der Namen in
receivedzurück, die nicht inaskedstehen, jeweils kleingeschrieben, in der Reihenfolge, in derreceivedsie liefert.Ignoriere Groß- und Kleinschreibung auf beiden Seiten:
X-Traceinreceivedpasst zux-TRACEinasked.Ein Name in
asked, der nicht inreceivedsteht, erscheint nie im Ergebnis.
Gib die Namen zurück, nicht die
Im python pl_http.py echo ein kleines Echo auf 127.0.0.1 und führt das main() deiner side_by_side.py mit dessen main gibt aus, was laut deinem unasked jeder Client hinzugefügt hat. Der Lauf zeigt die vier Anfragen schon, bevor unasked existiert.
Klicke dann auf Check my work. Es ruft unasked mit Header-Dictionaries auf, die du noch nicht gesehen hast, mit Namen in gemischter Schreibweise auf beiden Seiten, einem leeren asked und einem erfragten Namen, der nie angekommen ist. Während es läuft, weigern sich requests, httpx, http und wire in side_by_side, irgendetwas zu senden, sodass keine Anfrage die Prüfung verlässt. Nichts aus deinem Terminal wird bewertet.