Verzoeken met de hand schrijven · oefening
Percent-encodeer wat je verstuurt
Stel dat de leeslijst één record per titel bijhoudt, en een client wil het record voor “Café & Co”. De titel met de hand in het doel plakken, geeft deze verzoekregel:
Een verzoekregel heeft precies drie woorden, gescheiden door enkele spaties: request_line uit hoofdstuk 1 weigert om precies die reden een doel met een spatie, en make_request kon é toch al niet versturen, omdat het de kop als ASCII codeert. De & is een stiller probleem: in een query scheidt hij de ene & erin zou in tweeën worden geknipt.
De oplossing is de manier te veranderen waarop zulke tekens worden geschreven, zodat ze de server als data bereiken en nooit als structuur. Dat is percent-encoding, en in deze les schrijf je de twee wire.py die het maken en lezen. De
Een byte als drie tekens
Percent-encoding schrijft één byte als %, gevolgd door de waarde van de byte in twee hexadecimale cijfers. Een spatie is de byte 32, wat 20 is in hex, dus een spatie wordt %20. Een schuine streep wordt %2F, en een ampersand %26.
Een teken buiten ASCII is meer dan één byte. Zoals Networks with Python liet zien, schrijft é als twee bytes:
Elk van die bytes wordt apart gecodeerd, dus é wordt %C3%A9, en Café wordt Caf%C3%A9. De gecodeerde tekst is gewone ASCII, en elke byte van het origineel blijft behouden.
ASCII-letters, cijfers en de vier tekens -, ., _ en ~ hoeven nooit gecodeerd te worden; ze betekenen overal in een
Eén padsegment met quote
Een pad bestaat uit segmenten, gescheiden door /: /v1/items/42 heeft de segmenten v1, items en 42. Als een waarde in één segment gaat, moet elk teken erin dat geen gewone data is, gecodeerd worden, ook /.
urllib.parse heeft er de functie voor, quote. Als je die alleen met de tekst aanroept, laat ze met opzet één teken met
Standaard behandelt quote / als veilig, omdat het een heel pad verwacht, waarin schuine strepen segmenten scheiden. Voor één segment is dat fout: /v1/items/AC/DC noemt een segment AC en dan een segment DC, en dat is een ander pad. Met safe="" zeg je dat geen enkel teken veilig ongewijzigd kan blijven, dus de schuine streep wordt gecodeerd als %2F, en het segment blijft één segment.
quote("AC/DC") geeft 'AC/DC' terug. Waarom is dat fout voor een waarde die in één padsegment gaat?
Querywaarden met urlencode
Een query is een name=value, samengevoegd met &, het formaat dat Python in Practice met params= verstuurde. Die met de hand bouwen, loopt tegen hetzelfde probleem aan: een waarde met & of = erin zou een nieuw paar beginnen. urlencode krijgt een
Eén deel van de codering verschilt van quote: urlencode schrijft een spatie in een waarde als +, niet als %20. Dat komt van HTML-formulieren: als een browser een formulier verstuurt, schrijft hij elke spatie in een waarde als +, en een server die een query als formuliergegevens leest, maakt van + weer een spatie. urlencode volgt die conventie, en de leeskant ook, zoals parse_qs. Het geldt alleen voor de query. Overal elders in een URL is + een gewoon, toegestaan teken dat een plusteken betekent. In een padsegment schrijft quote een spatie als %20 en laat het de betekenis van + met rust.
Voorspel met dat in gedachten wat urlencode maakt van een titel met zowel een & als een spatie:
Wat drukt dit af?
from urllib.parse import urlencode
print(urlencode({"title": "R&D notes", "page": 2}))
De weg terug met unquote
Een server die AC%2FDC ontvangt en de tekst terug wil, decodeert hem met unquote, dat elke %XX weer omzet in zijn byte en de bytes als UTF-8 decodeert. Er is ook unquote_plus, dat eerst elke + omzet in een spatie. Dat is juist voor formuliergegevens in een query en fout voor een padsegment, waar + een plusteken is:
De eerste regel houdt de plustekens; de tweede maakt van de laatste een spatie.
Codering is een belofte tussen de code die codeert en de code die decodeert. Jouw kant ervan, als client, is elke waarde te coderen die je in een doel zet, zodat de server de waarde in zijn geheel ontvangt, wat hij daarna ook doet. Welke delen een server decodeert, en wanneer, is de keuze van de server; les 5 laat zien wat practice-api doet met een gecodeerd pad.
Je opdracht
Twee functies in wire.py horen bij deze les, make_target en decode_segment, en beide werpen nog NotImplementedError op. De imports bovenaan het bestand halen quote, unquote en urlencode al binnen.
Schrijf make_target(segments, params=None). Het krijgt een lijst segmentstrings en, optioneel, een dict met querywaarden, en geeft het doel terug als str:
haal elk segment door
quote(segment, safe="")en verzamel de resultaten in een lijst;voeg ze samen met
"/"en zet er één"/"voor, zodat["v1", "items"]/v1/itemsgeeft;als er iets in
paramszit, voeg dan"?"enurlencode(params)toe. AlsparamsNoneblijft, of als lege dict wordt meegegeven, is er geen query en geen"?". De testif params:is voor beide onwaar.
Schrijf decode_segment(segment): geef unquote(segment) terug.
Probeer daarna beide in de terminal:
python -c "import wire; print(wire.make_target(['v1', 'items', 'AC/DC live']))"
python -c "import wire; print(wire.make_target(['v1', 'items', 'Café']))"
python -c "import wire; print(wire.make_target(['v1', 'items'], {'q': 'R&D notes', 'page': 2}))"
python -c "import wire; print(wire.decode_segment('AC%2FDC%20live'))"
Als beide functies geschreven zijn, drukken de vier regels af:
/v1/items/AC%2FDC%20live
/v1/items/Caf%C3%A9
/v1/items?q=R%26D+notes&page=2
AC/DC live
Elk doel is één woord zonder spatie, dus request_line zou ze allemaal accepteren.
Als een controle mislukt, vraag Monty dan om samen met jou de foutmelding te lezen en de regel van make_target aan te wijzen waar die over gaat, zonder de oplossing te schrijven.
Als die regels afdrukken zoals hierboven, druk je op Check my work.
De doelen zijn nu veilig, maar elk verzoek bevat nog steeds de Host-regel die hoofdstuk 1 leverde. De volgende les vervangt hem door een die je zelf bouwt, en voegt eigen
Opdracht
Schrijf in wire.py de twee # Lesson 3 gemarkeerd zijn.
make_target(segments, params=None) geeft een verzoekdoel terug als str:
elk segment gaat door
quote(segment, safe=""), zodat een spatie,/,?,#,&,%,+of een letter buiten ASCII binnen eenwordt gepercent-encodeerd; de gecodeerde segmenten worden samengevoegd met
"/", met één"/"ervoor;als er iets in
paramszit, eindigt het doel met"?"enurlencode(params); alsparamsNoneof een legeis, is er geen "?".
decode_segment(segment) geeft unquote(segment) terug: elke %XX weer omgezet in zijn byte, de bytes gedecodeerd als + blijft een plusteken.
Probeer ze in de python -c "import wire; print(wire.make_target(['v1', 'items', 'AC/DC live']))" en de andere regels uit de les.
Druk daarna op Check my work. De controle roept make_target aan met segmenten en querywaarden die je niet hebt gezien, met spaties, schuine strepen, ampersands, plustekens, procenttekens, vraagtekens en letters buiten ASCII, en leest elk doel terug met een eigen decoder: elk segment moet decoderen tot zijn waarde, het pad moet één segment per waarde hebben, de query moet teruglezen als de waarden die je meegaf (een spatie geschreven als %20 of als + is allebei goed), en een aanroep zonder querywaarden mag geen "?" hebben. Ze roept decode_segment aan op gecodeerde segmenten die je niet hebt gezien, waaronder een met een + erin. Er wordt niets verstuurd, en ze leest je terminal niet.