Who May Connect? · practice
Refuse Before the Upgrade
check_request knows who may score, and nothing asks it. By the time handle_connection runs, the library has already sent 101, so the handler is too late to refuse with a status. The decision has to run inside the
The request hook
serve takes a keyword process_request: a process_request(connection, request) once per handshake, after it has read the request and before it writes any answer. request.path is the whole request target, query included, and request.headers is the same Headers
Nonemeans “carry on”: the library answers101and calls your handler, as it always has.A response object means “send this instead”: the library writes that response, closes the connection, and never calls your handler.
GET /score, no token
process_request returns a response:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
no WebSocket connection; the handler never runs
GET /watch
process_request returns None:
HTTP/1.1 101 Switching Protocols
then frames flow, and viewer_handler runs
In words: two handshakes arrive side by side. The one to /score without a token is answered 401 with a Bearer challenge, and no /watch passes the hook, gets its 101, and becomes a viewer.
Your process_request returns None for a /watch request. What happens next?
A response the library builds
You do not write the response bytes yourself. The connection the hook receives has a connection.respond(status, text) returns a response with that status, a plain-text body holding text, and the headers the library adds (Connection: close, Content-Length, Content-Type: text/plain; charset=utf-8, a Date and a Server). The response’s headers can still be changed before you return it, and a 401 should carry a challenge, the header that tells the client how to authenticate. www-authenticate: Bearer; here:
response = connection.respond(401, "a valid scorekeeper token is needed\n")
response.headers["WWW-Authenticate"] = "Bearer"
The hook then returns response.
What does a client see? In a run on a local computer, a hook that returned such a response (with the shorter text “a valid token is needed” and a newline) made the library’s Python client raise InvalidStatus, whose message was server rejected WebSocket connection: HTTP 401. Its response had status 401, reason Unauthorized, the headers Connection: close, Content-Length: 24 (the length of that text; this lesson’s text gives 36), Content-Type: text/plain; charset=utf-8 and WWW-Authenticate: Bearer, and exactly that text as its body. The handler was never called for that /score request, and it was called for a /watch one.
What does the library's client raise when the server's hook answers 401?
A function that makes a function
The hook takes exactly two arguments, connection and request, and the library chooses them. The expected token has to reach it another way. make_handler(board) met the same need: it returns a nested handler(connection) that remembers board. make_request_check(expected) does the same with the token. It defines a nested process_request(connection, request) that calls check_request(request.path, request.headers, expected), returns None when that answer is None, and otherwise builds and returns the 401 response with its challenge. Then make_request_check returns that nested function, without calling it. The token is chosen once, when the server is built, and every handshake after that is checked against it.
In Chapter 2, check_upgrade returned the response bytes itself, status line and headers spelled out by hand. Here the decision is yours and the bytes are the library’s.
Installing it
open_server(board, *, serve=serve) becomes open_server(board, token, *, serve=serve), and its call to serve gains one keyword argument beside the process_request=make_request_check(token). Chapter 10’s main will read the token with read_token and pass it here.
The response’s text names the problem and nothing else. It never contains the expected token, and it never repeats what the client sent, which could be a real token with one typo, or a token meant for another service.
Why does the 401's body say "a valid scorekeeper token is needed" instead of quoting what was sent?
Your task
In scoreboard_server.py, write make_request_check(expected) as described, and give open_server its token process_request argument. check_request from Lesson 2 still makes the decision.
Press Run. The Lesson 3 section builds your hook with the demo’s expected token and calls it twice with a stand-in connection. That stand-in’s respond returns a simple object with status_code, a headers body, not the library’s response, so that the demo needs no server; the run above shows the real one. A finished version, run on the site, printed:
Lesson 3: the request hook
/score without a token: 401, WWW-Authenticate: Bearer, body 'a valid scorekeeper token is needed\n'
/watch: None (the handshake goes on to 101)
Then press Submit.
A scorekeeper without the token is now refused before 101, and viewers still need nothing. Next lesson asks a different question about a handshake: not who sent it, but which web page made the browser send it.
Task
In scoreboard_server.py:
Write
make_request_check(expected). Inside it, defineprocess_request(connection, request): callcheck_request(request.path, request.headers, expected); when it returnsNone, returnNone; otherwise callconnection.respond(status, "a valid scorekeeper token is needed\n")once, setresponse.headers["WWW-Authenticate"] = "Bearer"on what it returns, and return that response.make_request_checkreturns the nestedwithout calling it. Change
open_server(board, *, serve=serve)toopen_server(board, token, *, serve=serve), and addprocess_request=make_request_check(token)to itsserve(...)call, keeping the handler,"127.0.0.1",0 and the three values.
Press Run: the Lesson 3 section prints 401, WWW-Authenticate: Bearer and the body text for /score without a token, and None for /watch.
Then press Submit. The check calls your hook the way the library does, with a stand-in connection whose respond returns a real library response, and with expected tokens you have not seen. A /score respond call and a 401 response whose WWW-Authenticate header names Bearer and whose body and headers hold neither the expected token nor the /watch must give None without calling respond. The hook must not call send or close: before 101 there is no check_request with one that refuses everything, to see that the hook asks it. open_server gets a stand-in serve, and the process_request it passes must behave as above for the token open_server was given. Any body text without a token is fine, and so is the header name in any case.