Kapitel 9
Wer darf sich verbinden?
Wie ein Browser-Client aussieht
Anzeigetafel v2 hält jeden Zuschauer auf dem neuesten Stand und trennt Zuschauer, die nicht mehr antworten. Sie lässt aber auch jeden, der /score erreicht, die Punktestände ändern: scorekeeper_handler wendet eine Wertung von jedem an, der sich verbunden hat, und nichts fragt, wer das ist. Dieses Kapitel bringt den Server dazu zu fragen. Schau dir zuerst an, wer sich verbinden würde. Die echten Zuschauer der Anzeigetafel wären Webseiten, und eine Webseite spricht
Eine Zuschauerseite und eine Punktezählerseite
Hier ist der Teil einer Zuschauerseite, der mit der Anzeigetafel spricht, in
const socket = new WebSocket("ws://127.0.0.1:8765/watch");
socket.onmessage = (event) => {
const snapshot = JSON.parse(event.data);
showScores(snapshot.version, snapshot.scores);
};
Und der Teil einer Punktezählerseite, der eine Wertung sendet:
const keeper = new WebSocket("ws://127.0.0.1:8765/score");
keeper.onopen = () => {
keeper.send(JSON.stringify({type: "score", team: "Owls", points: 3}));
};
Diese Snippets wurden für diese Lektion geschrieben. Sie werden nirgendwo auf dieser Seite bereitgestellt oder ausgeführt, und nichts hier kann einen Browser mit dem Server in deinem Arbeitsbereich verbinden; du musst sie nur lesen. Jede Zeile hat ein Gegenstück im Python-Client, den du in Kapitel 6 geschrieben hast:
new WebSocket(url)übernimmt die Rolle vonconnect(url): Es öffnet die, sendet die Upgrade-Anfrage und prüft die 101und ihren Accept-. Es wartet aber auf nichts davon. Es kehrt sofort zurück, und der Browser ruft die onopender Seite auf, wenn derfertig ist. Deshalb sendet die Punktezählerseite aus onopenheraus.socket.onmessage = ...übergibt dem Browser eine Funktion. Wo dein Python-Clientrecvin eineraufgerufen hat, fragt die Seite nie nach einer Nachricht: Der Browser ruft diese Funktion einmal für jede ankommende Nachricht auf, mit dem Text der Nachricht in event.data.JSON.parseundJSON.stringifysindjson.loadsundjson.dumps. Die Umschläge sind dieselben, die Kapitel 7 definiert hat: Einsnapshotkommt herein, einescoregeht hinaus.sendistsend.
showScores ist die eigene Zeichenfunktion der Seite, kein Teil der WebSocket-API.
Welchem Teil deines Python-Clients entspricht onmessage im Snippet?
Was der Browser für dich entscheidet
Dein Python-Client konnte der Upgrade-Anfrage jeden Header hinzufügen, den er wollte. Eine Webseite kann das nicht. Der Konstruktor WebSocket des Browsers nimmt eine Host, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, alle Origin-Header, der die Seite nennt, die die Verbindung geöffnet hat, und um den es in Lektion 4 geht. Das Skript der Seite hat keine Möglichkeit, dieser Anfrage einen Header hinzuzufügen.
Authorization: Bearer <token>, der Header, den du in
Warum kann die Punktezählerseite eines Browsers Authorization: Bearer ... nicht mit ihrem WebSocket-Handshake senden?
Wohin das Token stattdessen kommt
Die URL wählt die Seite selbst, also kann ein Token im Query-
ws://127.0.0.1:8765/score?token=<the token>
Die Bibliothek übergibt dem Server das ganze Anfrageziel samt Query: connection.request.path ist für diesen Handshake /score?token=<the token>, und deshalb schneidet handle_connection ihn schon mit urlsplit, bevor es Pfade vergleicht. Die Prüfung der nächsten Lektion akzeptiert das Token von beiden Stellen: aus einem Bearer-Header, von einem Programm, das einen senden kann, oder als token-Wert in der Query, von einer Seite, die das nicht kann. Ein Token mit Zeichen, die in URLs eine besondere Bedeutung haben, muss dort prozentcodiert werden, wie jeder Query-Wert.
Die Query hat ihren Preis. URLs werden aufgeschrieben: im Zugriffslog des Servers (und im eigenen Debug-Log einer Bibliothek, wenn es eingeschaltet ist), in den Logs jedes Proxys unterwegs, im Verlauf des Browsers, in den Entwicklertools. Ein Token in einer URL kann später von jedem gelesen werden, der diese Stellen lesen kann. Ein Token, das in der Query reist, sollte also kurzlebig sein: für eine Sitzung ausgestellt und bald danach abgelaufen, damit eine Kopie, die in einem Log gefunden wird, schon nutzlos ist.
Der andere Weg ist ein Cookie. Der Browser sendet die Cookies, die er für die Website des Servers hat, mit dem Handshake, wie bei jeder Anfrage, also berührt das Skript der Seite das Token überhaupt nicht. Der Haken ist, dass der Browser das Cookie vielleicht von sich aus anhängt, selbst wenn die Seite, die die Verbindung geöffnet hat, jemand anderem gehört. Lektion 4 schaut sich dieses Problem an.
Was ist der wichtigste Preis dafür, das Token in den Query-String zu legen?
Optional: einem Browser-Client zuschauen
Dieser Schritt wird nicht geprüft. Wenn du einen echten Browser-Client bei der Arbeit sehen willst, öffne die Entwicklertools deines Browsers auf einer Seite dieser Website, die das 101, die sie beantwortet hat, und die
Die nächste Lektion schreibt die Prüfung selbst: eine Funktion, die das Token in einem Bearer-Header oder in der Query findet, es mit dem erwarteten vergleicht und 401 antwortet, bevor irgendeine 101 gesendet wird.