0%

Wie mag er verbinding maken? · oefening

Weiger vóór de upgrade

check_request weet wie mag scoren, en niets vraagt het hem. Tegen de tijd dat handle_connection draait, heeft de bibliotheek al 101 gestuurd, dus de handler is te laat om met een status te weigeren. De beslissing moet binnen de draaien, nadat de bibliotheek het verzoek heeft gelezen en voordat ze antwoordt. De bibliotheek heeft precies daarvoor een plek.

De request-hook

serve neemt een keyword-, process_request: een die de bibliotheek één keer per handshake aanroept als process_request(connection, request), nadat ze het verzoek heeft gelezen en voordat ze een antwoord schrijft. request.path is het hele request-target, query inbegrepen, en request.headers is hetzelfde Headers- dat je in les 2 las. Wat de functie teruggeeft, bepaalt wat er daarna gebeurt:

  • None betekent “ga door”: de bibliotheek antwoordt 101 en roept je handler aan, zoals altijd.

  • Een response-object betekent “stuur in plaats daarvan dit”: de bibliotheek schrijft dat antwoord, sluit de verbinding en roept je handler nooit aan.

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 woorden: er komen twee handshakes naast elkaar binnen. Die naar /score zonder token wordt beantwoord met 401 en een Bearer-challenge, en er bestaat nooit een . Die naar /watch komt door de hook, krijgt zijn 101, en wordt een kijker.

Je process_request geeft None terug voor een verzoek naar /watch. Wat gebeurt er daarna?

Een antwoord dat de bibliotheek bouwt

Je schrijft de bytes van het antwoord niet zelf. De verbinding die de hook ontvangt, heeft er een voor: connection.respond(status, text) geeft een antwoord terug met die status, een body in platte tekst met text, en de headers die de bibliotheek toevoegt (Connection: close, Content-Length, Content-Type: text/plain; charset=utf-8, een Date en een Server). De headers van het antwoord kun je nog veranderen voordat je het teruggeeft, en een 401 hoort een challenge te dragen, de header die de client vertelt hoe hij zich moet authenticeren. De servers van with Python stuurden www-authenticate: Bearer; hier:

response = connection.respond(401, "a valid scorekeeper token is needed\n")
response.headers["WWW-Authenticate"] = "Bearer"

De hook geeft daarna response terug.

Wat ziet een client? In een run op een lokale computer liet een hook die zo’n antwoord teruggaf (met de kortere tekst “a valid token is needed” en een newline) de Python-client van de bibliotheek InvalidStatus opwerpen, met als melding server rejected WebSocket connection: HTTP 401. Het antwoord had status 401, reden Unauthorized, de headers Connection: close, Content-Length: 24 (de lengte van die tekst; de tekst van deze les geeft 36), Content-Type: text/plain; charset=utf-8 en WWW-Authenticate: Bearer, en precies die tekst als body. De handler werd nooit aangeroepen voor dat verzoek naar /score, en wel voor een verzoek naar /watch.

Wat werpt de client van de bibliotheek op als de hook van de server 401 antwoordt?

Een functie die een functie maakt

De hook neemt precies twee argumenten, connection en request, en de bibliotheek kiest ze. Het verwachte token moet hem op een andere manier bereiken. make_handler(board) had dezelfde behoefte: hij geeft een geneste handler(connection) terug die board onthoudt. make_request_check(expected) doet hetzelfde met het token. Hij definieert een geneste process_request(connection, request) die check_request(request.path, request.headers, expected) aanroept, None teruggeeft als dat antwoord None is, en anders het 401-antwoord met zijn challenge bouwt en teruggeeft. Daarna geeft make_request_check die geneste functie terug, zonder haar aan te roepen. Het token wordt één keer gekozen, als de server wordt gebouwd, en elke handshake daarna wordt ertegen gecontroleerd.

In hoofdstuk 2 gaf check_upgrade zelf de bytes van het antwoord terug, met statusregel en headers met de hand uitgeschreven. Hier is de beslissing van jou en zijn de bytes van de bibliotheek.

Hem installeren

open_server(board, *, serve=serve) wordt open_server(board, token, *, serve=serve), en zijn aanroep van serve krijgt naast de keepalivewaarden uit hoofdstuk 8 één keyword-argument erbij: process_request=make_request_check(token). De main van hoofdstuk 10 leest het token met read_token en geeft het hier mee.

De tekst van het antwoord noemt het probleem en verder niets. Hij bevat nooit het verwachte token, en hij herhaalt nooit wat de client stuurde, wat een echt token met één typfout kan zijn, of een token dat voor een andere service bedoeld is.

Waarom zegt de body van de 401 "a valid scorekeeper token is needed" in plaats van te citeren wat er werd gestuurd?

Je opdracht

Schrijf in scoreboard_server.py make_request_check(expected) zoals beschreven, en geef open_server zijn token en het argument process_request. check_request uit les 2 neemt nog steeds de beslissing.

Klik op Run. De sectie van les 3 bouwt je hook met het verwachte token van de demo en roept hem twee keer aan met een stand-in-verbinding. De respond van die stand-in geeft een eenvoudig object terug met status_code, een headers en body, niet het antwoord van de bibliotheek, zodat de demo geen server nodig heeft; de run hierboven toont het echte. Een afgemaakte versie, op de site uitgevoerd, drukte af:

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)

Klik daarna op Submit.

Een scorebijhouder zonder het token wordt nu geweigerd vóór 101, en kijkers hebben nog steeds niets nodig. De volgende les stelt een andere vraag over een handshake: niet wie hem stuurde, maar welke webpagina de browser hem liet sturen.

Opdracht

In scoreboard_server.py:

  1. Schrijf make_request_check(expected). Definieer daarin process_request(connection, request): roep check_request(request.path, request.headers, expected) aan; als dat None teruggeeft, geef je None terug; roep anders één keer connection.respond(status, "a valid scorekeeper token is needed\n") aan, zet response.headers["WWW-Authenticate"] = "Bearer" op wat dat teruggeeft, en geef dat antwoord terug. make_request_check geeft de geneste terug zonder haar aan te roepen.

  2. Verander open_server(board, *, serve=serve) in open_server(board, token, *, serve=serve), en voeg process_request=make_request_check(token) toe aan zijn aanroep van serve(...), waarbij je de handler, "127.0.0.1", 0 en de drie keepalivewaarden houdt.

Klik op Run: de sectie van les 3 drukt 401, WWW-Authenticate: Bearer en de bodytekst af voor /score zonder token, en None voor /watch.

Klik daarna op Submit. De controle roept je hook aan zoals de bibliotheek dat doet, met een stand-in-verbinding waarvan respond een echt antwoord van de bibliotheek teruggeeft, en met verwachte tokens die je nog niet hebt gezien. Een naar /score zonder token, met een verkeerd headertoken of met een verkeerd querytoken moet één aanroep van respond krijgen en een 401-antwoord waarvan de header WWW-Authenticate Bearer noemt en waarvan de body en de headers noch het verwachte token noch de gestuurde bevatten. Het juiste token, in een header of in de query, en /watch moeten None geven zonder respond aan te roepen. De hook mag send of close niet aanroepen: vóór 101 is er geen . Ze vervangt check_request door een versie die alles weigert, om te zien dat de hook het hem vraagt. open_server krijgt een stand-in-serve, en de process_request die hij meegeeft, moet zich gedragen zoals hierboven voor het token dat open_server kreeg. Elke bodytekst zonder token is prima, en de headernaam in hoofd- of kleine letters ook.