Vragen om van protocol te wisselen · oefening
De upgrade accepteren of weigeren
Je server kan een verzoek lezen en de acceptwaarde voor zijn sleutel berekenen. Nu moet hij beslissen: overschakelen naar check_upgrade(request), die de
Het ja
Voor het voorbeeldverzoek van de RFC is dit het antwoord van de server, met elke regel eindigend op \r\n:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
De status 101 Switching Protocols hoort bij de klasse 1xx die je in Upgrade: websocket en Connection: Upgrade herhalen waar de client om vroeg, nu als instemming van de server. Sec-WebSocket-Accept draagt de Content-Length, geen body.
Na die lege regel is HTTP op deze verbinding klaar. De bytes die volgen, in beide richtingen, zijn WebSocket-
Wat mag de server direct na de lege regel die zijn 101-antwoord beëindigt sturen?
Je server stuurt nooit Sec-WebSocket-Protocol of Sec-WebSocket-Extensions in zijn 101, omdat deze cursus over geen van beide onderhandelt.
Tokens, geen exacte waarden
Voordat hij ja zegt, controleert de server dat de client er echt om vroeg. De Upgrade-header moet het token websocket bevatten, en Connection moet het token upgrade bevatten, allebei zonder op hoofdletters te letten. Les 1 liet zien waarom een exacte vergelijking faalt: Connection: keep-alive, Upgrade vraagt net zo goed om de upgrade als Connection: Upgrade.
has_token(value, token) beantwoordt de vraag voor elke door komma’s gescheiden headerwaarde. Knip de waarde bij elke komma, strip elk stuk, zet het in kleine letters, en vergelijk het met het token, ook in kleine letters:
Wat drukt dit af?
value = "keep-alive, Upgrade"
for item in value.split(","):
print(item.strip().lower())
Het is verleidelijk om in plaats daarvan "upgrade" in value.lower() te schrijven. Dat vindt upgrade ook in upgrades, en in elk langer woord dat het bevat. Hele tokens vergelijken vindt alleen het token, dus has_token("upgrades", "upgrade") is False.
De sleutel controleren
Een sleutel moet de base64.b64decode vertelt het je, met één extra ! voor die van de RFC, !!!!dGhlIHNhbXBsZSBub25jZQ==, decodeert nog steeds naar de 16 bytes van de sample nonce. Met validate=True weigert het die sleutel en werpt het binascii.Error op. Ontbrekende opvulling werpt hoe dan ook dezelfde fout op.
binascii.Error is een soort except ValueError: rond het decoderen vangt hem. Controleer na een geslaagde decodering de lengte met len: dGhl is geldige base64 maar decodeert naar maar 3 bytes.
Denk aan de dubbele sleutel uit les 2. Twee Sec-WebSocket-Key-regels komen aan als één waarde met ", " in het midden, en een komma en een spatie zijn geen base64-tekens, dus deze controle weigert dat verzoek ook.
Twee manieren om nee te zeggen
RFC 6455 laat een server een
400 Bad Request beantwoordt elk verzoek dat misvormd of onvolledig is. check_upgrade geeft een 400 terug als:
de
niet GETis;de versie niet precies
HTTP/1.1is;er geen
host-header is;upgradegeenwebsocket-token heeft, of ontbreekt;connectiongeenupgrade-token heeft, of ontbreekt;sec-websocket-keyofsec-websocket-versionontbreekt;de sleutel geen strikte base64 is;
de sleutel niet naar 16 bytes decodeert.
De RFC staat elke versie vanaf HTTP/1.1 toe, maar geen latere versie van HTTP gebruikt deze handshake, dus je server accepteert precies HTTP/1.1.
426 Upgrade Required beantwoordt een verzoek dat volledig en goed gevormd is, maar om een andere WebSocket-versie dan 13 vraagt. Het draagt een Sec-WebSocket-Version: 13-header, die de client vertelt welke versie deze server spreekt, zodat een client die meerdere versies kent het met die versie opnieuw kan proberen. Controleer de versie als laatste, nadat elke 400-regel is geslaagd. Verwar het niet met 505 HTTP Version Not Supported, dat over de HTTP-versie in de verzoekregel gaat, niet over de WebSocket-versie. RFC 6455 stelt hier 426 voor en eist alleen de Sec-WebSocket-Version-header, dus 426 is de keuze van deze cursus: het noemt de reden voor de weigering.
Beide weigeringen zijn 4xx-statussen, de klasse die zegt dat het verzoek van de client de fout is. refuse(status) bouwt ze allebei: de statusregel, voor 426 de versieheader, dan Content-Length: 0 en Connection: close, en dan de lege regel. Content-Length: 0 zegt dat er geen body volgt, en Connection: close zegt dat de server de verbinding na dit antwoord sluit. De twee antwoorden zijn:
HTTP/1.1 400 Bad Request
Content-Length: 0
Connection: close
HTTP/1.1 426 Upgrade Required
Sec-WebSocket-Version: 13
Content-Length: 0
Connection: close
Een echte server kan ook met andere statussen weigeren. Het eigen voorbeeld van de RFC voor een versieverschil antwoordt 400 Bad Request met een Sec-WebSocket-Version-header, en een server die controleert waar een verbinding vandaan komt, zou 403 Forbidden kunnen antwoorden, zoals hoofdstuk 9 doet. Deze twee dekken alles wat je server nu controleert.
Een verzoek is volledig en geldig, behalve Sec-WebSocket-Version: 8. Wat geeft check_upgrade terug?
RFC 6455 zegt niet in welke volgorde een server deze regels moet controleren, dus een verzoek met twee fouten kan terecht elk van beide statussen krijgen. Je server controleert in de volgorde van de
Je opdracht
Schrijf in ws_handshake.py deze drie
has_token(value, token):Trueals een door komma’s gescheiden item vanvalue, gestript en in kleine letters, gelijk is aantokenin kleine letters.refuse(status): het volledige 400- of 426-antwoord hierboven, als bytes.check_upgrade(request): een 400 of een 426 volgens de regels hierboven, anders het 101-antwoord metaccept_valuevan de sleutel, als bytes. Gebruikheaders.get(name, "")voor een header die kan ontbreken.
Druk op Run. De sectie van les 4 drukt het 101-antwoord voor het verzoek van de RFC af als bytes-waarde, met \r\n en al:
Lesson 4: check_upgrade
b'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n'
Die regel komt uit een lokale run van de afgewerkte les. Als die van jou overeenkomt, druk je op Submit. De serverkant van de handshake is af; de volgende les schrijft de client die erom vraagt.
Opdracht
Schrijf drie ws_handshake.py, in deze volgorde. Submit noemt de eerste die nog NotImplementedError opwerpt, dus je krijgt ze één voor één.
has_token(value, token)geeftTrueterug als een item van de door komma’s gescheidenvalue, gestript en in kleine letters, gelijk is aantokenin kleine letters, en andersFalse. Een token in een langer woord telt niet:has_token("upgrades", "upgrade")isFalse.refuse(status)geeft bytes terug: voor 400HTTP/1.1 400 Bad Request; voor 426HTTP/1.1 426 Upgrade Requiredgevolgd doorSec-WebSocket-Version: 13; daarnaContent-Length: 0,Connection: closeen de lege regel. Elke regel eindigt op\r\n.check_upgrade(request)neemt eenaan in de vorm die parse_upgrade_requestteruggeeft en geeft bytes terug. Het geeftrefuse(400)terug als deniet GETis, de versie niet preciesHTTP/1.1is,hostontbreekt,upgradegeenwebsocket-token heeft,connectiongeenupgrade-token heeft, de sleutel of de versieheader ontbreekt, of de sleutel geen strikte( base64.b64decode(key, validate=True)) van precies 16 bytes is. Anders geeft hetrefuse(426)terug als de versie niet13is, en het 101-antwoord als ze dat wel is:HTTP/1.1 101 Switching Protocols,Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept:enaccept_value(key), dan de lege regel en niets erna.
Laat de functies voor les 5 en 6 zoals ze zijn. Druk op Run om het 101-antwoord voor het verzoek van de RFC te zien.
Druk daarna op Submit. De controle roept has_token aan op check_upgrade met verzoekdictionary’s die ze zelf bouwt, zodat ze niet van je parser afhangt: het verzoek van de RFC, een geldig verzoek met een sleutel die je nog niet hebt gezien en de tokens anders geschreven, tien verzoeken die een 400 moeten krijgen, elk met precies één fout, en één die een 426 moet krijgen. Ze leest elk antwoord zoals een client dat zou doen: de statusregel, de headers op naam in kleine letters, de lege regel aan het eind, en ofwel geen body ofwel een body zo lang als zijn Content-Length. De volgorde van de headers maakt niet uit, en een weigering mag extra headers of een korte body met de juiste Content-Length hebben; een 101 mag geen body en geen protocol- of extensieheader hebben. Het importeren van ws_handshake.py mag niets afdrukken.