Vragen om van protocol te wisselen · oefening
De helft van de client schrijven
Je server kan een verzoek parsen, beslissen en antwoorden. Nog niets stuurt hem een verzoek. Deze les schrijft de andere kant: de client die een sleutel maakt, het upgradeverzoek schrijft, en het antwoord van de server controleert voordat hij gelooft dat het protocol is omgeschakeld.
Een nieuwe sleutel
De sleutel van de client is 16 willekeurige bytes in os.urandom(16) geeft 16 bytes terug uit de bron van onvoorspelbare willekeur van het base64.b64encode maakt er de 24 tekens van een sleutel van, en .decode("ascii") maakt daar tekst van.
Een test kan een willekeurige sleutel niet controleren, omdat hij die niet van tevoren kan weten. Daarom neemt make_client_key de bron van willekeur aan als
def make_client_key(random_bytes=os.urandom):
...
Roep daarbinnen random_bytes(16) aan. Bij normaal gebruik roept dat os.urandom(16) aan. Een test geeft in plaats daarvan een andere connect=-parameter die je in Networks with Python tegenkwam: een functie die als waarde wordt meegegeven, zodat een test er zijn eigen functie voor in de plaats kan zetten.
import base64
def fixed(n):
return bytes(range(n))
key = base64.b64encode(fixed(16)).decode("ascii")
Met die functie is de sleutel bij elke run hetzelfde: de base64 van de bytes 0 tot en met 15. Een test roept make_client_key(random_bytes=fixed) aan. Hij geeft fixed mee, de functie zelf, zonder haakjes, en make_client_key beslist wanneer hij haar aanroept en met wat.
Waarom neemt make_client_key random_bytes aan als parameter?
Het verzoek
build_upgrade_request(host, path, key) schrijft het verzoek dat je in les 1 las, zonder de twee optionele regels:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Dat is het verzoek voor de host, het pad en de sleutel van de RFC; je functie zet haar eigen drie host is tekst zoals server.example.com, of een naam en een 127.0.0.1:50123 als de server niet op de standaardpoort zit, en gaat zoals gegeven in de Host-regel. path gaat zoals gegeven in de verzoekregel, query en al. Elke regel eindigt op \r\n, een lege regel beëindigt de kop, en het verzoek heeft geen body. Je client stuurt geen Origin-regel, omdat hij geen browser is, en geen Sec-WebSocket-Protocol-regel, omdat hij om geen subprotocol vraagt. Geef het verzoek gecodeerd met "ascii" terug.
Het antwoord controleren
De server antwoordt met een kop, die read_head teruggeeft zonder de laatste \r\n\r\n. check_accept_response(data, key) beslist of die kop ja betekent. Het geeft niets terug als dat zo is, en werpt
Begin met de statusregel, geknipt met split(" ", 2) zoals in HTTP/1.1 heeft helemaal geen code; controleer hoeveel delen je kreeg voordat je het tweede leest, en werp ValueError op als de code ontbreekt.
Elke andere status dan 101 betekent voor deze client dat de 426 of een 200 of een 100 Continue, een mislukte handshake. Vergelijk de code precies met "101"; een andere 1xx-status is geen omschakeling.
De woorden na de code, de reason phrase, zijn voor mensen. Een server mag 101 Web Socket Protocol Handshake schrijven in plaats van 101 Switching Protocols, en de handshake is net zo goed. Beslis op de code en de headers, nooit op de reason phrase.
Het antwoord is HTTP/1.1 426 Upgrade Required. Wat doet check_accept_response?
Een server antwoordt HTTP/1.1 101 Web Socket Protocol Handshake met de juiste headers. Is dat een succes?
Lees bij een 101 de headers met je parse_headers en controleer er drie, de serverkant van wat je in les 4 controleerde:
upgradeheeft het tokenwebsocket, viahas_token, dusWebSocketkomt erdoor enwebsocketsniet;connectionheeft het tokenupgrade, viahas_token;sec-websocket-acceptis precies gelijk aanaccept_value(key)voor de sleutel die deze client stuurde.
De laatste vergelijking let met opzet op hoofdletters. In base64 zijn a en A verschillende tekens die voor verschillende bits staan, dus een acceptwaarde die alleen in hoofdletters verschilt, is een andere waarde, en betekent dat de server het antwoord op deze sleutel niet heeft berekend.
Je opdracht
Schrijf in ws_handshake.py deze drie functies, in deze volgorde:
make_client_key(random_bytes=os.urandom): roeprandom_bytes(16)één keer aan en geef de base64 van het resultaat terug alsstr.build_upgrade_request(host, path, key): het verzoek hierboven, als bytes.check_accept_response(data, key): werpValueErrorop tenzijdataeen 101-kop is met de twee tokens en de juiste acceptwaarde.
Laat serve_handshake en client_handshake voor les 6. Druk op Run. De sectie van les 5 drukt het verzoek voor de host, het pad en de sleutel van de RFC af, als bytes:
Lesson 5: build_upgrade_request
b'GET /chat HTTP/1.1\r\nHost: server.example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n'
Die regel komt uit een lokale run van de afgewerkte les. Als die van jou overeenkomt, druk je op Submit.
Je hebt nu beide helften van de handshake, elk apart gecontroleerd. In het project dat dit hoofdstuk afsluit, ontmoeten ze elkaar over een echte verbinding.
Opdracht
Schrijf drie ws_handshake.py, in deze volgorde. Submit noemt de eerste die nog NotImplementedError opwerpt, dus je krijgt ze één voor één.
make_client_key(random_bytes=os.urandom)roeptrandom_bytes(16)één keer aan en geeft devan die bytes terug als str. Roep deaan, nooit rechtstreeks os.urandom, zodat een aanroeper zijn eigen functie kan meegeven.build_upgrade_request(host, path, key)geeft bytes terug:GET,pathenHTTP/1.1op de verzoekregel, daarnaHost:methostzoals gegeven,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Key:metkey,Sec-WebSocket-Version: 13, en de lege regel. Elke regel eindigt op\r\n. Stuur geenOrigin- en geenSec-WebSocket-Protocol-regel.check_accept_response(data, key)neemt een antwoordkop aan zoalsread_headhem teruggeeft, zonder de laatste\r\n\r\n. Decodeer hem met"iso-8859-1"en knip de statusregel metsplit(" ", 2). Werpop als de regel geen code heeft of de code niet precies 101is. Lees daarna de headers metparse_headersen werpValueErrorop tenzijupgradehet tokenwebsocketheeft,connectionhet tokenupgradeheeft, ensec-websocket-acceptprecies gelijk is aanaccept_value(key). Geef niets terug.
Laat serve_handshake en client_handshake voor les 6. Druk op Run om het verzoek voor het voorbeeld van de RFC te zien.
Druk daarna op Submit. De controle geeft make_client_key eigen functies mee die bekende bytes teruggeven en noteert waarmee ze werden aangeroepen. Ze parset de verzoeken die build_upgrade_request schrijft voor hosts en paden die je nog niet hebt gezien, dus de volgorde van de headers maakt niet uit. Ze roept check_accept_response aan op 101-koppen die moeten slagen, een ervan met een ongebruikelijke reason phrase en ongebruikelijke hoofdletters in de headers, en op koppen die ValueError moeten opwerpen: andere statussen, ontbrekende of verkeerde tokens, en acceptwaarden die niet bij de sleutel horen. Het importeren van ws_handshake.py mag niets afdrukken.