Um einen Protokollwechsel bitten · Übung
Das Upgrade annehmen oder ablehnen
Dein Server kann eine Anfrage lesen und den Accept-check_upgrade(request), die das
Das Ja
Für die Beispielanfrage des RFC lautet die Antwort des Servers so, jede Zeile endet mit \r\n:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Der Status 101 Switching Protocols gehört zur Klasse 1xx, die du in Upgrade: websocket und Connection: Upgrade wiederholen, worum der Client gebeten hat, jetzt als Zustimmung des Servers. Sec-WebSocket-Accept trägt den Wert aus Lektion 3. Dann kommt die Leerzeile, und sonst nichts: keine Content-Length, kein Body.
Nach dieser Leerzeile ist HTTP auf dieser Verbindung beendet. Die Bytes, die folgen, in beide Richtungen, sind WebSocket-
Was darf der Server direkt nach der Leerzeile senden, die seine 101-Antwort beendet?
Dein Server sendet in seiner 101 nie Sec-WebSocket-Protocol oder Sec-WebSocket-Extensions, weil dieser Kurs keins von beiden aushandelt.
Tokens, keine exakten Werte
Bevor er Ja sagt, prüft der Server, dass der Client wirklich gefragt hat. Der Upgrade-Header muss das Token websocket enthalten, und Connection muss das Token upgrade enthalten, beides ohne Rücksicht auf Groß- und Kleinschreibung. Lektion 1 hat gezeigt, warum ein exakter Vergleich scheitert: Connection: keep-alive, Upgrade bittet genauso um das Upgrade wie Connection: Upgrade.
has_token(value, token) beantwortet die Frage für jeden durch Kommas getrennten Header-Wert. Schneide den Wert an jedem Komma, entferne bei jedem Stück die Leerzeichen drumherum, wandle es in Kleinbuchstaben um und vergleiche es mit dem Token, ebenfalls in Kleinbuchstaben:
Was gibt das aus?
value = "keep-alive, Upgrade"
for item in value.split(","):
print(item.strip().lower())
Es ist verlockend, stattdessen "upgrade" in value.lower() zu schreiben. Das findet upgrade aber auch in upgrades und in jedem längeren Wort, das es enthält. Der Vergleich ganzer Tokens findet nur das Token, also ist has_token("upgrades", "upgrade") gleich False.
Den Schlüssel prüfen
Ein Schlüssel muss das base64.b64decode sagt es dir, wenn du ein ! vor dem des RFC, !!!!dGhlIHNhbXBsZSBub25jZQ==, trotzdem zu den 16 Bytes der Beispiel-Nonce. Mit validate=True lehnt es diesen Schlüssel ab und löst binascii.Error aus. Fehlende Auffüllung löst in beiden Fällen denselben Fehler aus.
binascii.Error ist eine Art von except ValueError: um das Dekodieren herum ihn ab. Prüfe nach einem erfolgreichen Dekodieren die Länge mit len: dGhl ist gültiges base64, dekodiert aber nur zu 3 Bytes.
Denk an den doppelten Schlüssel aus Lektion 2. Zwei Sec-WebSocket-Key-Zeilen kommen als ein Wert mit ", " in der Mitte an, und ein Komma und ein Leerzeichen sind keine base64-Zeichen, also lehnt diese Prüfung auch diese Anfrage ab.
Zwei Arten, Nein zu sagen
RFC 6455 erlaubt einem Server, einen
400 Bad Request beantwortet jede Anfrage, die fehlerhaft oder unvollständig ist. check_upgrade gibt eine 400 zurück, wenn:
die
nicht GETist;die Version nicht genau
HTTP/1.1ist;es keinen
host-Header gibt;upgradekeinwebsocket-Token hat oder fehlt;connectionkeinupgrade-Token hat oder fehlt;sec-websocket-keyodersec-websocket-versionfehlt;der Schlüssel kein striktes base64 ist;
der Schlüssel nicht zu 16 Bytes dekodiert.
Der RFC erlaubt jede Version ab HTTP/1.1, aber keine spätere HTTP-Version verwendet diesen Handshake, also akzeptiert dein Server genau HTTP/1.1.
426 Upgrade Required beantwortet eine Anfrage, die vollständig und wohlgeformt ist, aber nach einer anderen WebSocket-Version als 13 fragt. Sie trägt einen Sec-WebSocket-Version: 13-Header, der dem Client sagt, welche Version dieser Server spricht, sodass ein Client, der mehrere Versionen kennt, es mit dieser erneut versuchen kann. Prüfe die Version zuletzt, nachdem jede 400-Regel bestanden ist. Verwechsle sie nicht mit 505 HTTP Version Not Supported, die sich auf die HTTP-Version in der Anfragezeile bezieht, nicht auf die WebSocket-Version. RFC 6455 schlägt hier 426 vor und verlangt nur den Sec-WebSocket-Version-Header, also ist 426 die Wahl dieses Kurses: Sie nennt den Grund der Ablehnung.
Beide Ablehnungen sind 4xx-Statuscodes, die Klasse, die sagt, dass der Fehler bei der Anfrage des Clients liegt. refuse(status) baut beide: die Statuszeile, bei 426 den Versions-Header, dann Content-Length: 0 und Connection: close, dann die Leerzeile. Content-Length: 0 sagt, dass kein Body folgt, und Connection: close sagt, dass der Server die Verbindung nach dieser Antwort schließt. Die beiden Antworten sind:
HTTP/1.1 400 Bad Request
Content-Length: 0
Connection: close
HTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13
Content-Length: 0
Connection: close
Ein echter Server kann auch mit anderen Statuscodes ablehnen. Das eigene Beispiel des RFC für eine nicht passende Version antwortet mit 400 Bad Request und einem Sec-WebSocket-Version-Header, und ein Server, der prüft, woher eine Verbindung kommt, könnte mit 403 Forbidden antworten, wie es Kapitel 9 tut. Diese beiden decken alles ab, was dein Server jetzt prüft.
Eine Anfrage ist vollständig und gültig, bis auf Sec-WebSocket-Version: 8. Was gibt check_upgrade zurück?
RFC 6455 sagt nicht, in welcher Reihenfolge ein Server diese Regeln prüfen muss, also könnte eine Anfrage mit zwei Fehlern berechtigterweise jeden der beiden Statuscodes bekommen. Dein Server prüft in der Reihenfolge der
Deine Aufgabe
Schreibe in ws_handshake.py diese drei
has_token(value, token):True, wenn irgendein durch Kommas getrenntes Element vonvalue, ohne Leerzeichen drumherum und in Kleinbuchstaben, gleichtokenin Kleinbuchstaben ist.refuse(status): die vollständige 400- oder 426-Antwort von oben, als Bytes.check_upgrade(request): eine 400 oder eine 426 nach den Regeln oben, sonst die 101-Antwort mitaccept_valuedes Schlüssels, als Bytes. Verwendeheaders.get(name, "")für einen Header, der fehlen könnte.
Klicke auf Run. Der Abschnitt zu Lektion 4 gibt die 101-Antwort für die Anfrage des RFC als Bytes-Wert aus, mit allen \r\n:
Lesson 4: check_upgrade
b'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n'
Diese Zeile stammt aus einem lokalen Lauf der fertigen Lektion. Wenn deine übereinstimmt, klicke auf Submit. Die Serverseite des Handshakes ist fertig; die nächste Lektion schreibt den Client, der fragt.
Aufgabe
Schreibe drei ws_handshake.py, in dieser Reihenfolge. Submit nennt die erste, die noch NotImplementedError auslöst, sodass du sie eine nach der anderen angehst.
has_token(value, token)gibtTruezurück, wenn irgendein Element des durch Kommas getrenntenvalue, ohne Leerzeichen drumherum und in Kleinbuchstaben, gleichtokenin Kleinbuchstaben ist, und sonstFalse. Ein Token innerhalb eines längeren Worts zählt nicht:has_token("upgrades", "upgrade")istFalse.refuse(status)gibt Bytes zurück: bei 400HTTP/1.1 400 Bad Request; bei 426HTTP/1.1 426 Upgrade Required, gefolgt vonSec-WebSocket-Version: 13; dannContent-Length: 0,Connection: closeund die Leerzeile. Jede Zeile endet mit\r\n.check_upgrade(request)nimmt einin der Form, die parse_upgrade_requestzurückgibt, und gibt Bytes zurück. Es gibtrefuse(400)zurück, wenn dienicht GETist, die Version nicht genauHTTP/1.1ist,hostfehlt,upgradekeinwebsocket-Token hat,connectionkeinupgrade-Token hat, der Schlüssel oder der Versions-Header fehlt oder der Schlüssel kein striktes( base64.b64decode(key, validate=True)) von genau 16 Bytes ist. Andernfalls gibt esrefuse(426)zurück, wenn die Version nicht13ist, und die 101-Antwort, wenn sie es ist:HTTP/1.1 101 Switching Protocols,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept:undaccept_value(key), dann die Leerzeile und nichts danach.
Lass die Funktionen für die Lektionen 5 und 6, wie sie sind. Klicke auf Run, um die 101-Antwort für die Anfrage des RFC zu sehen.
Klicke dann auf Submit. Die Prüfung ruft has_token mit check_upgrade mit Anfrage-Dictionaries, die sie selbst baut, also hängt sie nicht von deinem Parser ab: die Anfrage des RFC, eine gültige Anfrage mit einem Schlüssel, den du nicht gesehen hast, und den Tokens in anderer Schreibweise, zehn Anfragen, die eine 400 bekommen müssen, jede mit genau einem Fehler, und eine, die eine 426 bekommen muss. Sie liest jede Antwort so, wie ein Client es tun würde: die Statuszeile, die Header nach ihrem Namen in Kleinbuchstaben, die Leerzeile am Ende und entweder keinen Body oder einen Body, der so lang ist wie seine Content-Length. Die Reihenfolge der Header spielt keine Rolle, und eine Ablehnung darf zusätzliche Header oder einen kurzen Body mit der richtigen Content-Length tragen; eine 101 darf weder einen Body noch einen Protokoll- oder Extensions-Header tragen. Das Importieren von ws_handshake.py darf nichts ausgeben.