0%

Eindproject: een live scorebord · eindproject

Eindproject: Maak de client-CLI af

Tot nu toe stond je aan de kant van de server van het scorebord, en besliste je wat je stuurde, wanneer je weigerde en hoe je sloot. Deze les zet je aan de andere kant van je eigen protocol. Alles wat de server beslist, bereikt een client als een bericht, een of een , en een client voor de moet elk daarvan omzetten in een exitstatus waar een persoon, of een script, op kan handelen.

scoreboard_client.py is half geschreven. client_parser() declareert de twee opdrachten, watch en score, en hun opties. De meegeleverde main(argv=None, *, connect=connect) parset ze, weigert een slechte opdrachtregel met parser.error (exitstatus 2: --points buiten 1 tot en met 10, een lege --team, een --wait buiten bereik), leest bij score het tokenbestand en geeft 1 terug met één regel op standard error als dat niet lukt, en geeft daarna terug wat watch(...) of score(...) teruggeeft. format_snapshot(snapshot) maakt van een snapshot één regel, met de teams in de volgorde waarin het bericht ze opsomt.

Wat drukt dit af?

snapshot = {"version": 3, "scores": {"Owls": 4, "Foxes": 0}}
parts = []
for team in snapshot["scores"]:
    parts.append(f"{team} {snapshot['scores'][team]}")
line = f"version {snapshot['version']}: " + ", ".join(parts)
print(line)

Nooit achteruit

watch(url, max_messages, wait, *, connect=connect) maakt verbinding, drukt met format_snapshot één regel per snapshot af, en stopt na max_messages regels. Hij past de regel toe uit de les van hoofdstuk 8 over opnieuw verbinden: een client toont nooit een bord dat ouder is dan een bord dat hij al heeft getoond. Onthoud de versie van de laatst afgedrukte snapshot, begin onder elke echte versie (-1 werkt), en sla een snapshot over waarvan de versie niet groter is. Alleen afgedrukte regels tellen mee voor --max-messages.

De server stuurt versies al op volgorde, maar de client vertrouwt niet alleen op de server: hij houdt de regel aan, ook als een server, een proxy of een latere versie van je eigen code dat niet doet. Niet elk bericht is een snapshot. Een geldige error-envelop is bedoeld voor een scorebijhouder, dus watch slaat die over. Een score-envelop breekt het protocol, omdat alleen de scorebijhouder scores stuurt, en dat geldt ook voor alles wat parse_message weigert.

watch heeft versie 7 afgedrukt en ontvangt daarna een snapshot met versie 6. Wat doet hij?

Hoe het eindigde, bepaalt de exitstatus

Elke manier waarop het gesprek kan eindigen, heeft in het contract één exitstatus. Voor watch:

Hoe het gesprek eindigdeExitstatus
max_messages regels afgedrukt0
een close met 1000 of 10010
een close met een andere code1
de verbinding eindigde zonder close- (exc.rcvd is None, de 1006 uit hoofdstuk 6)1
de werd geweigerd (InvalidStatus, met de status in exc.response.status_code)1
geen snapshot binnen wait seconden, of geen antwoord op de handshake op tijd (TimeoutError)1
er luistert niets op die plek (ConnectionRefusedError, of een andere OSError)1
een bericht dat het protocol breekt: eerst sluiten met 10081
een geldige error-envelopovergeslagen

Elke exit met 1 schrijft precies één regel naar standard error die zegt waarom, met print(..., file=sys.stderr); standard output bevat alleen snapshotregels. Een geweigerde handshake noemt zijn status, zoals in HTTP 503.

Waarom 1008 voor een slecht bericht? RFC 6455 sectie 7.4.1 maakt 1008, policy violation, de algemene code als er geen specifiekere past. 1003 is voor een soort data die het eindpunt niet kan accepteren, zoals een binair frame, en ongeldige laat de bibliotheek zelf al falen met 1007. Geldige die de regels van het scorebord breekt, is een kwestie van het beleid van dit protocol, net als de eigen close no such path van de server.

Twee feiten bepalen de volgorde van je except-clausules. TimeoutError is een soort OSError, dus zijn clausule moet vóór een except OSError: komen die hem anders met de verkeerde melding zou vangen. En InvalidStatus is helemaal geen OSError: het is een WebSocketException, dus het heeft een eigen clausule nodig. Geef open_timeout, close_timeout en proxy=None mee aan connect, zoals in hoofdstuk 6, en geef het wachten mee aan elke recv, zodat geen stille server de client eeuwig kan vasthouden.

Waarom moet except TimeoutError: in watch vóór except OSError: komen?

Het token gaat in een header

score(url, token, team, points, *, connect=connect) stuurt één score en meldt het antwoord. Het token reist mee in de handshake, als extra header:

headers = {"Authorization": f"Bearer {token}"}
with connect(url, additional_headers=headers, open_timeout=WAIT, close_timeout=WAIT, proxy=None) as connection:
    connection.send(text)

Op een lokale computer las de process_request-hook van een server precies één , Bearer en het token, uit een handshake die zo was verstuurd. Hoofdstuk 9 accepteerde ook ?token= in de , maar alleen voor een browserpagina, die de header niet kan toevoegen. Deze client is geen browser, en een URL is precies het soort tekst dat servers en proxy’s in hun logs schrijven.

