0%

HTTP selbst ausliefern · Übung

HEAD wie GET beantworten

In Kapitel 1 hat dein read_body nach einer Anfrage HEAD b"" zurückgegeben, ohne etwas zu lesen, weil eine Antwort auf HEAD keinen Body hat, auch wenn ihr Kopf eine Länge ankündigt. Da hat der Client dem Server vertraut. Jetzt bist du der Server, und dein handle beantwortet HEAD mit 405, weil Lektion 4 nur GET erlaubt hat. Führe das Szenario für diese Lektion aus, ein GET /books und danach ein HEAD /books:

python pl_http.py head

Das GET bekommt die . Das HEAD bekommt 405 Method Not Allowed mit allow: GET, und unter dieser Antwort gibt pl_http eine weitere Zeile aus, die die Bytes zählt, die followed the reply, und sie zeigt: deinen Fehler-Body. pl_http liest, wie jeder Client, nach HEAD keinen Body, also sind diese Bytes überzählig; sie sind angekommen, weil nichts zwischen deinem handle und dem einen Body entfernt. Der Adapter schreibt, was auch immer dein Antwort- enthält, und http.server sendet es, auch nach HEAD. Nach HEAD keinen Body zu senden ist deine Aufgabe.

Eine Regel am Ende von handle

HEAD fragt nach genau dem, was GET bekommen würde, ohne den Body: derselbe Status und dieselben Header. Ein Client benutzt es, um zu sehen, ob etwas existiert oder wie groß es ist, ohne es herunterzuladen. HEAD /books ist also ein 200 mit den Headern der Liste, und HEAD /nowhere ist ein 404 mit den Headern der Fehlerantwort.

Am einfachsten bekommst du all das richtig hin mit einer Regel am Ende von handle, nach dem Routing. Route HEAD genau wie GET, halte die Antwort in einer , statt aus jedem Zweig zurückzukehren, und leere den Body direkt vor dem einen :

    if method == "HEAD":
        response["body"] = b""
    return response

Jeder Zweig darüber, die Liste, das 404 und das 405, beantwortet HEAD dann so, wie er GET beantwortet, ohne den Body. Eine Regel, die nur im Zweig für /books steht, würde nach HEAD /nowhere immer noch den Body des 404 senden.

Warum steht die HEAD-Regel nach dem Routing statt im Zweig für /books?

Die Länge beschreibt weiterhin GET

Die Regel leert den Body und lässt die Header in Ruhe, also bleibt content-length, was es war: die Länge des Bodys, den GET senden würde. Die Definition von HEAD in (RFC 9110, Abschnitt 9.3.2) verlangt von einem Server, die Headerfelder zu senden, die er für GET gesendet hätte, und erlaubt ihm, Felder wegzulassen, deren er erst beim Erzeugen des Bodys erfährt. Ein Server darf content-length bei einer Antwort auf HEAD also senden oder weglassen, aber wenn er es sendet, muss der Wert die Länge sein, die GET senden würde (Abschnitt 8.6). Die Regel dieses Servers, festgehalten in SERVICE_CONTRACT.md, ist, es zu senden, weil ein Client so die Größe ohne den Download erfährt. Eine Länge von 0 würde behaupten, dass die Liste leer ist.

Die Client-Seite ist schon sicher. http.client liest nach HEAD keinen Body, egal was die Länge sagt, und dein wire.py hat das auch nicht getan. Ein trotzdem gesendeter Body bleibt ungelesen in der Verbindung. Unter HTTP/1.0 schließt der Server nach einer Antwort, und die verirrten Bytes verschwinden mit der Verbindung. Ab Kapitel 9 trägt eine Verbindung mehrere Anfragen, und diese Bytes würden als Anfang der nächsten Antwort gelesen.

GET /books sendet 132 Body-Bytes. Welches content-length trägt HEAD /books auf diesem Server?

Deine Aufgabe

  1. Setze am Anfang von handle method = request["method"]. Route dann HEAD so, wie du GET routest: Auf /books antwortet method == "GET" or method == "HEAD" mit der Liste.

  2. Halte die Antwort jedes Zweigs in einer Variablen, response, und gib sie einmal zurück, am Ende.

  3. Setze direkt vor diesem return response["body"] = b"", wenn die HEAD ist.

  4. Ändere das 405 zu not_allowed("GET, HEAD"), weil /books jetzt beides unterstützt.

Führe python pl_http.py head noch einmal aus und vergleiche die beiden Zeilen server -> client. Die Statuszeile und jeder Header sind in beiden gleich, einschließlich content-length: 132. Die Antwort auf GET geht mit dem Body weiter; die Antwort auf HEAD hört bei der Leerzeile auf, und keine Zeile folgt ihr.

Drücke dann Check my work. Dein Server kann die Bücher auflisten und HEAD beantworten, aber noch kein einzelnes Buch zeigen. Die Projektlektion fügt /books/{id} hinzu.

Aufgabe

Erweitere handle in reading_server.py, sodass HEAD genau das bekommt, was GET bekommen würde, ohne den Body.

  • Route HEAD wie GET: Auf /books antworten GET und HEAD beide mit der .

  • Halte die Antwort jedes Zweigs in einer und gib sie einmal zurück, am Ende von handle. Setze direkt vor diesem den Body auf b"", wenn die HEAD ist, damit die Regel jeden Pfad abdeckt, auch das 404 und das 405.

  • Lass die Header in Ruhe: content-length in einer Antwort auf HEAD ist die Länge des Bodys, den GET senden würde.

  • Beantworte andere Methoden auf /books mit not_allowed("GET, HEAD").

Führe im python pl_http.py head aus und vergleiche die Antworten auf GET und HEAD.

Drücke dann Check my work. Die Prüfung ruft handle mit eigenen States auf und vergleicht jede Antwort auf HEAD mit der Antwort auf GET für dasselbe Ziel: für /books, für ein kodiertes /books mit einer Query, für eine leere Liste und für Pfade, die mit 404 antworten. Jede Antwort auf HEAD muss denselben Status und dieselben Header haben wie die Antwort auf GET, mit content-length gleich der Länge des Bodys von GET, und einen leeren Body. Antworten auf GET müssen ihre Bodys behalten, und DELETE, PUT und POST auf /books müssen mit 405 antworten, mit einem Header allow, der sowohl GET als auch HEAD nennt. Nichts aus deinem Terminal wird bewertet, und kein Server wird gestartet.