De bibliotheek websockets, zonder asyncio · oefening
Lezen hoe een verbinding eindigde
De handler uit les 2 echoot correct, en elk gesprek dat hij bedient, eindigt nog steeds in een recv wierp ConnectionClosedOK op omdat de client als eerste sloot, niets in echo_handler ving het, dus de connection handler failed. De verbinding zelf sloot netjes, zoals de laatste regel van het blok zei. Een client die sluit, is het normale einde van een gesprek, dus de handler moet daar zijn
OK en Error
Zodra een verbinding gesloten is, werpt elke latere recv en send erop een exception uit websockets.exceptions op. Welke hangt af van hoe de verbinding eindigde:
ConnectionClosedOKals er een close-elke kant op ging en beide 1000, 1001 of helemaal geen code droegen (een lege close, die wordt gemeld als 1005); ConnectionClosedErrorin elk ander geval: een close met een andere code, zoals 1008, of een verbinding die eindigde zonder enig close-frame.
Die verdeling is de eigen keuze van de bibliotheek van wat als normaal einde telt, geen regel in RFC 6455. Beide klassen zijn soorten ConnectionClosed, de naam die echo_library.py al importeert. Een except-clausule die een klasse noemt, vangt ook elke exception die een soort van die klasse is, dus except ConnectionClosed: vangt allebei.
De server sluit met 1008. Wat werpt de volgende recv van de client op?
Wat er aankwam
Elk van deze exceptions draagt de close-frames. Noem de exception in de except-regel om ze te lezen:
try:
connection.recv(timeout=WAIT)
except ConnectionClosed as exc:
print(exc.rcvd)
exc.rcvd is het close-frame dat van de peer aankwam, met .code en .reason, en exc.sent is het frame dat deze kant stuurde. Elk van beide kan None zijn. Als exc.rcvd is None, kwam er geen close-frame aan: de describe_close doet hetzelfde.
describe_close(exc) controleert dus eerst exc.rcvd is None en geeft dan {"code": 1006, "reason": ""} terug. Anders geeft het de code en reden van exc.rcvd terug in dezelfde soort is None voor de controle, zoals je bij elke test op None zou doen.
Oudere code leest exc.code. Dat werkt nog in de versie die je vastpinde, maar het geeft een DeprecationWarning die naar rcvd.code verwijst, dus lees het close-frame via rcvd.
exc.rcvd is None. Wat is er gebeurd?
Bewaak send ook
recv is niet de enige aanroep die de verbinding gesloten kan aantreffen. De client kan sluiten nadat je recv terugkeerde en voordat je send draait; dan werpt send ConnectionClosedOK op. De try dekt dus beide regels van de lusbody:
try:
message = connection.recv(timeout=WAIT)
connection.send(message)
except ConnectionClosed:
return
Het andere einde is een client die niets zegt. Als er binnen WAIT geen bericht aankomt, werpt recv de ingebouwde TimeoutError van Python op. Een client die 2 seconden niets heeft gestuurd, wacht nergens op van deze server, dus sluit hem zelf, met 1000 en een reden zoals "no message arrived in time", en keer terug. Geef de try een tweede clausule, except TimeoutError:, die dat doet.
Waarom dekt het try-blok send en niet alleen recv?
Je opdracht
In echo_library.py:
schrijf
describe_close(exc):{"code": 1006, "reason": ""}alsexc.rcvd is None, en anders de code en reden uitexc.rcvd;maak
echo_handleraf: zet binnen de lusrecvensendin eentry; keer bijConnectionClosedterug; roep bijTimeoutErrorconnection.close(1000, "no message arrived in time")aan en keer terug. De limiet en de laatste close naMAX_MESSAGESblijven zoals ze zijn.
Run
Druk op Run. Standaardfout blijft deze keer leeg: geen blok connection handler failed. Drie nieuwe secties maken verbinding met de client van de bibliotheek, lezen totdat de verbinding gesloten is, en drukken de klassenaam van de exception af en wat je describe_close ervan maakte. Een afgewerkte versie, gedraaid op de site, drukte af:
Lesson 4: you close
ConnectionClosedOK, {'code': 1000, 'reason': 'done'}
Lesson 4: the server closes with 1008
ConnectionClosedError, {'code': 1008, 'reason': 'this path is closed'}
Lesson 4: the server vanishes
ConnectionClosedError, {'code': 1006, 'reason': ''}
In de eerste sluit de client met 1000 en done, en de server antwoordt met dezelfde close, dus beide frames dragen 1000: OK. In de tweede sluit de handler van een bibliotheekserver meteen met 1008, dus het einde is een Error, en rcvd bevat de code en reden van de server. In de derde beantwoordt een server die met de hand is gebouwd uit serve_handshake van hoofdstuk 2 de upgrade met een 101 en sluit daarna de TCP-verbinding zonder close-frame: een Error met rcvd op None, wat je ending en read_the_ending in library_demo.py om te zien hoe elk ervan tot stand komt. Druk daarna op Submit.
Je handler is een handvol regels, en je client een paar meer. De volgende les legt ze naast de functies die je in hoofdstuk 2 tot en met 5 schreef en benoemt wat de bibliotheek doet dat die nooit deden.
Opdracht
In echo_library.py:
Schrijf
describe_close(exc). Geef{"code": 1006, "reason": ""}terug alsexc.rcvd is None. Geef anders{"code": exc.rcvd.code, "reason": exc.rcvd.reason}terug.Maak
echo_handleraf. Zet binnen dede recven desendin ééntry. Keer bijConnectionClosedterug. Roep bijTimeoutErrorconnection.close(1000, "no message arrived in time")aan en keer terug. Houd de limiet en de close met 1000 naMAX_MESSAGESberichten.
Druk op Run: er verschijnt geen blok connection handler failed, en de drie secties van les 4 drukken ConnectionClosedOK, {'code': 1000, 'reason': 'done'}, ConnectionClosedError, {'code': 1008, 'reason': 'this path is closed'} en ConnectionClosedError, {'code': 1006, 'reason': ''} af.
Druk daarna op Submit. De controle bouwt de describe_close: OK- en Error-eindes met codes en redenen die je nog niet hebt gezien, een close-exc.code lezen in plaats van exc.rcvd.code faalt, omdat de controle de deprecationwaarschuwing van de bibliotheek in een fout verandert. Ze geeft echo_handler stand-in-verbindingen: een client die na twee berichten sluit, een verbinding die eindigt zonder close-frame, een send die de verbinding gesloten aantreft, een client die niets op tijd stuurt (een close met 1000 wordt verwacht, en geen verdere recv), en tien berichten gevolgd door meer. Elk moet eindigen doordat de handler terugkeert, niet doordat hij opwerpt. Twee except-clausules, een voor ConnectionClosedOK en een voor ConnectionClosedError, zijn prima in plaats van except ConnectionClosed:.