0%

Who May Connect? · capstone

Capstone project: Project: Scoreboard v3

Only the token holder can score now, and the token is checked before 101. One rule from version one is still in place: when a scorekeeper sends anything that is not a valid score, scorekeeper_handler closes the connection with 1008 and "that was not a valid score". Run the demo before you change anything, and the Lesson 5 section ends exactly that way:

Lesson 5: the scorekeeper's mistakes
    then the connection was closed with [(1008, 'that was not a valid score')]

The demo’s scorekeeper sent points 11 first, and that was the end of its conversation. This project finishes scoreboard v3: mistakes get answers instead of a closed connection, and the token is repeated nowhere.

Requirements

Scoreboard v3, as you leave it at the end of this lesson:

  1. Scores come only from the token holder, checked before 101 by the hook from Lesson 3. Viewers need no token.

  2. error_message(board, reason) returns {"type": "error", "version": <the board's current version>, "reason": reason}. It reads board["version"] while it holds board["lock"], as every other read of the board does.

  3. scorekeeper_handler answers every with an error envelope and keeps reading. A ValueError can come from three places: parse_message, for a binary message, text that is not or an invalid envelope; the type check, for a valid envelope that is not a score; and apply_score, for a team the board does not have. The reason is str(exc), the exception’s message.

  4. Everything else stays: at most MAX_SCORE_MESSAGES messages, counting the invalid ones; recv(timeout=WAIT), with a close 1000 and "no score arrived in time" when nothing arrives; a valid score answered with the new snapshot; a quiet return on ConnectionClosed; 1000 once the limit is reached.

  5. No reply, envelope, close reason or printed line contains the token.

Chapter 10’s main will build the server with open_server(board, read_token("token.txt")), so everything above is what a running scoreboard does.

In scoreboard v3, the scorekeeper sends points 11 while the board is at version 5. What comes back?

Mistakes are messages now

A scorekeeper is a person at a keyboard, or a page they use, and people mistype. Under version one, a typed 11 cost the scorekeeper its connection: its page would have to notice the close, reconnect, send the token again, and try again, and the close reason alone said nothing about which part was wrong. An error envelope keeps the conversation going and says what was wrong in its reason, and the scorekeeper’s next message is read as if nothing had happened. The board does not change, so viewers hear nothing about it: an error goes to the scorekeeper only.

The envelope carries the board’s version so that the client can tell which board the refusal is about. A scorekeeper’s page that shows “rejected at version 5” next to a board it last saw at version 5 knows its view is current; at version 7 it knows other scores arrived in between.

Reasons are written for people. str(exc) is whatever the exception said, and for text that is not JSON that is the JSON parser’s own message, Expecting value: line 1 column 1 (char 0), which is accurate and not friendly. That is acceptable for this course: no program should parse a reason, and the type and version are what a client acts on.

In scorekeeper_handler, the except ValueError clause no longer closes and returns. Name the exception in it, except ValueError as exc:, make reply the error envelope there and a snapshot in the try, and send reply after the try statement, once, whichever it is.

Never repeat a secret

The token travels in a header or in the query, and both reach code you write. A reply that repeats part of a request can repeat the token into the client’s logs, the server’s logs, or a screen someone is filming. The rule: no response, envelope, close reason or printed line contains the token. In practice:

  • the 401 body is the fixed text from Lesson 3;

  • an error’s reason comes from the ValueError, never from connection.request;

  • no print of a path or header, not even while debugging, because the path of a scorekeeper that sent its token in the query holds the token. The library’s own logging at its debug level writes the request line and headers, token included; this course never turns it on, and a real server should keep it off.

Which of these could repeat the token?

Your task

In scoreboard_server.py, write error_message(board, reason) and rewrite scorekeeper_handler to the requirements above. Keep every other as it is.

Press Run. The Lesson 5 section gives a stand-in scorekeeper four messages: points 11, the text not json, a score for a team called Bats, which is not on the demo’s board, and a valid score of 2 for Owls. A finished version, run on the site, printed:

Lesson 5: the scorekeeper's mistakes
    the scorekeeper was sent {'type': 'error', 'version': 0, 'reason': 'points must be a whole number from 1 to 10'}
    the scorekeeper was sent {'type': 'error', 'version': 0, 'reason': 'Expecting value: line 1 column 1 (char 0)'}
    the scorekeeper was sent {'type': 'error', 'version': 0, 'reason': 'unknown team'}
    the scorekeeper was sent {'type': 'snapshot', 'version': 1, 'scores': {'Owls': 2, 'Foxes': 0}}
    then the connection was closed with [(1000, 'no score arrived in time')]

Three errors at version 0, because nothing changed, then the valid score’s snapshot at version 1. The connection stayed open through all four; it closed only when the stand-in had nothing more to send and recv timed out.

Then press Submit. Scoreboard v3 is feature-complete. The capstone turns it into a program with a written contract and a .

Task

In scoreboard_server.py:

  1. Write error_message(board, reason): inside with board["lock"]:, read board["version"]; return {"type": "error", "version": version, "reason": reason}.

  2. Rewrite scorekeeper_handler(connection, board). Keep its limit of MAX_SCORE_MESSAGES messages (an invalid one counts), recv(timeout=WAIT) with connection.close(1000, "no score arrived in time") on TimeoutError, the final close(1000, ...) at the limit, and the quiet return on ConnectionClosed. Change only what a does: catch it as except ValueError as exc:, set reply = error_message(board, str(exc)), and send reply with scoreboard_messages.encode_message exactly as a valid score’s snapshot is sent. Do not close the connection for a mistake.

  3. Keep Lesson 2’s and Lesson 3’s and everything from Chapter 8 as they are.

Press Run: the Lesson 5 section prints three error envelopes at version 0, a snapshot at version 1, and a close with 1000.

Then press Submit. The check runs Lesson 2’s and Lesson 3’s checks again, with a token you have not seen, and checks that Chapter 8’s outbox and viewer still work. Then it registers two viewers and gives your scorekeeper_handler ten messages, starting on a board at a version above 0: points 11, text that is not , a binary message, an error envelope, a score for a team the board does not have, a valid score, points true, a snapshot envelope, another valid score, and a JSON , with an eleventh message waiting. Each mistake must get an error envelope with the board’s current version and a non-empty reason; the board and the viewers’ outboxes must change only for the two valid scores, which get their snapshots; the handler must read ten messages, not the eleventh, and close once with 1000. The scorekeeper’s path holds the token in its query, and the token must appear in nothing sent, no close reason and no printed line. To run without threads, the check replaces scoreboard_server.threading with its own locks and events, so keep import threading and make locks only inside functions, as make_board and make_outbox do. Any reason text is fine.