Bouw de score-envelop met scoreboard_messages.encode_message, stuur hem één keer, en lees één antwoord met recv(timeout=WAIT). Een snapshot als antwoord wordt afgedrukt met format_snapshot, exit 0. Een error-envelop, een geweigerde handshake, een close vóór het antwoord, een time-out of geen verbinding is één regel op standard error, exit 1. De regel voor een geweigerde handshake noemt de status, zoals HTTP 401, en nooit het token: het token verschijnt op geen enkele stream.

watch maakt niet opnieuw verbinding als zijn verbinding wegvalt. Opnieuw verbinden met backoff, uit hoofdstuk 8, blijft iets wat je kunt herkennen in plaats van iets wat deze client doet: hij eindigt, en zijn exitstatus zegt hoe.

Waarom stuurt score het token in een Authorization-header en niet als ?token= in de URL?

Je opdracht

Schrijf watch en score. Het grootste deel van elk is except-clausules. Draai daarna zeven opdrachten van je client tegen een scorebord dat voor deze cursus is geschreven, zodat een fout die je ziet van de client is en niet van je server:

python pl_ws.py client-main

Een afgemaakte versie, op de site uitgevoerd, drukte na de met opdrachten af:

1 watch | version 0: Owls 0, Foxes 0, Herons 0
1 watch | version 1: Owls 3, Foxes 0, Herons 0
1 watch | version 2: Owls 3, Foxes 2, Herons 0
1 watch exited with 0
2 score | version 1: Owls 3, Foxes 0, Herons 0
2 score exited with 0
3 score | version 2: Owls 3, Foxes 2, Herons 0
3 score exited with 0
4 wrong token ! the scoreboard refused the connection: HTTP 401
4 wrong token exited with 1
5 unknown team ! the scoreboard refused the score: unknown team
5 unknown team exited with 1

Regels met | gingen naar standard output en regels met ! naar standard error. Opdracht 6, --points 11, wordt door de meegeleverde main geweigerd met zijn gebruiksaanwijzing en exitstatus 2, en opdracht 7 kijkt naar een waar niets luistert en stopt met 1. Je eigen regels op standard error mogen het anders zeggen. Draai daarna de tests en vraag om hun exitstatus:

python -m pytest -q
echo $?

De zes tests met assert False falen nog steeds, dus echo $? drukt 1 af: je hebt nog voor geen van beide programma’s een eigen test geschreven. Dat is les 4.

Opdracht

Schrijf in scoreboard_client.py twee . De meegeleverde main weigert al een slechte met exitstatus 2 en een onleesbaar tokenbestand met 1; laat hem, client_parser, read_token en format_snapshot zoals ze zijn.

  1. watch(url, max_messages, wait, *, connect=connect): maak verbinding met connect(url, open_timeout=WAIT, close_timeout=WAIT, proxy=None) in een with-blok; roep voor elk bericht connection.recv(timeout=wait) aan; druk een snapshot alleen af met format_snapshot als zijn versie groter is dan de laatst afgedrukte, en geef 0 terug na max_messages regels. Sla een geldige error-envelop over. Roep voor alles wat scoreboard_messages.parse_message weigert, en voor een score-envelop, connection.close(1008, ...) aan en geef daarna 1 terug. Geef 0 terug voor een close met 1000 of 1001, en 1 voor elke andere close, voor exc.rcvd gelijk aan None, voor InvalidStatus, TimeoutError en elke andere OSError.

  2. score(url, token, team, points, *, connect=connect): stuur één score-envelop, gebouwd met scoreboard_messages.encode_message, op een verbinding die is geopend met additional_headers={"Authorization": f"Bearer {token}"}, open_timeout=WAIT, close_timeout=WAIT en proxy=None; lees één antwoord met recv(timeout=WAIT). Druk een snapshot als antwoord af met format_snapshot en geef 0 terug. Geef 1 terug voor een error-envelop, InvalidStatus, ConnectionClosed, TimeoutError en elke andere OSError.

Elke keer dat 1 wordt teruggegeven, wordt er precies één regel naar standard error geschreven, en de regel van een geweigerde noemt exc.response.status_code. Standard output bevat alleen snapshotregels. Geen van beide functies drukt ooit het token af of zet het in de .

Draai python pl_ws.py client-main en python -m pytest -q. Klik daarna op Check my work. Die draait de controles van les 1 en 2 opnieuw, en roept daarna de meegeleverde main aan met een stand-in-connect waarvan de verbinding gescripte berichten uitdeelt, elke time-out van recv vastlegt en send en close vastlegt; geweigerde handshakes zijn de eigen InvalidStatus van de bibliotheek. watch --max-messages 3 --wait 1.5 die versies 5, 4, 5, 6, 7, 8 krijgt, moet versies 5, 6 en 7 afdrukken en 0 teruggeven, met proxy=None en het wachten bij elke recv. Elk einde in de tabel van deze les moet zijn exitstatus geven, met precies één regel op standard error voor elke 1 en een close met 1008 voor een bericht dat het protocol breekt. score moet verbinding maken met de opgegeven URL, precies één Authorization- sturen, Bearer en een token dat je nog niet hebt gezien, één score sturen waarvan de gelijk is aan het opgegeven team en de opgegeven punten, het snapshotantwoord afdrukken, en 1 teruggeven voor de mislukkingen hierboven, met 401 genoemd voor een geweigerde handshake. Het token mag op geen enkele stream en in geen enkele URL verschijnen. Elke formulering op standard error is prima, de header mag een , een van paren of een Headers zijn, met de naam in hoofd- of kleine letters, en de time-out mag op positie worden meegegeven.