0%

HTTP selbst ausliefern · Übung

Ein paar Verbindungen bedienen, dann anhalten

handle gibt das richtige zurück, wenn du es aufrufst, aber noch hat es kein Client erreicht. In Networks with Python hast du die Serverseite von Hand geschrieben: make_listener hat einen gebunden, und serve hat Clients einen nach dem anderen angenommen, mit einem Timeout, bis es max_connections davon bedient hatte. http.server.HTTPServer ist dieser Listener und dieser Annahmeschritt in einem . Jedes Mal, wenn es eine Verbindung annimmt, erzeugt es dafür einen Request-Handler aus einer Klasse, die du ihm gibst, und diese Klasse ist das, was http_adapter.make_handler_class(handle, state) zurückgibt. Diese Lektion schreibt zwei kleine : make_server, das den Server baut, und serve, das ihn eine feste Zahl von Verbindungen beantworten lässt und ihn dann schließt.

Binden, wie Networks es gezeigt hat

HTTPServer nimmt eine Adresse und eine Handler-Klasse:

handler_class = http_adapter.make_handler_class(handle, state)
server = http.server.HTTPServer(("127.0.0.1", 0), handler_class)

Die Adresse folgt den Regeln, die du für Listener gelernt hast. 127.0.0.1 ist die , also können sich nur Programme auf demselben Rechner verbinden; binde nie 0.0.0.0, das Verbindungen aus jedem Netzwerk annimmt, in dem der Rechner ist. 0 bittet das System um irgendeinen freien Port, sodass der Server nie fehlschlägt, weil ein fester Port belegt ist, und keine Portnummer je in deinem Code stehen muss. Der Server bindet und beginnt zu lauschen, sobald er erzeugt wird. Ab dann kann sich ein Client verbinden, sogar bevor der Server bereit ist, ihn anzunehmen: Das System hält die Verbindung fest, bis das nächste Annehmen sie übernimmt.

make_server(handle, state, *, timeout=1.0) bekommt handle als . Innerhalb der Funktion bedeutet handle die Funktion, die übergeben wurde, meist deine eigene, sodass ein Test eine andere übergeben kann.

Eine weitere Zeile setzt server.timeout. Dieses Attribut begrenzt, wie lange der Server auf die Ankunft eines Clients wartet, bevor er dieses Warten aufgibt. Es ist ein anderes Timeout als SOCKET_TIMEOUT des Adapters, das jedes Lesen auf einer Verbindung begrenzt, sobald ein Client da ist.

Ein Aufruf, eine Verbindung

server.handle_request() wartet auf einen Client, höchstens server.timeout Sekunden lang. Wenn sich ein Client rechtzeitig verbindet, beantwortet es diese Verbindung, samt Request-Handler, und kehrt zurück. Wenn niemand kommt, kehrt es still zurück, wenn das Timeout abläuft, ohne eine auszulösen. So oder so behandelt ein Aufruf höchstens eine Verbindung. serve ist also eine for- über range(max_connections) mit einem Aufruf von handle_request() darin:

max_connections=3, three clients come
  handle_request()   waits, client 1 connects, answered
  handle_request()   waits, client 2 connects, answered
  handle_request()   waits, client 3 connects, answered
  server closed

max_connections=3, server.timeout=1.0, nobody comes
  handle_request()   waits 1 second, returns
  handle_request()   waits 1 second, returns
  handle_request()   waits 1 second, returns
  server closed      about 3 seconds in all

In beiden Läufen macht serve genau drei Aufrufe und hält dann an; ein Aufruf, der mit Timeout endete, zählt trotzdem. Das macht den Lauf begrenzt: Er endet nach höchstens max_connections Wartezeiten, egal was Clients tun.

HTTPServer hat auch eine namens serve_forever(). Sie tut, was ihr Name sagt, und kehrt nur zurück, wenn ein anderer Teil des Programms ihr sagt, dass sie anhalten soll. Dein Server benutzt sie nie.

serve(server, max_connections=3) läuft mit server.timeout = 1.0, und kein Client verbindet sich je. Was passiert?

Schließ ihn

make_server gibt den Server offen zurück, und serve schließt ihn, wenn die Schleife fertig ist, sodass der lauschende Socket den Lauf nie überlebt. Ein Server funktioniert mit with so, wie deine Sockets es getan haben: Das Verlassen eines Blocks with server: ruft server.server_close() auf, ob der Block fertig wurde oder eine Ausnahme ausgelöst hat. server.server_close() nach der Schleife aufzurufen funktioniert auch.

pl_http.py

Du hast ein , und ein Server braucht einen Client. pl_http.py in deinem Arbeitsbereich ist ein für diesen Kurs geschriebener Helfer, der den Client spielt. Er ruft dein make_server mit deinem handle und den Büchern aus sample_state() auf, lässt dein serve im Hintergrund laufen, sendet die Anfragen des Szenarios, das du nennst, und gibt die Bytes aus, die in jede Richtung gingen. Du führst ihn aus; du bearbeitest ihn nie. Das Szenario für diese Lektion ist books, und sein Client ist wire.fetch, die Funktion, die du in Kapitel 2 fertiggestellt hast. Das wire.py in deinem Arbeitsbereich ist die fertige Version aus Kapitel 3, zum Lesen und Ausführen da, nicht zum Bearbeiten:

python pl_http.py books

Wenn beide Funktionen geschrieben sind, siehst du Folgendes, wobei hier drei Zeilen geändert sind: Die Antwort und die Zeile headers sind mit ... gekürzt, und die Uhrzeit in der Log-Zeile ist durch Wörter ersetzt.

