Abschnitt 8 · Lektion 4
Status und Header setzen
Der Statuscode ist das Erste, was der Empfänger von deiner Antwort sieht, und oft das Einzige, was er auswertet. Es lohnt sich, ihn richtig zu wählen.
Vier Gruppen, und was sie sagen
- 2xx hat geklappt.
- 3xx sieh woanders nach.
- 4xx du hast einen Fehler gemacht.
- 5xx ich habe einen Fehler gemacht.
Diese vier Sätze tragen erstaunlich weit. Die Grenze zwischen 4xx und 5xx ist dabei die interessanteste, denn sie beantwortet die Frage, wer das Problem beheben muss.
Die zehn, die du wirklich brauchst
- 200 OK hat geklappt, hier ist das Ergebnis.
- 201 Created hat geklappt, und dabei ist etwas Neues entstanden.
- 204 No Content hat geklappt, und es gibt nichts zurückzugeben. Der übliche Fall nach einem erfolgreichen Löschen.
- 301 Moved Permanently das liegt ab jetzt woanders, merk dir die neue Adresse.
- 302 Found das liegt gerade woanders, frag nächstes Mal wieder hier.
- 400 Bad Request deine Anfrage ergibt keinen Sinn.
- 401 Unauthorized ich weiß nicht, wer du bist.
- 403 Forbidden ich weiß, wer du bist, und du darfst das nicht.
- 404 Not Found so etwas gibt es hier nicht.
- 500 Internal Server Error bei mir ist etwas kaputtgegangen.
401 und 403 werden ständig verwechselt, dabei ist der Unterschied leicht zu merken: 401 heißt melde dich an, 403 heißt melde dich ruhig an, es ändert nichts.
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.url === "/ok") {
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ status: "ok" }));
return;
}
if (req.url === "/leer") {
res.statusCode = 204;
res.end();
return;
}
// Dieselbe Sache in einem Aufruf statt in zweien.
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.end("nicht gefunden");
});
server.listen(3000); Rechts steht die Antwort auf /ok, und den Statuscode siehst du dabei nicht: Der Browser zeigt ihn
nirgends an, er richtet sich nur danach. Im Terminal steht er in der ersten Zeile:
curl -i http://localhost:3000/ok
curl -i http://localhost:3000/leer
curl -i http://localhost:3000/gibtsnicht
Beachte die 204 im zweiten Pfad: kein Inhalt, und Node schickt dann auch keinen Content-Type mit.
Das ist richtig so, denn es gibt ja keinen Inhalt, dessen Typ man angeben könnte. Im Browser sieht
/leer deshalb aus wie nichts, und das ist keine kaputte Seite, sondern eine vollständige Antwort.
Zwei Schreibweisen für dieselbe Sache
Im Beispiel stehen beide nebeneinander. res.statusCode = 200 zusammen mit res.setHeader(...)
setzt die Angaben einzeln. res.writeHead(404, { ... }) setzt Status und Kopfzeilen in einem Aufruf.
Nimm, was dir besser gefällt. Es gibt genau einen praktischen Unterschied, und der ist fein:
writeHead schickt den Kopf sofort los, setHeader sammelt ihn nur ein. Deshalb kann Node bei
setHeader noch Content-Length ausrechnen, sobald es den Inhalt kennt, und bei writeHead nicht
mehr. Es behilft sich dann mit einem stückweisen Verfahren, das in der rohen Antwort als
Transfer-Encoding: chunked auftaucht. Für dich ändert sich dadurch nichts, aber wenn du dich beim
Betrachten einer rohen Antwort irgendwann wunderst, warum die Längenangabe fehlt, ist das der Grund.
Der Kopf muss vor dem Inhalt fertig sein
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.write("die erste Haelfte\n");
// Der Kopf ist zu diesem Zeitpunkt laengst unterwegs. Was daraus
// wird, schreiben wir in die Antwort selbst, sonst saehe es niemand.
try {
res.setHeader("Content-Type", "text/plain");
res.write("die Kopfzeile wurde gesetzt\n");
} catch (fehler) {
res.write(`${fehler.name}: ${fehler.code}\n`);
}
res.end();
});
server.listen(3000); Sobald das erste Zeichen des Inhalts unterwegs ist, ist der Kopf weg. Er steht ja davor. Ein
setHeader danach kommt zu spät, und Node sagt das deutlich: In der zweiten Zeile rechts steht
Error: ERR_HTTP_HEADERS_SENT.
Dieser Fehlercode wird dich noch mehrfach besuchen, und die Ursache ist fast nie die, die du gerade
gesehen hast. Viel häufiger ist es ein zweites res.end() nach einem ersten, meistens weil vor
einer frühen Antwort ein return fehlt. Das ist Lektion 9.4, und dort ist es die Kernaufgabe.
Die Regel für hier lautet schlicht: Erst alles am Kopf erledigen, dann den Inhalt schicken.
Der Content-Type ist eine Behauptung, und sie sollte stimmen
Der Content-Type sagt dem Empfänger, wie er den Inhalt lesen soll. Fehlt er, muss der Empfänger
raten. Browser sind darin gut, aber eben nicht unfehlbar, und ein geratener Typ ist an einer Stelle
sogar ein Sicherheitsproblem: Wer eine hochgeladene Datei ohne Typangabe ausliefert, gibt dem
Browser die Gelegenheit, sie für HTML zu halten und auszuführen.
Und dann ist da noch der Zusatz hinter dem Semikolon.
import { createServer } from "node:http";
const server = createServer((req, res) => {
// Ohne charset, absichtlich. Haeng "; charset=utf-8" an und schau
// dir den Unterschied an.
res.setHeader("Content-Type", "text/plain");
res.end("Fröhliche Küche");
});
server.listen(3000); Fünfzehn Zeichen, und curl verrät dir, wie viele Bytes daraus werden:
curl -i http://localhost:3000/
Content-Length: 17. Die beiden Umlaute brauchen je zwei Bytes. Das ist UTF-8, und Node schreibt
Zeichenketten immer so heraus.
Das Problem ist nicht das Schreiben, sondern das Lesen. Ohne charset=utf-8 steht nirgends, wie
diese Bytes gemeint sind, also muss der Empfänger raten. Was im Reiter „Browser” steht, hängt
deshalb nicht von deinem Server ab, sondern vom Browser, der es liest, und zwei Browser dürfen sich
hier verschieden entscheiden. Rät einer auf eine ältere Kodierung, steht dort Fröhliche Küche.
Diese Sorte Zeichensalat hast du im Netz mit Sicherheit schon gesehen, und jetzt weißt du, welche
fehlende Angabe dahintersteckt.
Häng im Beispiel ; charset=utf-8 an und lade die Seite neu. Jetzt rät niemand mehr.
Für Text gilt deshalb: text/plain; charset=utf-8, nicht nur text/plain. Bei
application/json brauchst du den Zusatz nicht, dort ist UTF-8 vorgeschrieben.
Zum Mitnehmen
Zehn Statuscodes reichen für den Alltag. Und alles am Kopf der Antwort muss stehen, bevor das erste Zeichen des Inhalts rausgeht.
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.