Chapter 9
Who May Connect?
What a Browser Client Looks Like
Scoreboard v2 keeps every viewer current and drops viewers that stop answering. It also lets anyone who reaches /score change the scores: scorekeeper_handler applies a score from whoever connected, and nothing asks who that is. This chapter makes the server ask. First, look at who would connect. The scoreboard’s real viewers would be web pages, and a web page speaks
A viewer page and a scorekeeper page
Here is the part of a viewer page that talks to the scoreboard, 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);
};
And the part of a scorekeeper page that sends a score:
const keeper = new WebSocket("ws://127.0.0.1:8765/score");
keeper.onopen = () => {
keeper.send(JSON.stringify({type: "score", team: "Owls", points: 3}));
};
These snippets were written for this lesson. They are not served or run anywhere on this site, and nothing here can connect a browser to the server in your workspace; you only need to read them. Each line has a partner in the Python client you wrote in Chapter 6:
new WebSocket(url)plays the part ofconnect(url): it opens the, sends the upgrade request and checks the 101and its accept. It does not wait for any of that, though. It returns at once, and the browser calls the page’s onopenwhen the is done. That is why the scorekeeper page sends from inside onopen.socket.onmessage = ...hands the browser a function. Where your Python client calledrecvin a, the page never asks for a message: the browser calls this function once for each message that arrives, with the message’s text in event.data.JSON.parseandJSON.stringifyarejson.loadsandjson.dumps. The envelopes are the same ones Chapter 7 defined: asnapshotcomes in, ascoregoes out.sendissend.
showScores is the page’s own drawing function, not part of the WebSocket API.
A browser's viewer page does this:
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);
};
What does onmessage correspond to in your Python client?
What the browser decides for you
Your Python client could add any header it liked to the upgrade request. A web page cannot. The browser’s WebSocket constructor takes a Host, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, any Origin header naming the page that opened the connection, which Lesson 4 is about. The page’s script has no way to add a header to that request.
So Authorization: Bearer <token>, the header you read on every write in
Why can a browser's scorekeeper page not send Authorization: Bearer ... with its WebSocket handshake?
Where the token goes instead
The page does choose the URL, so a token can travel in the query
ws://127.0.0.1:8765/score?token=<the token>
The library hands the server the whole request target, query included: connection.request.path for that handshake is /score?token=<the token>, which is why handle_connection already cuts it with urlsplit before comparing paths. Next lesson’s check accepts the token from either place: a Bearer header, from a program that can send one, or a token value in the query, from a page that cannot. A token with characters that are special in URLs must be percent-encoded there, as any query value is.
The query has a cost. URLs get written down: in the server’s access log (and in a library’s own debug log, when that is turned on), in the logs of any proxy on the way, in the browser’s history, in the developer tools. A token in a URL can be read later by anyone who can read those places. So a token that travels in the query should be short-lived: issued for one session and expired soon after, so that a copy found in a log is already useless.
The other route is a cookie. The browser sends the cookies it holds for the server’s site with the handshake, as it does with any request, so the page’s script never touches the token at all. The catch is that the browser may attach the cookie on its own even when the page that opened the connection belongs to someone else. Lesson 4 looks at that problem.
What is the main cost of putting the token in the query string?
Optional: watch a browser client
This step is not checked. If you want to see a real browser client at work, open your browser’s developer tools on a page of this site that shows the 101 that answered it, and the
Next lesson writes the check itself: a function that finds the token in a Bearer header or in the query, compares it with the expected one, and answers 401 before any 101 is sent.