Aufbau eines Requests
Methode, URL, Header und Body, und was der Server wirklich empfängt.
In der letzten Lektion hast du einen Request geschickt, indem du eine URL in den Browser getippt hast. Der Browser hat alles andere für dich ergänzt. Diese Lektion nimmt einen Request auseinander, damit du weißt, was jedes Teil ist, denn jeder Test, den du schreibst, setzt einige dieser Teile gezielt und prüft, wie der Server reagiert.
Ein HTTP-Request hat vier Teile:
- eine Methode, die sagt, welche Art von Aktion du willst
- eine URL, die sagt, mit welcher Sache auf welchem Server das geschehen soll
- Header, die zusätzliche Informationen über den Request tragen
- einen optionalen Body, der die Daten trägt, die du sendest
Wie ein Request auf der Leitung aussieht
HTTP/1.1 ist ein Textprotokoll. Vor jeder Verschlüsselung ist ein Request ein paar Zeilen reiner Text. Das ist der Request, den curl für https://api.testingapis.com/api/echo?color=red sendet. Du kannst ihn selbst sehen, wenn du curl mit -v (ausführlich) startest: Die Zeilen, die mit > beginnen, sind das, was rausging.
GET /api/echo?color=red HTTP/1.1
Host: api.testingapis.com
User-Agent: curl/8.18.0
Accept: */*Die erste Zeile ist die Request-Zeile: Methode, Pfad mit Query-String und Protokollversion. Jede Zeile danach ist ein Header, ein Name und ein Wert, getrennt durch einen Doppelpunkt. Eine leere Zeile beendet die Header. Gäbe es einen Body, käme er nach dieser leeren Zeile. Neuere HTTP-Versionen (HTTP/2 und HTTP/3) senden dieselben Informationen in einem Binärformat, aber die Teile sind identisch, und jedes Werkzeug zeigt sie dir weiterhin in dieser Textform.
Sehen, was der Server bekommen hat
Am einfachsten lernst du Requests kennen, indem du einen an einen Server schickst, der dir sagt, was er bekommen hat. /api/echo macht genau das. Er nimmt jede Methode an und antwortet mit einer Beschreibung deines Requests. Schick diesen:
/api/echo?color=red&size=10Hier ist die Antwort für curl, gekürzt auf die interessanten Felder. Wenn du ihn von der Seite aus sendest, sind es die Header deines Browsers.
{
"method": "GET",
"url": "http://api.testingapis.com/api/echo?color=red&size=10",
"path": "/api/echo",
"queryString": "?color=red&size=10",
"query": { "color": "red", "size": "10" },
"headers": {
"Accept": "*/*",
"Host": "api.testingapis.com",
"User-Agent": "curl/8.18.0",
"Accept-Encoding": "gzip, br",
"Cf-Ipcountry": "IN",
"X-Forwarded-For": "203.0.113.7, 172.71.198.178, 10.30.107.46",
"X-Forwarded-Proto": "https"
},
"contentType": null,
"contentLength": 0,
"body": null
}Zwei Dinge darin verdienen einen zweiten Blick. Der Server hat die URL als http:// gesehen, nicht als https://. Und er hat Header bekommen, die du nie gesendet hast, wie Cf-Ipcountry und X-Forwarded-For. Dein Request ist durch ein CDN und einen Load Balancer gelaufen, bevor er die Anwendung erreicht hat, und jeder davon hat etwas umgeschrieben oder ergänzt. Die erste Adresse in X-Forwarded-For ist deine (die oben ist ein Platzhalter). Das ist in Produktivsystemen normal, und genau deshalb ist ein Echo-Endpunkt nützlich: Er zeigt, was ankam, nicht was du glaubst, gesendet zu haben.
Die Methode
Die Methode ist das Verb. GET fordert Daten an. POST schickt Daten, um etwas anzulegen. PUT, PATCH und DELETE ändern oder entfernen Dinge. Die Adressleiste eines Browsers kann nur GET senden, deshalb brauchst du für den Rest andere Werkzeuge. Methoden bekommen eine eigene Lektion, HTTP-Methoden, also reicht es fürs Erste zu wissen, dass dieselbe URL je nach Methode Verschiedenes tun kann.
Die URL
Zerleg diese URL in ihre Teile:
https://api.testingapis.com/api/Products?category=books&limit=5| Teil | Wert | Bedeutung |
|---|---|---|
| Schema | https | Wie verbunden wird. https ist HTTP in einer verschlüsselten Verbindung. |
| Host | api.testingapis.com | Welcher Server. Wird auch als Host-Header gesendet. |
| Pfad | /api/Products | Welche Ressource auf diesem Server. |
| Query-String | category=books&limit=5 | Zusätzliche Parameter, nach dem ?, getrennt durch &. |
URLs können auch einen Port tragen (:8080 nach dem Host) und ein Fragment (#section am Ende). Ports siehst du oft in Test- und lokalen Umgebungen. Fragmente erreichen den Server nie: Der Browser behält sie.
Pfadparameter und Query-Parameter
Werte können im Pfad oder im Query-String reisen. In /api/Products/1 ist die 1 ein Pfadparameter. Sie bezeichnet ein bestimmtes Produkt, und die Dokumentation schreibt den Pfad als /api/Products/{id}. In /api/Products?limit=5 ist limit ein Query-Parameter. Query-Parameter filtern, sortieren oder blättern meistens durch Ergebnisse und sind meistens optional. Diese Aufteilung ist eine Konvention, keine Regel, also schau bei jeder API in die Dokumentation.
Pfade unterscheiden oft Groß- und Kleinschreibung, und die Namen von Query-Parametern meistens auch. Das hängt vom Server-Framework ab, also mach bei jeder neuen API einen schnellen Test: Schick /api/products statt /api/Products und schau, was passiert.
Prozentkodierung
Manche Zeichen haben in einer URL eine Aufgabe. ? beginnt den Query-String, & trennt Parameter, # beginnt das Fragment, / trennt Pfadsegmente, und Leerzeichen sind gar nicht erlaubt. Um eines davon als Daten zu senden, ersetzt du es durch % und seinen hexadezimalen Bytewert. Ein Leerzeichen wird zu %20 und & zu %26. Das nennt man Prozentkodierung oder URL-Kodierung.
Angenommen, du willst nach "red shoes" suchen und nach einem Tag filtern, dessen Wert wörtlich der Text a&b ist. Kodiert klappt es:
/api/echo?q=red%20shoes&tag=a%26b{
"queryString": "?q=red%20shoes&tag=a%26b",
"query": { "q": "red shoes", "tag": "a&b" }
}Schick den Tag jetzt, ohne das Kaufmanns-Und zu kodieren:
/api/echo?tag=a&b{
"queryString": "?tag=a&b",
"query": { "tag": "a", "b": "" }
}Jetzt sieht der Server zwei Parameter: tag mit dem Wert a und einen leeren Parameter namens b. Kein Fehler, keine Warnung. Der Request hat "funktioniert", und die Daten sind falsch. Das ist eine echte Klasse von Bugs in Clients, die URLs aus aneinandergeklebten Strings bauen, und der Grund, warum Suchfelder beim Testen ein guter Ort für Kaufmanns-Und, Pluszeichen, Schrägstriche und nicht-englischen Text sind. Der URL-Encoder auf dieser Seite erledigt die Umwandlung, wenn du sie von Hand brauchst.
Header
Header sind Paare aus Name und Wert, die den Request beschreiben. Bei den Namen spielt Groß- und Kleinschreibung keine Rolle, content-type und Content-Type bedeuten also dasselbe. Die meisten Clients fügen automatisch mehrere Header hinzu. Die, die du am häufigsten setzen oder prüfen wirst, sind diese:
| Header | Was er sagt | Beispiel |
|---|---|---|
Content-Type | Das Format des Bodys, den du sendest. | application/json |
Accept | Die Formate, die du annehmen willst. | application/json |
User-Agent | Welcher Client den Request gesendet hat. | curl/8.18.0 |
Authorization | Zugangsdaten, etwa ein Token. | Bearer abc123 |
Host | Welche Site auf dem Server. Clients füllen ihn aus der URL aus. | api.testingapis.com |
Accept ist wichtig, wenn eine API in mehr als einem Format antworten kann. /api/formats/negotiateanmelden zum Anzeigen liefert denselben Nutzer standardmäßig als JSON und als XML, wenn du danach fragst:
/api/formats/negotiateVergleich das XML mit dem JSON, das du ohne den Header bekommst. Beide tragen dieselben sieben Felder, jeweils auf die Art des Formats geschrieben: Die Liste roles wird zu wiederholten role-Elementen, und der leere manager wird zu einem leeren Element mit xsi:nil. Wenn eine API mehrere Formate anbietet, teste jedes und nimm nicht an, dass sie übereinstimmen. Als diese Lektion zuerst geschrieben wurde, fehlten in der XML-Version dieses Nutzers drei Felder.
Authorization wird in Authentifizierung und Autorisierung ausführlich behandelt. Eins solltest du schon jetzt wissen: Füg nie echte Tokens in Screenshots, Bug-Reports oder geteilte Collections ein. Sie sind Passwörter.
Der Body
GET-Requests haben normalerweise keinen Body. POST, PUT und PATCH meistens schon, weil sie Daten an den Server senden. Der Body kann jedes Format haben, und der Header Content-Type sagt dem Server, wie er ihn lesen soll. Bei den meisten heutigen APIs ist der Body JSON. Das hier schickt mit curl ein kleines JSON-Objekt an den Echo-Endpunkt:
curl -X POST https://api.testingapis.com/api/echo \
-H "Content-Type: application/json" \
-d '{"title":"Dune","year":1965}'{
"method": "POST",
"path": "/api/echo",
"query": {},
"contentType": "application/json",
"contentLength": 28,
"body": "{\"title\":\"Dune\",\"year\":1965}",
"json": { "title": "Dune", "year": 1965 }
}Der Server hat den Header gelesen, application/json gesehen und die 28 Bytes in ein Objekt geparst. Du kannst denselben Request von hier senden:
/api/echo{"title":"Dune","year":1965}Wenn Content-Type und Body nicht zusammenpassen
Schick jetzt mit curl die gleiche Art Body, aber lass den Header weg:
curl -X POST https://api.testingapis.com/api/echo \
-d '{"title":"Dune"}'{
"contentType": "application/x-www-form-urlencoded",
"contentLength": 16,
"body": "{\"title\":\"Dune\"}",
"json": null,
"form": { "{\"title\":\"Dune\"}": "" }
}Die Option -d von curl setzt standardmäßig application/x-www-form-urlencoded, das Format von HTML-Formularen. Der Server hat dem Header geglaubt, versucht, den Body als Formular zu lesen, und am Ende ein einziges Formularfeld bekommen, dessen Name der gesamte JSON-Text ist. Kein Fehler kam zurück. Viele echte Server verhalten sich genauso, andere antworten mit 415 Unsupported Media Type. Ein fehlender oder falscher Content-Typeist einer der häufigsten Gründe, warum ein Request "richtig aussieht" und trotzdem scheitert, also prüfst du ihn als Erstes, wenn ein Server deine Daten ignoriert.
Stimmt der Header, aber das JSON ist kaputt, sagt ein guter Server das. Hier ist ein Teil der Echo-Antwort für den Body {"title":"Dune",}, der ein überzähliges Komma hat:
"json": null,
"jsonError": "The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. LineNumber: 0 | BytePositionInLine: 16."Übung
Beantworte jede Frage mit /api/echo. Sieh dir jedes Mal die Antwort an, statt zu raten.
- Schick
/api/echo?name=Zoë&city=São Pauloaus der Adressleiste deines Browsers. Was hat der Browser vor dem Senden an der URL geändert, und wozu hat der Server sie dekodiert? - Schick
/api/echo?id=1&id=2. Wie stellt der Server einen Parameter dar, der zweimal vorkommt? Verschiedene Frameworks sind sich hier uneins, was es zu einer guten Frage für jede API macht, die du testest. - Schick einen POST mit JSON-Body und
Content-Type: text/plain. Hat der Server das JSON trotzdem geparst? - Probier
/api/echo/user-agentim Browser und mit curl. Warum sollte es eine API überhaupt kümmern, welches von beiden du benutzt hast?