Abschnitt 8 · Lektion 2
Die rohe Antwort
Du hast in diesem Kurs einen Vorteil, den kein Browser-Tutorial hat: Du siehst die Antwort als Text und nicht als fertig gezeichnete Seite. Diese Lektion nutzt ihn aus, denn wer einmal gesehen hat, wie eine Antwort wirklich aussieht, versteht alles ab hier in einem Satz.
Vier Teile, immer dieselben
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("Guten Tag");
});
server.listen(3000); Rechts im Browser stehen drei Wörter, und das ist genau ein Viertel der Geschichte. Die anderen drei Viertel stehen davor, und der Browser zeigt sie nicht, weil sie nicht für dich gedacht sind. Im Terminal schon:
curl -si http://localhost:3000/
Ganz oben die Statuszeile: Protokollversion, Statuscode, ein Wort dazu. Darunter die Kopfzeilen, je eine pro Zeile, Name und Wert durch einen Doppelpunkt getrennt. Dann eine Leerzeile. Und danach der Inhalt.
Der Schalter, auf den es ankommt, ist -i: Er nimmt den Kopf mit. Das -s nimmt nur den
Fortschrittsbalken weg, den curl sonst über die Antwort schreibt.
Die Leerzeile ist die wichtigste Zeile im ganzen Protokoll. Sie ist die einzige Angabe, an der der Empfänger erkennt, wo der Kopf aufhört und der Inhalt anfängt. Ohne sie gäbe es keine Möglichkeit, das eine vom anderen zu trennen, denn ein Inhalt darf ja alles enthalten, auch etwas, das wie eine Kopfzeile aussieht.
Ein Detail, das man der Ausgabe nicht ansieht: Jede dieser Zeilen endet auf der Leitung mit
\r\n, also mit Wagenrücklauf und Zeilenvorschub, nicht mit einem einfachen Zeilenumbruch. Sichtbar
wird das, wenn du cat bittest, die unsichtbaren Zeichen mitzuzeichnen:
curl -si http://localhost:3000/ | cat -A
Jetzt steht am Ende jeder Zeile ^M$. Das $ ist der Zeilenumbruch, das ^M der Wagenrücklauf
davor. Die berühmte Leerzeile ist damit übrigens gar keine leere Zeile, sondern ein \r\n, vor dem
nichts steht.
Zwei der Kopfzeilen hat niemand gesetzt. Date und Content-Length kommen von Node selbst. Die
Länge rechnet es aus, sobald es den Inhalt kennt, und das Datum setzt es, weil das Protokoll es so
vorsieht. Die Datumszeile ändert sich bei jedem Aufruf, denn sie trägt den Zeitpunkt der Antwort.
Sie ist die einzige in dieser Ausgabe, bei der das so ist.
Auf dem Hinweg genauso
import { createServer } from "node:http";
// Dieser Server beantwortet jede Anfrage mit der Anfrage selbst,
// Zeile fuer Zeile, so wie sie hereingekommen ist.
const server = createServer((req, res) => {
const zeilen = [`${req.method} ${req.url} HTTP/${req.httpVersion}`];
// rawHeaders ist eine flache Liste: Name, Wert, Name, Wert.
for (let i = 0; i < req.rawHeaders.length; i = i + 2) {
zeilen.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`);
}
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end(zeilen.join("\n") + "\n");
});
server.listen(3000); Eine Anfrage ist genauso gebaut, nur heißt die erste Zeile nicht Statuszeile, sondern nennt Methode,
Pfad und Version. Danach wieder Kopfzeilen, wieder eine Leerzeile, und danach der Inhalt, den es bei
einem GET üblicherweise nicht gibt.
Dieser Server antwortet mit dem, was hereinkam, und damit steht rechts im Browser die Anfrage, die dein Browser gerade gestellt hat. Nicht ungefähr so eine, sondern seine. Er schickt eine ganze Reihe Kopfzeilen mit: welche Sprachen du bevorzugst, welche Kompressionsverfahren er versteht, welches Programm er ist. Alles davon ist Text, und alles davon kommt vom Aufrufer.
Frag denselben Server im Terminal, und die Liste wird kurz:
curl http://localhost:3000/
curl schickt drei Kopfzeilen, dein Browser ein Dutzend. Beide sprechen dasselbe Protokoll, sie
haben nur unterschiedlich viel zu sagen. Mit curl -v siehst du zusätzlich, was curl selbst
hinausgeschickt hat: Es setzt vor jede Zeile ein Zeichen, > für den Hinweg und < für den Rückweg,
und dann stehen beide Richtungen untereinander.
req.rawHeaders ist dabei bewusst die rohe Liste und nicht req.headers. Das zerlegte Objekt
schreibt jeden Namen klein und wirft doppelte Zeilen zusammen. Hier geht es um das, was wirklich
ankam, also um die Liste, an der noch nichts gemacht wurde. In der nächsten Lektion nehmen wir das
bequeme Objekt.
Auch Daten sind nur Inhalt
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ titel: "CSS", frei: true }));
});
server.listen(3000); Eine JSON-Antwort ist keine andere Sorte Antwort. Dieselben vier Teile, und hinter der Leerzeile
steht eine Zeichenkette, die zufällig als JSON gelesen werden kann. Die einzige Stelle, an der das
irgendwo vermerkt ist, ist die Kopfzeile Content-Type. Hol sie dir mit curl -si und vergleiche sie
mit der aus dem ersten Beispiel: Der Rest der Antwort ist Zeile für Zeile derselbe Aufbau.
Das ist ein wichtiger Punkt für den ganzen Rest des Kurses: Der Server schickt Text und eine Behauptung darüber, was der Text ist. Ob der Empfänger sich daran hält, ob er es überhaupt liest, und ob die Behauptung stimmt, sind drei verschiedene Fragen. In Lektion 8.4 sehen wir, was passiert, wenn sie nicht stimmt.
Das gilt auch für deine eigene Aufgabe
curl fragt nicht nur die Beispiele. Hol die Aufgabe dieser Lektion nach vorn, starte sie, und
derselbe Befehl zeigt dir deine eigene Antwort:
curl -si http://localhost:3000/
Das ist ab hier dein Werkzeug für alles, was der Browser daneben nicht zeigt: Statuscodes,
Kopfzeilen, Weiterleitungen, Cookies. Der Browser zeigt dir das Ergebnis, curl zeigt dir den Weg
dorthin.
Und du hast diese Ansicht sowieso ständig vor Augen: Was unter einer Prüfzeile steht, wenn du auf
Prüfen klickst, ist genau das, was curl gegen deinen Server geliefert hat. Nicht ungefähr dasselbe,
sondern derselbe Befehl, die Prüfung fährt curl -s -i --max-time 5.
Der Reiter „Netzwerk” schreibt mit, was der Browser daneben lädt, und in dieser Lektion bleibt er leer. Der Mitschnitt läuft nämlich in der Seite mit, die der Browser geladen hat, und eine Textantwort ist keine Seite. Ab Lektion 8.5 lieferst du HTML aus, und dann steht dort etwas. Die Entwicklerwerkzeuge in deinem eigenen Browser haben denselben Reiter, und der kennt diese Grenze nicht: Bei ihm steht jede Anfrage in der Liste, auch eine, die nur Text zurückgibt.
Zum Mitnehmen
Statuszeile, Kopfzeilen, Leerzeile, Inhalt. Die Leerzeile ist die wichtigste Zeile im ganzen Protokoll, denn sie sagt, wo der Kopf aufhört.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.