0%

Zelf HTTP serveren · oefening

Een paar verbindingen bedienen en dan stoppen

handle geeft de juiste terug als je het aanroept, maar nog geen enkele client heeft het bereikt. In Networks with Python schreef je de serverkant met de hand: make_listener bond een en serve accepteerde clients een voor een, met een time-out, totdat het er max_connections had bediend. http.server.HTTPServer is die listener en die accept-stap in één . Elke keer dat het een verbinding accepteert, maakt het er een request handler voor uit een klasse die je het geeft, en die klasse is wat http_adapter.make_handler_class(handle, state) teruggeeft. Deze les schrijft twee kleine : make_server, die de server bouwt, en serve, die hem een vast aantal verbindingen laat beantwoorden en hem dan sluit.

Binden zoals Networks het leerde

HTTPServer krijgt een adres en een handlerklasse:

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

Het adres volgt de regels die je voor listeners leerde. 127.0.0.1 is het , dus alleen programma’s op dezelfde machine kunnen verbinding maken; bind nooit 0.0.0.0, dat verbindingen accepteert van elk netwerk waarop de machine zit. 0 vraagt het systeem om een willekeurige vrije poort, zodat de server nooit mislukt omdat een vaste poort bezet is, en er nooit een poortnummer in je code hoeft te staan. De server bindt en begint te luisteren op het moment dat hij wordt gemaakt. Vanaf dat moment kan een client verbinding maken, zelfs voordat de server klaar is om haar te accepteren: het systeem houdt de verbinding vast totdat de volgende accept haar oppakt.

make_server(handle, state, *, timeout=1.0) krijgt handle als . Binnen de functie betekent handle welke functie er ook werd meegegeven, meestal je eigen, zodat een test een andere kan meegeven.

Nog één regel stelt server.timeout in. Dat attribuut begrenst hoe lang de server wacht tot er een client aankomt voordat hij dat wachten opgeeft. Het is een andere time-out dan de SOCKET_TIMEOUT van de adapter, die elke leesactie op een verbinding begrenst zodra er een client is.

Eén aanroep, één verbinding

server.handle_request() wacht op één client, hoogstens server.timeout seconden. Maakt er op tijd een client verbinding, dan beantwoordt het die verbinding, request handler en al, en keert het terug. Komt er niemand, dan keert het stilletjes terug als de time-out afloopt, zonder een op te werpen. Hoe dan ook handelt één aanroep hoogstens één verbinding af. serve is dus een for- over range(max_connections) met één aanroep van handle_request() erin:

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 beide uitvoeringen doet serve precies drie aanroepen en stopt dan; een aanroep met een time-out telt ook. Dat maakt de uitvoering begrensd: ze eindigt na hoogstens max_connections keer wachten, wat clients ook doen.

HTTPServer heeft ook een die serve_forever() heet. Die doet wat de naam zegt en keert pas terug als een ander deel van het programma zegt dat hij moet stoppen. Je server gebruikt hem nooit.

serve(server, max_connections=3) draait met server.timeout = 1.0, en er maakt nooit een client verbinding. Wat gebeurt er?

Sluit hem

make_server geeft de server open terug, en serve sluit hem zodra de lus klaar is, zodat de luisterende socket de uitvoering nooit overleeft. Een server werkt met with zoals je sockets dat deden: het verlaten van een blok with server: roept server.server_close() aan, of het blok nu afliep of een exception opwierp. server.server_close() aanroepen na de lus werkt ook.

pl_http.py

Je hebt één , en een server heeft een client nodig. pl_http.py, in je werkruimte, is een hulpprogramma dat voor deze cursus is geschreven en de client speelt. Het roept je make_server aan met je handle en de boeken uit sample_state(), voert je serve op de achtergrond uit, verstuurt de verzoeken van het scenario dat je noemt, en drukt de bytes af die elke kant op gingen. Jij voert het uit; je bewerkt het nooit. Het scenario voor deze les is books, en de client ervan is wire.fetch, de functie die je in hoofdstuk 2 afmaakte. De wire.py in je werkruimte is de afgewerkte versie uit hoofdstuk 3, om te lezen en uit te voeren, niet om te bewerken:

