0%

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 , after the library has read the request and before it answers. The library has a place for exactly that.

The request hook

serve takes a keyword , process_request: a the library calls as 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 you read in Lesson 2. What the function returns decides what happens next:

  • None means “carry on”: the library answers 101 and 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 ever exists. The one to /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 for it: 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. with Python’s servers sent 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 values of Chapter 8: 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 and the 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 and 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:

  1. Write make_request_check(expected). Inside it, define process_request(connection, request): call check_request(request.path, request.headers, expected); when it returns None, return None; otherwise call connection.respond(status, "a valid scorekeeper token is needed\n") once, set response.headers["WWW-Authenticate"] = "Bearer" on what it returns, and return that response. make_request_check returns the nested without calling it.

  2. Change open_server(board, *, serve=serve) to open_server(board, token, *, serve=serve), and add process_request=make_request_check(token) to its serve(...) 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 with no token, a wrong header token or a wrong query token must get one respond call and a 401 response whose WWW-Authenticate header names Bearer and whose body and headers hold neither the expected token nor the that was sent. The right token, in a header or in the query, and /watch must give None without calling respond. The hook must not call send or close: before 101 there is no . It replaces 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.