pl_http: books, your server on 127.0.0.1 (a port the system chose)
--- requests and replies ---
client -> server  b'GET /books HTTP/1.1\r\nHost: books.test\r\n\r\n'  (41 bytes)
server -> client  b'HTTP/1.0 200 OK\r\nServer: ...
--- wire.fetch returned ---
version: 'HTTP/1.0'
status: 200
reason: 'OK'
headers: {'server': [...], 'date': [...], 'content-type': ['application/json'], 'content-length': ['132']}
body: b'{"books": [{"id": 1, "title": "Kindred", "author": "Octavia E. Butler"}, {"id": 2, "title": "Caf\xc3\xa9 Society", "author": "Ana Ruiz"}]}'  (132 bytes)
--- your server's log ---
127.0.0.1 - - [the date and time] "GET /books HTTP/1.1" 200 -
--- result ---
serve() returned after 1 connection; the server is closed
pl_http: every thread has stopped

Lies es von oben. Die erste Zeile nennt die Adresse, die dein Server gebunden hat; pl_http findet den Port selbst und gibt ihn nie aus. client -> server ist die Anfrage, die fetch geschrieben hat. server -> client ist die Antwort, nach ihren ersten 200 Bytes abgeschnitten: die Statuszeile HTTP/1.0 200 OK, dann Server, das BaseHTTP und die Python-Version nennt, und Date, beide von http.server hinzugefügt, dann deine beiden Header, die Leerzeile und der Anfang deines Bodys. wire.fetch returned ist der Datensatz, den dein Client aus Kapitel 1 aus diesen Bytes gebaut hat. Das Log ist die Zeile, die http.server für jede Anfrage auf die Standardfehlerausgabe schreibt. Das Ergebnis sagt, ob serve zurückgekehrt ist und ob der Server geschlossen wurde.

Wenn etwas nicht stimmt, sagt derselbe Bericht das. Wenn make_server oder serve nicht geschrieben ist, steht dort not written yet: Lesson 3. Wenn serve zurückkehrt, ohne den Server zu schließen, sagt das Ergebnis the server is still open, und pl_http endet mit Status 1. Wenn serve nie zurückkehrt, hört pl_http etwa 2 Sekunden, nachdem der Client fertig ist, auf zu warten, sagt, dass serve() noch lief, und endet mit Status 3; kein Server bleibt zurück. Wenn das Binden fehlschlägt, nennt er das ein Problem der Website, aber prüfe zuerst die Adresse in deinem make_server: Eine Adresse, die der Rechner nicht hat, schlägt auf dieselbe Weise fehl.

wire.fetch hat HTTP/1.1 gesendet, aber die Antwort sagt HTTP/1.0. Warum?

Deine Aufgabe

  1. Schreibe make_server(handle, state, *, timeout=1.0): Baue die Handler-Klasse mit http_adapter.make_handler_class(handle, state), erzeuge den Server mit http.server.HTTPServer(("127.0.0.1", 0), handler_class), setze server.timeout = timeout und gib den Server zurück.

  2. Schreibe serve(server, *, max_connections): Rufe innerhalb von with server: mit einer for-Schleife max_connections Mal server.handle_request() auf.

  3. Führe python pl_http.py books aus und lies das Protokoll.

Behalte import http.server oben in der Datei und schreibe http.server.HTTPServer vollständig aus, wie oben.

Drücke dann Check my work. Dein Server beantwortet jetzt jede Anfrage mit der ganzen , auch /nowhere. Die nächste Lektion bringt ihn dazu, nein zu sagen.

Aufgabe

Schreibe zwei in reading_server.py.

  • make_server(handle, state, *, timeout=1.0) baut die Handler-Klasse mit http_adapter.make_handler_class(handle, state), wobei es seine eigenen handle und state benutzt, erzeugt den Server mit http.server.HTTPServer(("127.0.0.1", 0), handler_class), setzt server.timeout = timeout und gibt den Server zurück, noch offen.

  • serve(server, *, max_connections) ruft genau max_connections Mal server.handle_request() auf und schließt dann den Server, mit with server: um die oder server.server_close() danach.

Behalte die Zeile import http.server und schreibe http.server.HTTPServer(...) vollständig aus; benutze keine Zeile from http.server import. Rufe nicht serve_forever() auf.

Führe im python pl_http.py books aus, um zu sehen, wie dein Server wire.fetch antwortet.

Drücke dann Check my work. Die Prüfung durchsucht reading_server.py nach einer Zeile from http.server import und einem Aufruf von serve_forever. Sie führt make_server aus, wobei http.server über das durch einen Ersatz ersetzt ist, der aufzeichnet, worum dein Code gebeten hat, sodass kein gebunden wird: dass der Server ein HTTPServer auf ("127.0.0.1", 0) war, dass server.timeout das übergebene Timeout ist (1.0, wenn keines übergeben wird) und dass eine Anfrage durch die übergebene Handler-Klasse das handle und den state erreicht, die make_server bekommen hat. Sie führt serve auf einem Ersatz-Server mit 3, 1 und 5 Verbindungen aus und prüft, dass handle_request genau so oft aufgerufen wurde und dass der Server nach dem letzten Aufruf geschlossen wurde; ein serve, das immer weiter aufruft, schlägt nach 50 Aufrufen fehl, statt weiterzulaufen. Nichts aus deinem Terminal wird bewertet.