mitmario.dev

Kopfzeilen und Statuscodes

PHP Sandbox 4 Min Lesezeit 4 BeispieleLektion 4 von 7

Eine Antwort besteht aus Statuszeile, Kopfzeilen und Inhalt. Den Inhalt schreibst du längst mit echo. Für die beiden anderen Teile gibt es zwei Funktionen, und mehr braucht man nicht.

header("Name: Wert") setzt eine Kopfzeile. http_response_code(404) setzt den Statuscode.

Der Content-Type entscheidet, was der Browser daraus macht

Eine Kopfzeile, bevor etwas rausgeht
<?php

// Vor der ersten Ausgabe. Genau darauf kommt es an.
header("Content-Type: text/plain; charset=UTF-8");

echo "Diese Antwort traegt text/plain.\n\n";
echo "Deshalb siehst du das hier als Zeichen und nicht als Ueberschrift:\n";
echo "<h1>Ich bin keine Ueberschrift</h1>\n";

Ohne dein Zutun schickt PHP Content-Type: text/html; charset=UTF-8 mit, und deshalb hat bisher alles funktioniert: Der Browser hat deine Ausgabe als Markup gelesen und eine Seite daraus gebaut.

Das Beispiel dreht das um. Es setzt text/plain, und plötzlich ist dieselbe Ausgabe kein Markup mehr, sondern Text. Im Browser steht die Überschrift deshalb mit ihren spitzen Klammern da.

Das ist keine Spielerei. Es ist derselbe Mechanismus, mit dem der Browser weiß, dass eine Antwort ein Bild ist, eine Textdatei zum Herunterladen oder eine Antwort für ein anderes Programm. Und der Zusatz charset=UTF-8 ist der Grund, warum deine Umlaute ankommen: Er sagt, in welcher Kodierung die Zeichen gemeint sind.

Die Kopfzeile selbst siehst du auf zwei Wegen. Im Reiter Netzwerk steht bei jeder Seite die Antwort mit ihren Kopfzeilen, darunter Content-Type. Bei einer Antwort, die keine Seite ist, wie hier, trägt der Reiter nur Methode und Pfad, denn ohne Seite gibt es nichts, was ihm die Kopfzeilen herüberreicht. Dann hilft das Terminal: curl -si localhost:3000/ holt dieselbe Antwort noch einmal vom Server und schreibt sie so hin, wie sie über die Leitung geht, Statuszeile, Kopfzeilen, Leerzeile, Inhalt. Das ist die Form aus Lektion 10.1, diesmal echt.

Die eiserne Regel: vor der ersten Ausgabe

Zu spät ist zu spät
<?php

echo "<!doctype html>\n";
echo "<html lang=\"de\"><head><meta charset=\"utf-8\"><title>Zu spaet</title></head>\n";
echo "<body>\n";
echo "<h1>Zu spaet</h1>\n";

// Ab hier ist der Kopf der Antwort schon unterwegs.
header("Content-Type: text/plain; charset=UTF-8");
http_response_code(404);

echo "<p>Sind die Kopfzeilen schon raus? " . (headers_sent() ? "ja" : "nein") . "</p>\n";
echo "<p>Du liest das als Seite und nicht als Zeichen: text/plain kam nie an.</p>\n";
echo "</body></html>\n";

Und jetzt die Regel, an der jeder einmal hängenbleibt. Kopfzeilen und Statuscode musst du setzen, bevor du das erste Zeichen ausgibst.

Der Grund steckt in Lektion 10.1: Erst gehen Statuszeile und Kopfzeilen über die Leitung, dann die Leerzeile, dann der Inhalt. Sobald dein erstes echo gelaufen ist, hat PHP den Kopf schon verschickt. Er ist weg. Man kann ihn nicht zurückholen.

Das Beispiel zeigt beides auf einmal: headers_sent() sagt ja, die Antwort trägt weiter text/html, und der Statuscode ist 200 geblieben. Der Versuch war wirkungslos, und PHP sagt das auch. Es meldet Cannot modify header information - headers already sent by, dazu die Stelle, an der die Ausgabe angefangen hat, und die Zeile, in der du es zu spät versucht hast. Zwei Angaben, die zusammen fast immer reichen.

