Masken und Steuerframes · Übung
Sag, warum du schließt
Dein Decoder erkennt einen Close-
Was eine Close-Payload enthält
RFC 6455, Abschnitt 5.5.1, erlaubt zwei Formen. Die Payload ist entweder leer, oder sie beginnt mit einem 2 Byte langen vorzeichenlosen Code in
88 02 03 e8
88 06 03 e8 64 6f 6e 65
88 ist FIN plus 03 e8 ist 1000 als Big-Endian-16-Bit-Zahl, die struct.pack("!H", 1000) baut und struct.unpack("!H", ...)[0] zurückliest, genau wie bei der 16-Bit-Länge in Kapitel 3. 64 6f 6e 65 ist “done” in UTF-8. Ein Close ist ein Steuerframe, also ist seine ganze Payload höchstens 125 Bytes lang, und der Grund bekommt, was der Code übrig lässt: höchstens 123 Bytes.
Was gibt das aus?
import struct
packed = struct.pack("!H", 1001).hex(" ")
number = struct.unpack("!H", bytes([0x03, 0xF1]))[0]
print(packed, number)
Die Codes, die dieser Kurs verwendet
Abschnitt 7.4.1 des RFC definiert, was die Codes im Bereich um 1000 bedeuten. Das sind die, die dieser Kurs sendet oder liest:
| Code | Bedeutung | In einem Close-Frame gesendet? |
|---|---|---|
| 1000 | normales Schließen: Das Gespräch ist beendet | ja |
| 1001 | going away, der Endpunkt geht weg, etwa ein Server, der herunterfährt | ja |
| 1002 | Protokollfehler: Die Gegenseite hat eine Framing-Regel verletzt | ja |
| 1003 | nicht unterstützte Daten: Der Endpunkt kann diese Art von Daten nicht annehmen | ja |
| 1005 | im Close-Frame war kein | nie; von einem Empfänger gemeldet |
| 1006 | die Verbindung endete ganz ohne Close-Frame | nie; von einem Empfänger gemeldet |
| 1007 | ungültige Payload-Daten, etwa eine Textnachricht, die kein UTF-8 ist | ja |
| 1008 | Richtlinienverstoß, die allgemeine Ablehnung, wenn kein anderer Code passt | ja |
| 1009 | Nachricht zu groß | ja |
| 1011 | der Server ist auf eine unerwartete | ja |
1005 und 1006 unterscheiden sich vom Rest. Sie beschreiben, was ein Empfänger gesehen hat, und sie existieren, damit ein Programm “die Gegenseite hat einen leeren Close gesendet” oder “die Verbindung ist einfach zu Ende gegangen” mit einer Zahl melden kann, wie jedes andere Ende. Sie reisen nie in einem Close-Frame.
Codes, die nicht reisen dürfen
Die Tabelle des RFC ist nicht die ganze
unter 1000: nicht verwendet;
1004: reserviert;
1005, 1006 und 1015: Kennzeichnungen, die ein Endpunkt über sich selbst meldet, nie gesendet (1015 bedeutet, dass der
- gescheitert ist); 1016 bis 2999: niemandem zugewiesen;
3000 bis 3999: von Bibliotheken und Frameworks registriert;
4000 bis 4999: für die private Verwendung durch eine Anwendung.
Damit bleiben 1000 bis 1003, 1007 bis 1014 und 3000 bis 4999 als die Codes, die in einem Close-Frame vorkommen dürfen. Diese Menge wurde beim Schreiben dieses Kurses mit der Prüfung verglichen, die die Bibliothek websockets (die du ab Kapitel 6 verwendest) auf Close-Codes anwendet, und die beiden stimmen bei jedem Code überein; mit der aktuellen Seite des Registers wurde sie nicht verglichen.
Ein Server schließt mit 1012 (Dienst-Neustart). Akzeptiert parse_close_payload das?
Was ist der längste Grund, den ein Close-Frame tragen kann?
Einen Close lesen
parse_close_payload verwandelt die Payload zurück in {"code": ..., "reason": ...}, und es muss vier Fälle unterscheiden:
Leer. Gültig. Es war kein Code vorhanden, also melde 1005 mit dem Grund
"". Hier ist die Kennzeichnung des Empfängers am Werk: Die Gegenseite hat nicht 1005 gesendet, sie hat nichts gesendet.Ein Byte. Ungültig: Es gibt keinen Platz für einen 2 Byte langen Code.
Ein Code, der nicht reisen darf, etwa 1004 oder 1005. Ungültig: Die Gegenseite hat die Regeln verletzt.
Ein Grund, der kein UTF-8 ist. Ungültig.
.decode("utf-8")löst bei solchen BytesUnicodeDecodeErroraus, und du kannst ihn durchlassen:UnicodeDecodeErrorist eine Art von, also fängt ein Aufrufer, der ValueErrorabfängt, auch ihn ab.
try:
bytes.fromhex("c3 28").decode("utf-8")
except ValueError:
print("a UnicodeDecodeError, caught as a ValueError")
Dieselben Prüfungen schützen die andere Richtung. close_payload(code, reason="") weigert sich, eine Payload mit einem Code zu bauen, der nicht gesendet werden darf, oder mit einem Grund, der die Payload länger als 125 Bytes macht, gezählt nach dem Kodieren.
Der Close-Frame einer Gegenseite hat eine leere Payload. Welchen Code meldet parse_close_payload?
Deine Aufgabe
Schreibe in ws_frames.py die drei
close_code_allowed(code):Truefür 1000 bis 1003, 1007 bis 1014 und 3000 bis 4999,Falsefür jeden anderen Code.close_payload(code, reason=""): der Code als 2 Bytes in Netzwerk-Byte-Reihenfolge, dann der Grund in UTF-8. LöseValueErroraus für einen Code, denclose_code_allowedablehnt, und für eine Payload über 125 Bytes.parse_close_payload(payload):{"code": 1005, "reason": ""}für eine leere Payload; sonst der 2 Byte lange Code und der als UTF-8 dekodierte Grund. LöseValueErroraus für eine Payload aus einem Byte und für einen Code, der nicht reisen darf; ein Grund, der kein UTF-8 ist, darfUnicodeDecodeErrorauslösen.
Klicke auf Run. Der erste Abschnitt zu Lektion 4 baut eine Close-Payload 1000 “done” und liest sie zurück. Der zweite versucht dasselbe mit 1005, und show gibt den ValueError aus, den dein close_payload auslöst; die Meldung ist deine eigene, also kann sie anders lauten als diese:
Lesson 4: close 1000 and back
{'payload': '03 e8 64 6f 6e 65', 'parsed': {'code': 1000, 'reason': 'done'}}
Lesson 4: close 1005, which no one may send
ValueError: close code 1005 may not be sent
Lesson 6: what a server reads
not written yet
Klicke dann auf Submit. Die Prüfung fragt close_code_allowed nach jedem Code von 0 bis 5.000. Sie baut Payloads für die Codes des RFC und für einen Code und Grund, die du nicht gesehen hast, darunter eine mit einem Grund von genau 123 Bytes und eine, die ein Byte länger ist, und erwartet ValueError für Codes, die nicht gesendet werden dürfen. Sie liest Payloads mit Codes aus dem Register und Gründen in UTF-8 und erwartet ValueError für eine Payload aus einem Byte, für Codes, die nicht reisen dürfen, und für einen Grund, der kein UTF-8 ist. Jeder Frame bisher hat eine ganze Nachricht getragen; die nächste Lektion sieht sich Nachrichten an, die in Stücken ankommen.
Aufgabe
Schreibe die drei ws_frames.py:
close_code_allowed(code):Truefür 1000 bis 1003, 1007 bis 1014 und 3000 bis 4999;Falsefür jeden anderen Code.close_payload(code, reason=""): der Code als 2 Bytes in, dann der als kodierte Grund. Löse aus für einen Code, den close_code_allowedablehnt, und für eine Payload, die länger als 125 Bytes ist (ein Grund von höchstens 123 Bytes nach dem Kodieren).parse_close_payload(payload):{"code": 1005, "reason": ""}für eine leere Payload; sonst{"code": ..., "reason": ...}mit dem 2 Byte langen Big-Endian-Code und dem als UTF-8 dekodierten Rest. LöseValueErroraus für eine Payload aus einem Byte und für einen Code, der nicht reisen darf. Ein Grund, der kein UTF-8 ist, darfUnicodeDecodeErrorauslösen, und das ist einValueError.
Klicke auf Run: Die Abschnitte zu Lektion 4 sollten {'payload': '03 e8 64 6f 6e 65', 'parsed': {'code': 1000, 'reason': 'done'}} und dann deinen ValueError für 1005 ausgeben. Klicke dann auf Submit. Die Prüfung vergleicht close_code_allowed für jeden Code von 0 bis 5.000 mit der Regel, baut die Payloads des RFC und eine unbekannte mit einem Häkchen als Grund, akzeptiert einen 123 Byte langen Grund und lehnt einen 124 Byte langen ab, lehnt Codes wie 1004, 1005, 1006, 1015, 999, 2999 und 5000 in beide Richtungen ab, liest die unbekannten Codes 1012, 1014, 3000 und 4999 mit Gründen in UTF-8 und lehnt eine Payload aus einem Byte und einen Grund ab, der kein UTF-8 ist. Dein Ergebnis-