python pl_http.py books

Als beide functies geschreven zijn, zie je dit, met drie regels die hier zijn aangepast: het antwoord en de regel headers zijn ingekort met ..., en de tijd in de logregel is vervangen door woorden.

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

Lees het van boven af. De eerste regel noemt het adres dat je server bond; pl_http vindt de poort zelf en drukt hem nooit af. client -> server is het verzoek dat fetch schreef. server -> client is het antwoord, afgekapt na de eerste 200 bytes: de statusregel HTTP/1.0 200 OK, dan Server, met BaseHTTP en de Python-versie, en Date, allebei toegevoegd door http.server, dan je twee headers, de lege regel en het begin van je body. wire.fetch returned is het record dat je client uit hoofdstuk 1 uit die bytes bouwde. De log is de regel die http.server voor elk verzoek naar standard error schrijft. Het resultaat zegt of serve terugkeerde en of de server werd gesloten.

Als er iets mis is, zegt hetzelfde verslag dat. Als make_server of serve niet geschreven is, zegt het not written yet: Lesson 3. Als serve terugkeert zonder de server te sluiten, zegt het resultaat the server is still open, en eindigt pl_http met status 1. Als serve nooit terugkeert, stopt pl_http met wachten ongeveer 2 seconden nadat de client klaar is, zegt het dat serve() nog draaide, en eindigt het met status 3; er blijft geen server achter. Als binden mislukt, noemt het dat een probleem van de site, maar controleer eerst het adres in je make_server: een adres dat de machine niet heeft, mislukt op dezelfde manier.

wire.fetch verstuurde HTTP/1.1, maar het antwoord zegt HTTP/1.0. Waarom?

Je opdracht

  1. Schrijf make_server(handle, state, *, timeout=1.0): bouw de handlerklasse met http_adapter.make_handler_class(handle, state), maak de server met http.server.HTTPServer(("127.0.0.1", 0), handler_class), stel server.timeout = timeout in, en geef de server terug.

  2. Schrijf serve(server, *, max_connections): roep binnen with server: server.handle_request() max_connections keer aan met een for-lus.

  3. Voer python pl_http.py books uit en lees het transcript.

Houd import http.server bovenaan het bestand en schrijf http.server.HTTPServer voluit, zoals hierboven.

Druk daarna op Check my work. Je server beantwoordt nu elk verzoek met de hele , /nowhere inbegrepen. De volgende les laat hem nee zeggen.

Opdracht

Schrijf twee in reading_server.py.

  • make_server(handle, state, *, timeout=1.0) bouwt de handlerklasse met http_adapter.make_handler_class(handle, state), met zijn eigen handle en state, maakt de server met http.server.HTTPServer(("127.0.0.1", 0), handler_class), stelt server.timeout = timeout in, en geeft de server terug, nog open.

  • serve(server, *, max_connections) roept server.handle_request() precies max_connections keer aan en sluit daarna de server, met with server: rond de of server.server_close() erna.

Houd de regel import http.server en schrijf http.server.HTTPServer(...) voluit; gebruik geen regel from http.server import. Roep serve_forever() niet aan.

Voer python pl_http.py books uit in de om je server wire.fetch te zien beantwoorden.

Druk daarna op Check my work. Die zoekt in reading_server.py naar een regel from http.server import en een aanroep van serve_forever. Ze voert make_server uit met http.server via de vervangen door een vervanger die vastlegt waar je code om vroeg, zodat er geen wordt gebonden: dat de server een HTTPServer was op ("127.0.0.1", 0), dat server.timeout de meegegeven time-out is (1.0 als er geen wordt meegegeven), en dat één verzoek via de handlerklasse die je meegaf de handle en de state bereikt die make_server kreeg. Ze voert serve uit op een vervangende server met 3, 1 en 5 verbindingen en controleert dat handle_request precies zo vaak werd aangeroepen en dat de server na de laatste aanroep werd gesloten; een serve die blijft aanroepen, mislukt na 50 aanroepen in plaats van door te draaien. Niets uit je terminal wordt beoordeeld.