Die Meldung steht mitten auf der Seite, zwischen deiner Überschrift und dem nächsten Absatz. Das ist das -d display_errors=1 aus dem Startbefehl in Lektion 10.2: Der Server dieses Kurses zeigt Fehler dort an, wo sie passieren, so wie es jeder Entwicklungsserver tut. Beim Prüfen steht dieselbe Meldung zusätzlich im Terminal auf dem Fehlerkanal, und genau dorthin schaut die letzte Zeile jeder Prüfliste. Auf einem Server im echten Betrieb ist die Anzeige aus, dort landet die Meldung nur im Log; deshalb sucht man den Fehler dort oft lange, hier siehst du ihn sofort. Der Dateipfad in der Meldung ist der Ort, an dem deine Datei auf der Maschine liegt.

Der häufigste Auslöser ist übrigens kein echo, sondern etwas, das gar nicht wie Ausgabe aussieht: ein Leerzeichen oder ein Zeilenumbruch hinter einem schließenden ?>. Genau davor warnt Lektion 1.3, und hier siehst du, warum. Alles außerhalb der Marken geht unverändert hinaus, auch ein einzelner Umbruch, und danach sind die Kopfzeilen weg. Deshalb bekommt eine reine PHP-Datei am Ende kein ?>.

Einen Statuscode selbst setzen

Eine eigene 404
<?php

http_response_code(404);

echo "<!doctype html>\n";
echo "<html lang=\"de\"><head><meta charset=\"utf-8\"><title>Nicht gefunden</title></head>\n";
echo "<body>\n";
echo "<h1>Nicht gefunden</h1>\n";
echo "<p>Diese Seite antwortet mit 404 und sieht trotzdem aus wie jede andere.</p>\n";
echo "<p>Der Statuscode steht nicht im Text, sondern in der Statuszeile darueber.</p>\n";
echo "</body></html>\n";

http_response_code(404) macht aus einer ganz normalen Antwort eine, die „gibt es nicht” bedeutet. Der Text der Seite ändert sich dadurch nicht, und das ist genau der Punkt: Was ein Mensch liest, steht im Inhalt, was ein Programm liest, steht in der Statuszeile. Im Browser sieht die Seite aus wie jede andere; im Reiter Netzwerk steht bei ihr eine 404 statt der 200, die bisher an jeder Zeile stand. Mit dem Auge allein wäre der Unterschied nicht zu sehen, und genau darum geht es.

Warum das mehr ist als eine Textausgabe, sieht man an denen, die keine Augen haben. Eine Suchmaschine nimmt eine Seite mit 404 nicht in den Index. Ein Programm, das deine Seite abfragt, merkt am Statuscode, dass etwas fehlt, ohne den Text lesen zu müssen. Und ein Browser weiß, dass er diese Antwort nicht zwischenspeichern sollte.

Eine Seite, die „Diese Seite gibt es nicht” schreibt und dabei 200 sendet, sagt also zwei verschiedene Dinge an zwei verschiedene Leser. Man nennt das eine weiche 404, und sie ist einer der häufigsten Fehler auf selbstgebauten Seiten.

Wenn die Antwort gar keine Seite ist

Eine Antwort, die keine Seite ist
<?php

$preise = ["kaffee" => 850, "brot" => 320, "aepfel" => 240];

header("Content-Type: application/json");

echo json_encode($preise, JSON_PRETTY_PRINT);

Hier steht kein Markup mehr, sondern JSON aus Lektion 8.3, dazu die passende Kopfzeile application/json. Genau so antwortet eine Schnittstelle: nicht für ein Auge, sondern für ein anderes Programm.

Achte darauf, was im Browser passiert. Er bekommt etwas, das keine Seite ist, und zeigt dir deshalb den nackten Text. Das ist richtig so und kein Fehler. Eine Antwort ohne text/html hat nichts, was sich rendern ließe. Und wie beim ersten Beispiel ist das Terminal der Ort, an dem du die Kopfzeile dazu siehst: curl -si localhost:3000/ zeigt Content-Type: application/json über der Leerzeile und das JSON darunter.

Diese drei Zeilen sind schon eine kleine API. Was noch fehlt, damit sie brauchbar wird, ist der Umgang mit dem Fall, in dem es die gewünschten Daten nicht gibt. Und dafür brauchst du beides, was diese Lektion zeigt: den richtigen Statuscode und die richtige Kopfzeile. Genau das ist die Aufgabe.

Zum Mitnehmen

Bisher hast du bestimmt, was in der Antwort steht. Jetzt bestimmst du auch, was darübersteht: welchen Statuscode sie trägt und wie der Browser ihren Inhalt lesen soll. Die einzige Regel dabei ist eine Reihenfolge, und wer sie verletzt, sucht den Fehler stundenlang.

Jetzt du

Basis Konto, kostenlos

Zu 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 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.