Ein Gespräch auf Loopback · Übung
Die Verbindung scheitern lassen
Zwei Enden funktionieren jetzt: Jede Seite kann schließen, und beide melden 1000. Der unhöfliche Client in Run bekommt immer noch die alte Behandlung. Nach Lektion 2 lauten seine Zeilen so:
A rude client:
client: the server closed the connection without a close frame
server: stopped by ValueError: (1002, 'the frame breaks the masking rule')
Der Codec hat seine Aufgabe erfüllt: Er hat einen unmaskierten serve_conversation heraus, beendete den Thread des Servers, und der Client hat nie erfahren, was er falsch gemacht hat. Diese Lektion verwandelt diese Ausnahme in einen Close-Frame.
Schließen und Scheitern
Ein Close ist ein Gespräch, das beide Seiten gemeinsam beenden. RFC 6455, Abschnitt 7.1.7, beschreibt das andere Ende, das Scheiternlassen der Verbindung, für den Fall, dass eine Gegenseite das Protokoll verletzt hat. Der Endpunkt, der es bemerkt, “SHOULD send a Close frame with an appropriate status code”, soll also einen Close-Frame mit einem passenden
Ein scheiternder Server tut also drei Dinge: Er sendet einen Close-Frame mit dem Code für die verletzte Regel, er liest nichts mehr, und er kehrt zurück. Dieser Kurs sendet diesen Close immer, wenn der
Codes aus dem Codec
Seit dem Projekt aus Kapitel 4 ist jede Ablehnung in read_message ein ValueError(code, reason). Der Code ist exc.args[0] und der Grund exc.args[1]:
1002, Protokollfehler: ein unmaskierter Frame an einen Server, reservierte Bits oder
, ein Steuerframe, der zu lang oder nicht final ist, ein , der nicht reisen darf; 1003, eine Nachricht, die dieser Codec nicht annehmen kann: jedes Fragment, weil der Lern-Codec fragmentierte Nachrichten nicht zusammensetzt;
1007, eine Textnachricht oder ein Close-Grund, der kein gültiges
ist; 1009, Nachricht zu groß: ein Frame, dessen Header mehr als
MAX_SIZEBytes ankündigt, abgelehnt, bevor ein einziges Payload-Byte gelesen wird.
failure_frame(exc) baut den Close aus diesen beiden ws_frames.close_payload(exc.args[0], exc.args[1]) in einem unmaskierten Close-Frame. Für ValueError(1009, "x") ist das:
88 03 03 f1 78
03 f1 ist 1009, und 78 ist “x”.
Ein maskierter binärer Frame kündigt 2.000.000 Bytes an, mehr als MAX_SIZE. Was sendet der Server?
Eine Gegenseite, die verschwindet
Zwei andere Ausnahmen können einen Lesevorgang beenden, und keine davon ist eine verletzte Regel:
ConnectionError:read_exacthatb""bekommen, weil die Gegenseite dieohne Close-Frame geschlossen hat, wie es der Client aus Lektion 1 getan hat; TimeoutError: Die Gegenseite hat 2 Sekunden lang nichts gesendet, das Timeout, das die Verdrahtung auf jedem angenommenen Socket setzt.
In beiden Fällen gibt es niemanden, an den man einen Close senden könnte, oder keinen Grund zu glauben, dass ein Close gelesen würde. Der Server sendet nichts und meldet den Code 1006, den RFC 6455 genau dafür reserviert: Die Verbindung endete ohne Close-Frame. 1006 erscheint immer nur in Meldungen. Wie 1005 darf er nie in einem Frame reisen.
Die Verbindung des Clients bricht mitten in einem Gespräch ab. Was sendet der Server?
Deine Aufgabe
Schreibe failure_frame(exc). Umschließe dann die serve_conversation, einschließlich der Schließschritte danach, mit try, mit zwei Handlern:
def serve_conversation(sock, read_exact, *, max_messages=MAX_MESSAGES):
count = 0
try:
... # Lesson 2's loop and closing steps, unchanged
except ValueError as exc:
sock.sendall(failure_frame(exc))
return {"messages": count, "code": exc.args[0]}
except (ConnectionError, TimeoutError):
return {"messages": count, "code": 1006}
count ist der Name, den deine Schleife für die bisher als Echo zurückgesendeten Nachrichten verwendet. Zwei getrennte Klauseln except ConnectionError und except TimeoutError funktionieren genauso gut. Beschränke die Handler auf diese Ausnahmetypen: Ein breites except Exception würde eine verletzte Regel wie eine verschwundene Gegenseite behandeln und überhaupt keinen Close senden.
Warum nicht warten
Nach einem Close in Lektion 2 hat der Server auf die Antwort gewartet. Nach einem Scheitern tut er das nicht. Warten würde bedeuten, der Gegenseite noch einmal zu vertrauen. Ein Client, der einen unmaskierten Frame gesendet hat, ist vielleicht ein kaputtes Programm oder etwas, das gar kein
Warum wartet der Server nicht auf den Close des Clients, nachdem er die Verbindung hat scheitern lassen?
Run
Klicke auf Run. Nach dieser Lektion werden die Zeilen des unhöflichen Clients zu:
A rude client:
client: {'type': 'close', 'code': 1002, 'reason': 'the frame breaks the masking rule'}
server: {'messages': 0, 'code': 1002}
server thread still running: False
Der unhöfliche Client liest jetzt einen Close-Frame, der seinen Fehler nennt, und der Thread des Servers endet mit einem Ergebnis statt mit einer Ausnahme. Das erste Gespräch meldet weiterhin auf beiden Seiten 1000.
Klicke dann auf Submit. Es gibt serve_conversation Frames, die jede der oben genannten Regeln verletzen, einen Header, der zu viele Bytes ankündigt, ohne dass etwas dahinter kommt, und Clients, die nach einer Nachricht verschwinden oder verstummen, und prüft den Close, den du sendest, den Code, den du zurückgibst, und dass nach einem Scheitern nichts gelesen wird.
Dein Server hat jetzt eine Antwort auf jede Art, wie ein Gespräch enden kann. Bisher hast du das in Run gesehen, und Submit hat es geprüft; die nächste Lektion schreibt eigene Tests, die es beweisen, über zwei verbundene Sockets ganz ohne
Aufgabe
Schreibe eine loopback_demo.py.
failure_frame(exc): Gib den unmaskierten Close-für einen ValueError(code, reason)zurück:ws_frames.close_payload(exc.args[0], exc.args[1])inws_frames.encode_frame(ws_frames.OP_CLOSE, ...).serve_conversation: Umschließe dieund die Schließschritte danach mit try. Sende beiValueError as excfailure_frame(exc)und gib sofort{"messages": n, "code": exc.args[0]}zurück, ohne erneut zu lesen. Sende beiConnectionErroroderTimeoutErrornichts und gib{"messages": n, "code": 1006}zurück. Fange nur dieseab, nicht Exception.
Klicke auf Run: Der unhöfliche Client sollte jetzt {'type': 'close', 'code': 1002, ...} empfangen, und das erste Gespräch sollte weiterhin auf beiden Seiten 1000 melden.
Klicke dann auf Submit. Die Prüfung ruft failure_frame direkt auf und gibt serve_conversation Stellvertreter-Clients, die einen unmaskierten Text senden, einen Text, der kein