Vragen om van protocol te wisselen · oefening
Het upgradeverzoek parsen
De vorige les las het upgradeverzoek van de RFC met het oog. Je server moet het met code lezen: de bytes van een verzoekkop omzetten in een
Het project
Vanaf deze les staat de code van het hoofdstuk in een project op de servers van Python Land, en de bestanden blijven van les tot les bewaard. De projecteditor toont twee bestanden:
ws_handshake.pyis waar je werkt. Het bevat de constantenGUIDenMAX_HEAD, twee afgewerkte, en een stub voor elke functie die dit hoofdstuk vraagt, waarvan de docstring de les noemt die hem schrijft. handshake_demo.pyis het programma dat de knop Run start. Het roept de functie van elke les aan op het voorbeeld van de RFC en drukt het resultaat af, ofnot written yetvoor een stub. Het opent geen verbinding tot les 6.
De afgewerkte functies ken je. make_reader(sock) omhult een read_head(reader) is je functie uit HTTP with Python les 1.3: hij geeft de bytes vóór de lege regel terug, zonder de \r\n\r\n, en houdt wat erna komt in de buffer. Dat is de kop die je parser ontvangt.
Van bytes naar regels
De eerste stappen zijn die uit HTTP with Python les 1.4. Decodeer de bytes met "iso-8859-1", dat elke byte in één teken omzet en nooit faalt, en splits de tekst bij "\r\n". De eerste regel is de verzoekregel; de rest zijn
Knip de verzoekregel bij enkele spaties in drie delen met split(" ", 2): methode, pad en versie. Het getal 2 betekent hooguit twee knippen, dus een versie met iets erachter, zoals in GET /chat HTTP/1.1 extra, blijft in het derde deel, waar je het kunt zien en weigeren.
Headers in een dict
Een headerregel is een naam, een dubbele punt en een Host-header met een Host: example.com:8080, heeft er een in zijn waarde.
Wat drukt dit af?
print("Host: example.com:8080".split(":", 1))
De waarde houdt de spatie die na de dubbele punt kwam, en ze kan ook spaties aan het eind hebben, dus pas .strip() erop toe. Headernamen worden vergeleken zonder op hoofdletters te letten, dus sla elke naam op met .lower(). Laat de hoofdletters van de waarden met Upgrade: WebSocket houdt zijn hoofdletters in de dictionary, en les 4 vergelijkt tokens zonder op hoofdletters te letten als ze ze controleert.
parse_headers(lines) doet dit voor een lijst headerregels. parse_upgrade_request(data) decodeert en splitst de kop, knipt de verzoekregel, geeft de andere regels aan parse_headers, en geeft één dictionary terug:
{"method": ..., "path": ..., "version": ..., "headers": {...}}
Een verzoek zonder enige headerregel geeft "headers": {}, dus parse_headers([]) geeft een lege dictionary terug.
Een naam die twee keer voorkomt
HTTP laat een afzender één header over meerdere regels met dezelfde naam verdelen. Connection: keep-alive op de ene regel en Connection: Upgrade op een andere betekenen hetzelfde als Connection: keep-alive, Upgrade. RFC 9110 zegt dat een ontvanger ze zo mag samenvoegen: de waarden in de volgorde waarin ze aankwamen, verbonden door een komma en een spatie.
In HTTP with Python hield je antwoordparser voor elke naam een lijst waarden bij. Deze cursus bewaart in plaats daarvan één string en voegt de waarden van een herhaalde naam samen met ", ", omdat elke header die de Upgrade-token in de tweede Connection-regel is het token waar het om gaat.
Een verzoek heeft Connection: keep-alive en, twee regels later, Connection: Upgrade. Wat moet headers["connection"] bevatten?
Samenvoegen heeft nog een effect waar RFC 6455 op rekent. Een verzoek mag Sec-WebSocket-Key maar één keer bevatten. Als een client hem twee keer stuurt, bevat je dictionary twee sleutels verbonden door ", ", wat geen geldige sleutel is, en de controle van les 4 weigert hem zonder een eigen regel daarvoor.
Wat telt als misvormd
Je parser weigert wat hij niet kan lezen in plaats van te gokken. Werp
een verzoekregel die bij enkele spaties niet in precies drie delen uiteenvalt, zoals
GET /chat, ofGET /chat HTTP/1.1 extra, waar het derde deel een spatie bevat;een versie die niet met
HTTP/begint, zoalsFTP/1.0;een headerregel zonder dubbele punt;
een headerregel waarvan de naam leeg is of een spatie bevat, zoals
: xofBad Name: x.
Geef elke ValueError een melding die de geweigerde regel citeert. Echte servers verschillen in wat ze toelaten; sommige accepteren twee spaties achter elkaar in de verzoekregel. Een strikte parser weigert met een reden, en niets wat hij accepteert, is gegokt.
Je opdracht
Schrijf in ws_handshake.py parse_headers(lines) en daarna parse_upgrade_request(data), zoals hierboven beschreven. Laat de functies voor les 3 tot en met 6 zoals ze zijn. Druk daarna op Run. Met je parser op zijn plaats drukt handshake_demo.py de dictionary voor het verzoek van de RFC af, en de secties van de latere lessen zeggen nog not written yet:
Lesson 2: parse_upgrade_request
{'method': 'GET', 'path': '/chat', 'version': 'HTTP/1.1', 'headers': {'host': 'server.example.com', 'upgrade': 'websocket', 'connection': 'Upgrade', 'sec-websocket-key': 'dGhlIHNhbXBsZSBub25jZQ==', 'origin': 'http://example.com', 'sec-websocket-protocol': 'chat, superchat', 'sec-websocket-version': '13'}}
Lesson 3: accept_value
not written yet
Lesson 4: check_upgrade
not written yet
Lesson 5: build_upgrade_request
not written yet
Lesson 6: run_loopback is not written yet
Die uitvoer komt uit een lokale run van de afgewerkte les. Een naam met hoofdletters of een waarde met spaties eromheen wijst op .lower() of .strip().
Als de dictionary er goed uitziet, druk je op Submit. De controle roept je twee functies aan op verzoekkoppen die je nog niet hebt gezien, met ongebruikelijke hoofdletters, opgevulde waarden, een herhaalde naam en een dubbele punt in een waarde, en op koppen die geweigerd moeten worden.
Niets in de dictionary zegt al of de server het verzoek moet accepteren. De volgende les berekent de ene waarde waar het antwoord van de server niet zonder kan.
Opdracht
Schrijf twee ws_handshake.py, in deze volgorde. Submit noemt de eerste die nog NotImplementedError opwerpt, dus je krijgt ze één voor één.
parse_headers(lines)neemt een( ) aan en geeft een terug. Knip elke regel alleen bij zijn eerste dubbele punt; de naam gaat erin in kleine letters, de met de spaties eromheen weggestript. Als een naam opnieuw voorkomt, voeg je de nieuwe waarde met ", "aan de opgeslagen waarde toe, op volgorde. Werpop voor een regel zonder dubbele punt, en voor een naam die leeg is of een spatie bevat. parse_headers([])geeft{}terug.parse_upgrade_request(data)neemt de kop aan als bytes, zoalsread_headhem teruggeeft: alles vóór de lege regel, zonder de laatste\r\n\r\n. Decodeer hem met"iso-8859-1", splits hem bij"\r\n", en knip de eerste regel metsplit(" ", 2). WerpValueErrorop tenzij dat precies drie delen geeft, het derde geen spatie bevat en metHTTP/begint. Geef{"method": ..., "path": ..., "version": ..., "headers": parse_headers(the other lines)}terug.
Laat de functies voor les 3 tot en met 6 zoals ze zijn. Druk op Run om je dictionary voor het verzoek van de RFC te zien.
Druk daarna op Submit. De controle roept beide functies rechtstreeks aan, zonder Connection-header en een dubbele punt in een waarde, en op verzoekregels en headerregels die ValueError moeten opwerpen. Ze controleert de waarden in je dictionary, niet de volgorde van de sleutels. Het importeren van ws_handshake.py mag niets afdrukken.