Anfragen von Hand schreiben · Übung
Prozentkodiere, was du sendest
Angenommen, die Leseliste führt einen Datensatz pro Titel, und ein Client möchte den Datensatz für “Café & Co”. Den Titel von Hand ins Ziel einzufügen ergibt diese Anfragezeile:
Eine Anfragezeile hat genau drei Wörter, getrennt durch einzelne Leerzeichen: request_line aus Kapitel 1 lehnt ein Ziel mit Leerzeichen genau aus diesem Grund ab, und make_request könnte é ohnehin nicht senden, weil es den Kopf als ASCII kodiert. Das & ist ein leiseres Problem: In einer Query trennt es einen & enthält, in zwei Teile geschnitten.
Die Lösung ist, die Schreibweise solcher Zeichen zu ändern, damit sie den Server als Daten erreichen und nie als Struktur. Das ist die Prozentkodierung (percent-encoding), und in dieser Lektion schreibst du in wire.py die beiden
Ein Byte als drei Zeichen
Die Prozentkodierung schreibt ein Byte als %, gefolgt vom Wert des Bytes in zwei Hexadezimalziffern. Ein Leerzeichen ist das Byte 32, hexadezimal 20, also wird ein Leerzeichen zu %20. Ein Schrägstrich wird zu %2F und ein kaufmännisches Und zu %26.
Ein Zeichen außerhalb von ASCII ist mehr als ein Byte. Wie Networks with Python gezeigt hat, schreibt é als zwei:
Jedes dieser Bytes wird für sich kodiert, also wird é zu %C3%A9 und Café zu Caf%C3%A9. Der kodierte Text ist reines ASCII, und jedes Byte des Originals bleibt erhalten.
ASCII-Buchstaben, Ziffern und die vier Zeichen -, ., _ und ~ müssen nie kodiert werden; sie bedeuten überall in einer
Ein Pfadsegment mit quote
Ein Pfad besteht aus Segmenten, getrennt durch /: /v1/items/42 hat die Segmente v1, items und 42. Wenn ein Wert in ein Segment kommt, muss jedes Zeichen darin, das keine einfachen Daten sind, kodiert werden, auch /.
urllib.parse hat die Funktion dafür, quote. Nur mit dem Text aufgerufen, lässt sie absichtlich ein Zeichen in Ruhe:
Standardmäßig behandelt quote das / als sicher, weil es einen ganzen Pfad erwartet, in dem Schrägstriche Segmente trennen. Für ein einzelnes Segment ist das falsch: /v1/items/AC/DC nennt ein Segment AC und danach ein Segment DC, was ein anderer Pfad ist. Die Übergabe von safe="" sagt, dass kein Zeichen sicher so bleiben darf, wie es ist, also wird der Schrägstrich als %2F kodiert, und das Segment bleibt ein Segment.
quote("AC/DC") gibt 'AC/DC' zurück. Warum ist das für einen Wert falsch, der in ein einzelnes Pfadsegment kommt?
Query-Werte mit urlencode
Eine Query ist eine name=value, verbunden durch &, das Format, das Python in der Praxis mit params= gesendet hat. Sie von Hand zu bauen führt zum selben Problem: Ein Wert, der & oder = enthält, würde ein neues Paar beginnen. urlencode nimmt ein
Ein Teil seiner Kodierung unterscheidet sich von quote: urlencode schreibt ein Leerzeichen in einem Wert als +, nicht als %20. Das kommt von HTML-Formularen: Wenn ein Browser ein Formular sendet, schreibt er jedes Leerzeichen in einem Wert als +, und ein Server, der eine Query als Formulardaten liest, macht aus + wieder ein Leerzeichen. urlencode folgt dieser Konvention, und die lesende Seite, etwa parse_qs, ebenso. Das gilt nur für die Query. Überall sonst in einer URL ist + ein gewöhnliches, erlaubtes Zeichen, das ein Pluszeichen bedeutet. In einem Pfadsegment schreibt quote ein Leerzeichen als %20 und lässt die Bedeutung von + in Ruhe.
Sag mit diesem Wissen voraus, was urlencode aus einem Titel macht, der sowohl ein & als auch ein Leerzeichen enthält:
Was gibt das aus?
from urllib.parse import urlencode
print(urlencode({"title": "R&D notes", "page": 2}))
Der Rückweg mit unquote
Ein Server, der AC%2FDC empfängt und den Text zurückhaben möchte, dekodiert ihn mit unquote, das jedes %XX wieder in sein Byte verwandelt und die Bytes als UTF-8 dekodiert. Es gibt auch unquote_plus, das zuerst jedes + in ein Leerzeichen verwandelt. Das ist richtig für Formulardaten in einer Query und falsch für ein Pfadsegment, in dem + ein Pluszeichen ist:
Die erste Zeile behält die Pluszeichen; die zweite macht aus dem letzten ein Leerzeichen.
Kodierung ist ein Versprechen zwischen dem Code, der kodiert, und dem Code, der dekodiert. Deine Seite davon, als Client, ist, jeden Wert zu kodieren, den du in ein Ziel setzt, damit der Server den Wert vollständig erhält, egal was er als Nächstes tut. Welche Teile ein Server dekodiert, und wann, entscheidet der Server; Lektion 5 zeigt, was practice-api mit einem kodierten Pfad macht.
Deine Aufgabe
Zwei Funktionen in wire.py gehören zu dieser Lektion, make_target und decode_segment, und beide lösen noch NotImplementedError aus. Die Importe oben in der Datei holen schon quote, unquote und urlencode herein.
Schreibe make_target(segments, params=None). Die Funktion bekommt eine Liste von Segment-Strings und optional ein Dict mit Query-Werten und gibt das Ziel als str zurück:
Gib jedes Segment durch
quote(segment, safe="")und sammle die Ergebnisse in einer Liste;verbinde sie mit
"/"und setze ein"/"davor, sodass["v1", "items"]den Wert/v1/itemsergibt;wenn
paramsetwas enthält, füge"?"undurlencode(params)hinzu. WennparamsalsNonestehen bleibt oder als leeres Dict übergeben wird, gibt es keine Query und kein"?". Der Testif params:ist für beides falsch.
Schreibe decode_segment(segment): Gib unquote(segment) zurück.
Probiere dann beide im Terminal aus:
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'))"
Wenn beide Funktionen geschrieben sind, geben die vier Zeilen Folgendes aus:
/v1/items/AC%2FDC%20live
/v1/items/Caf%C3%A9
/v1/items?q=R%26D+notes&page=2
AC/DC live
Jedes Ziel ist ein Wort ohne Leerzeichen darin, also würde request_line jedes davon akzeptieren.
Wenn eine Prüfung fehlschlägt, bitte Monty, die fehlschlagende Meldung mit dir zu lesen und auf die Zeile von make_target zu zeigen, um die es geht, ohne die Korrektur zu schreiben.
Wenn diese Zeilen wie gezeigt ausgegeben werden, drücke Check my work.
Die Ziele sind jetzt sicher, aber jede Anfrage trägt immer noch die Zeile Host, die Kapitel 1 mitgeliefert hat. Die nächste Lektion ersetzt sie durch eine, die du baust, und fügt eigene
Aufgabe
Schreibe in wire.py die beiden # Lesson 3 markiert sind.
make_target(segments, params=None) gibt ein Anfrageziel als str zurück:
Jedes Segment geht durch
quote(segment, safe=""), sodass ein Leerzeichen,/,?,#,&,%,+oder ein Buchstabe außerhalb von ASCII innerhalb einesprozentkodiert wird; die kodierten Segmente werden mit
"/"verbunden, mit einem"/"davor;wenn
paramsetwas enthält, endet das Ziel mit"?"undurlencode(params); wennparamsgleichNoneoder ein leeresist, gibt es kein "?".
decode_segment(segment) gibt unquote(segment) zurück: Jedes %XX wird wieder zu seinem Byte, die Bytes werden als + bleibt ein Pluszeichen.
Probiere sie im python -c "import wire; print(wire.make_target(['v1', 'items', 'AC/DC live']))" und den anderen Zeilen aus der Lektion aus.
Drücke dann Check my work. Die Prüfung ruft make_target mit Segmenten und Query-Werten auf, die du nicht gesehen hast, mit Leerzeichen, Schrägstrichen, kaufmännischen Unds, Pluszeichen, Prozentzeichen, Fragezeichen und Buchstaben außerhalb von ASCII, und liest jedes Ziel mit ihrem eigenen Decoder zurück: Jedes Segment muss zu seinem Wert dekodieren, der Pfad muss ein Segment pro Wert haben, die Query muss sich als die Werte zurücklesen lassen, die du übergeben hast (ein Leerzeichen als %20 oder als + ist beides in Ordnung), und ein Aufruf ohne Query-Werte darf kein "?" haben. Sie ruft decode_segment auf kodierten Segmenten auf, die du nicht gesehen hast, darunter eines mit einem + darin. Nichts wird gesendet, und sie liest dein Terminal nicht.