sslxy

cgi-und-logfiles

Nicht Web als Oberfläche. Sondern Web als Prozess, Ausgabe, Fehler und Spur im Log.

Ein großer Teil früher Webpraxis bestand nicht aus glatten Frontends, sondern aus Dingen, die für Außenstehende kaum sichtbar waren: Shell-Accounts, Dateirechte, CGI-Verzeichnisse, Header-Ausgabe, Formularparameter, Server-Umgebungsvariablen und am Ende vor allem Logfiles. Wer in den 1990er-Jahren mit dem Web ernsthaft arbeitete, lernte sehr schnell, dass eine Seite nicht nur aus HTML bestand. Sie bestand genauso aus Zuständen, Übergaben, Fehlermeldungen und der nüchternen Frage, warum ein Server etwas gerade nicht tat.

CGI war dabei kein modischer Zusatz, sondern der praktische Weg, aus statischer Auslieferung echte Reaktion zu machen. Ein Formular kam herein, ein Skript lief, ein Header musste stimmen, eine Ausgabe musste sauber codiert werden, Rechte mussten passen, und wenn etwas schieflief, stand die Wahrheit oft nicht auf dem Bildschirm, sondern im Error-Log. Gerade dort begann die eigentliche Arbeit.

Diese Seite hält diese Schicht bewusst nüchtern fest: CGI als Übergang zwischen Webserver und Programm, typische Perl-Praxis, GET und POST, Umgebungsvariablen, Headerfehler, Access- und Error-Logs, Debugging sowie die Art von geduldiger Systemarbeit, bei der man nicht rät, sondern liest.

System Diagnostic

> CGI / LOGGING ANALYSIS
RUNTIME Shell-Account, Webserver, CGI-Bin, Interpreter, Dateirechte INPUT GET, POST, QUERY_STRING, STDIN, Formulardaten, Umgebungsvariablen OUTPUT HTTP-Header, Content-Type, Leerzeile, danach eigentliche Nutzdaten LANGUAGE früh oft Perl, teils Shell, später auch andere Interpreter FAILURE 500er, Headerfehler, Rechtefehler, Pfadprobleme, Syntax, fehlende Module TRUTH Access-Log für Verkehr, Error-Log für Ursachen MINDSET lesen, prüfen, eingrenzen – nicht raten
Eine gute Webphase beginnt nicht dort, wo etwas schön aussieht, sondern dort, wo sich Fehler sauber nachweisen lassen.
[cgi/overview]

CGI überhaupt: was es war und warum es wichtig war

CGI – das Common Gateway Interface – war im frühen Web der schlichte Mechanismus, mit dem ein Webserver ein externes Programm starten und dessen Ausgabe als HTTP-Antwort zurückgeben konnte. Das klingt heute fast banal. Damals war es der entscheidende Schritt vom reinen Dokumentabruf zur tatsächlich reagierenden Website.

Eine statische HTML-Datei musste nur gelesen und ausgeliefert werden. Ein CGI-Skript dagegen wurde bei einem Request tatsächlich ausgeführt. Das hieß: Parameter entgegennehmen, eventuell Daten prüfen, Inhalte zusammensetzen, vielleicht eine Mail verschicken, vielleicht eine Datei lesen oder schreiben und am Ende eine korrekte HTTP-Antwort zurückgeben. Schon damit war das Web nicht mehr nur ein Dokumentenraum, sondern eine Prozessschicht.

Gerade deshalb war CGI so prägend. Es war klein genug, um begreifbar zu sein, und direkt genug, um jeden Fehler sofort offenzulegen. Wer CGI schrieb, spürte unmittelbar, dass der Server nichts „errät“. Fehlt ein Header, ist der Header kaputt. Stimmt die Ausgabe-Reihenfolge nicht, ist die Antwort kaputt. Ist das Skript nicht ausführbar, läuft es nicht. Und wenn der Interpreter ein Problem hat, landet man im Error-Log.

[CGI Grundmodell]
> Browser sendet Request
> Webserver erkennt CGI-Ziel
> Skript/Programm wird gestartet
> Parameter werden über Umgebung / STDIN übergeben
> Skript gibt Header + Inhalt zurück

„CGI war keine Magie. Es war nur der Moment, in dem der Server statt einer Datei ein Programm laufen ließ.“

[runtime/environment]

Shell-Account, Serverpfade und Laufzeitumgebung

Wer frühe CGI-Praxis nur aus heutiger Framework-Sicht betrachtet, unterschätzt leicht, wie nah das Ganze an der Server- und Benutzerumgebung hing. Man arbeitete nicht in einer abstrahierten Plattform, sondern in einem realen Dateibaum mit Benutzerrechten, Gruppenrechten, Skriptpfaden, Shebang-Zeilen und konkreten Interpreter-Versionen.

Der Shell-Account war dabei oft nicht bloß Bequemlichkeit, sondern Voraussetzung. Man musste Dateien hochladen, Rechte setzen, Skripte direkt aufrufen, vielleicht lokal mit der Shell testen, Logfiles lesen, Verzeichnisse durchsuchen und herausfinden, ob ein Problem im Skript, im Pfad, in den Rechten oder in der Serverkonfiguration lag. Das war keine getrennte DevOps-Welt. Das gehörte schlicht zur Webarbeit dazu.

  • cgi-bin oder freigeschaltete CGI-Verzeichnisse: nicht jede Datei durfte einfach ausgeführt werden.
  • Shebang: der Interpreterpfad musste stimmen, sonst lief das Skript nicht oder falsch.
  • Dateirechte: Lesbarkeit allein reichte nicht – das Skript musste vom Serverprozess sinnvoll ausführbar sein.
  • Pfadlogik: relative und absolute Pfade waren reale Fehlerquellen, besonders beim Zugriff auf Includes, Templates oder Datendateien.
  • Umgebung: REQUEST_METHOD, QUERY_STRING, CONTENT_LENGTH, REMOTE_ADDR, HTTP_USER_AGENT und ähnliche Variablen waren echte Arbeitsbasis.

Diese Nähe zur Infrastruktur war lehrreich. Man sah sehr schnell, dass ein Webprozess keine abstrakte Oberfläche ist, sondern ein ausgelöster Systemvorgang unter konkreten Bedingungen. Wer das einmal sauber begriffen hatte, verlor meistens die Lust an mystifizierten Erklärungen darüber, warum „der Server heute irgendwie spinnt“.

[http/headers]

Header zuerst: warum kleinste Ausgabefehler alles stoppen

Einer der frühesten und wichtigsten Lerneffekte bei CGI war die Tatsache, dass die HTTP-Antwort formal beginnen musste. Das Skript durfte nicht einfach „irgendwas“ ausgeben. Es musste zuerst einen gültigen Header liefern – klassisch etwa Content-Type: text/html – dann eine Leerzeile und erst danach den eigentlichen Inhalt.

Für heutige Systeme klingt das trivial. In der frühen Praxis war es eine konstante Fehlerquelle. Ein zusätzliches Leerzeichen an falscher Stelle, eine Warnung vor dem Header, eine kaputte Zeichenausgabe, ein Modulhinweis, ein Debug-Print zu früh – und schon war die Antwort aus Sicht des Servers beschädigt. Der Browser zeigte oft nur einen generischen Fehler. Das Error-Log hingegen war meist präziser.

#!/usr/bin/perl
print "Content-Type: text/html\\n\\n";
print "<!doctype html><html>...";

Gerade daran lernte man ein frühes Gefühl für Protokolldisziplin. Das Problem war selten „CGI allgemein“. Das Problem war meistens sehr konkret: falscher Header, Header zu spät, Zeichensatzfrage, ungewollte Ausgabe, fehlende Leerzeile oder ein Skriptabbruch, bevor überhaupt ein Header zurückgegeben wurde.

[http/input]

GET, POST und die tatsächliche Eingaberealität

Die begriffliche Trennung zwischen GET und POST war früh klar, aber in der Praxis musste man sie wirklich verstehen, nicht nur benennen. GET hing seine Parameter an die URL – sichtbar, speicherbar, bookmarkbar, mit Längen- und Zeichengrenzen je nach Umgebung. POST übergab Daten im Request-Body. Das wirkte „sauberer“, verlangte aber korrektes Lesen von CONTENT_LENGTH und die disziplinierte Verarbeitung des Eingabestroms.

Methode Praktische Eigenschaft
GET Parameter in der URL, schnell sichtbar, gut für einfache Abfragen und Zustände, aber unpassend für längere oder vertraulichere Eingaben.
POST Daten im Request-Body, geeignet für Formulareingaben, längere Inhalte und klarere Trennung zwischen Ziel und Nutzdaten.

Das Entscheidende lag jedoch nicht nur in der Methode selbst, sondern in der Verarbeitung. URL-Decoding, Pluszeichen als Leerzeichen, Prozentkodierung, Sonderzeichen, Zeilenumbrüche, Zeichensatzprobleme – all das musste das Skript sauber behandeln. Wer dabei nachlässig war, bekam keine abstrakte Theoriediskussion, sondern kaputte Formulardaten, abgeschnittene Felder oder später sogar Sicherheitsprobleme.

[CGI Input Modell]
> REQUEST_METHOD prüfen
> bei GET: QUERY_STRING lesen
> bei POST: CONTENT_LENGTH lesen + STDIN auswerten
> danach dekodieren, prüfen, erst dann verwenden
[cgi/perl]

Perl-CGI in der frühen Praxis

Frühes CGI war oft praktisch gleichbedeutend mit Perl. Nicht weil Perl die einzige Möglichkeit gewesen wäre, sondern weil es auf vielen Systemen vorhanden, textstark, pragmatisch und gut genug für genau diese Übergangsschicht war. Formulare lesen, Strings zerlegen, Mail zusammensetzen, Dateien öffnen, Datum und Zeit formatieren, kleinere Zustände verwalten – all das ließ sich in Perl mit vergleichsweise wenig Aufwand umsetzen.

Gleichzeitig war Perl kein Schutzraum. Wenn das Skript schlampig war, war das Ergebnis schlampig. Wenn Variablen unklar geführt wurden, wurde die Fehleranalyse unerquicklich. Wenn reguläre Ausdrücke schlecht saßen oder Eingaben blind übernommen wurden, entstanden echte Probleme. Gerade deshalb war frühe Perl-Praxis für viele lehrreich: nicht wegen Eleganz im akademischen Sinn, sondern weil sich die Folgen jeder Nachlässigkeit unmittelbar zeigten.

Was gut funktionierte

Stringverarbeitung, kleine Webformulare, Mail-Dispatch, Zustandsdateien, schnelle Prototypen, klare Textausgabe.

Wo es unangenehm wurde

unsaubere Eingabeprüfung, Dateirechte, Shell-Aufrufe, Zeichensatzfragen, nicht abgefangene Warnungen, schwer lesbar gewordene Ein-Datei-Skripte.

Wer mit Perl-CGI arbeitete, lernte außerdem etwas Wichtiges über Webdynamik: Der eigentliche Aufwand lag selten im sichtbaren HTML. Der Aufwand lag in korrekter Übergabe, validen Zuständen und reproduzierbarer Fehlersuche. Genau deshalb waren Access- und Error-Logs kein Nebenthema, sondern permanenter Bestandteil der Arbeit.

[cgi/forms]

Formulare, Mail-Skripte und die Realität dahinter

Für viele war CGI zuerst über Formulare sichtbar. Kontaktformulare, einfache Bestellanfragen, Gästebuch-Einträge, kleine Suchmasken oder Konfigurationsübergaben – all das lief im Kern auf dieselbe Frage hinaus: Wie kommen Benutzereingaben kontrolliert auf dem Server an und was passiert dann damit?

In der frühen Praxis war das häufig ein Mail-Skript. Der Browser schickte ein Formular, CGI nahm Felder entgegen, prüfte sie mehr oder weniger gut und setzte daraus eine E-Mail oder eine lokale Datendatei zusammen. Technisch simpel, praktisch aber voller Fallstricke: fehlende Feldprüfung, ungefilterte Sonderzeichen, kaputte Newlines, Header-Manipulation, falsche Rechte auf temporären Dateien oder ein Sendmail-Aufruf, der lokal anders funktionierte als gedacht.

  • Pflichtfelder: nicht nur im Browser markieren, sondern serverseitig wirklich prüfen.
  • Zeilenumbrüche: harmlose Textfelder konnten Mails und Dateiformate schnell unlesbar machen, wenn man sie blind übernahm.
  • Mailversand: funktionierte nur dann zuverlässig, wenn lokaler MTA, Rechte und Aufrufpfad sauber abgestimmt waren.
  • Dankeseiten und Redirects: mussten als HTTP-Antwort technisch ebenso korrekt gebaut werden wie jede normale HTML-Seite.

Gerade daran trennte sich frühe Webromantik von tatsächlicher Webarbeit. Ein Formular war eben nicht „fertig“, nur weil es hübsch erschien. Es war erst dann brauchbar, wenn Eingaben sauber entgegengenommen, protokolliert, geprüft und nachvollziehbar verarbeitet wurden.

[logs/access]

Access-Log: wer kam, wann und womit

Das Access-Log war die rohe Spur des Verkehrs. Dort stand nicht die philosophische Bedeutung eines Zugriffs, sondern ganz praktisch: wann etwas angefordert wurde, von welcher Adresse, mit welcher Methode, auf welches Ziel, mit welchem Statuscode, in welcher Größe und oft mit welchem Referer oder User-Agent.

Für frühe Webarbeit war das enorm nützlich. Man sah, ob eine Seite wirklich aufgerufen wurde, ob ein CGI-Endpunkt 200 oder 500 lieferte, ob ein Formular mit GET statt POST ankam, ob ein Bot über alte Pfade lief oder ob jemand immer wieder dieselbe defekte URL abfragte. Das Access-Log war damit kein Statistikspielzeug, sondern Betriebsprotokoll.

127.0.0.1 - - [12/Mar/1998:19:14:08 +0100] "GET /cgi-bin/test.pl?x=1 HTTP/1.0" 200 482
203.0.113.10 - - [12/Mar/1998:19:15:02 +0100] "POST /cgi-bin/formmail.pl HTTP/1.0" 500 213
198.51.100.44 - - [12/Mar/1998:19:16:41 +0100] "GET /nonexistent.cgi HTTP/1.0" 404 178

Gerade in Kombination mit dem Error-Log wurde daraus eine brauchbare Arbeitsmethode: Im Access-Log sah man, dass etwas passiert war. Im Error-Log sah man oft, warum es scheiterte.

[logs/error]

Error-Log: wo die Wahrheit über Fehler stand

Das Error-Log war für CGI meistens der eigentliche Arbeitsplatz. Browserfehler waren oft unpräzise. Eine leere Seite, ein 500 Internal Server Error oder einfach nur eine kaputte Antwort sagten wenig. Das Error-Log dagegen sagte häufig das Entscheidende: Syntaxfehler, fehlende Module, falscher Interpreterpfad, unzulässige Rechte, Header-Probleme, Warnungen vor dem Header, Datei nicht gefunden, uninitialisierte Variablen oder Shell-Aufrufe, die nicht durchliefen.

Wer ernsthaft mit CGI arbeitete, gewöhnte sich früh an diese Reihenfolge: Nicht zuerst den Browser anstarren, sondern das Error-Log lesen. Dort lag meistens die eigentliche Wahrheit. Und wenn dort nichts stand, war das auch eine Information – dann musste man die Logkonfiguration, die Rechte oder den tatsächlichen Ausführungspfad prüfen.

[Fri Mar 12 19:15:02 1998] [error] Premature end of script headers: formmail.pl
[Fri Mar 12 19:15:02 1998] [error] syntax error at /home/user/cgi-bin/formmail.pl line 47
[Fri Mar 12 19:18:11 1998] [error] malformed header from script. Bad header=Hello: test.pl
[debug/practice]

Debugging ohne Komfortschicht

Frühe CGI-Arbeit bedeutete oft Debugging ohne die Bequemlichkeiten späterer Entwicklungsumgebungen. Kein hübscher Live-Inspector, kein eingebauter Stacktrace im Browser, kein vollintegriertes Observability-System. Stattdessen: Skript lokal aus der Shell aufrufen, Testparameter manuell setzen, Header-Ausgaben kontrollieren, temporäre Debug-Ausgaben einbauen, Logfiles lesen und dann die Kette aus Request, Skript und Antwort gedanklich sauber nachverfolgen.

Gerade diese Reduktion hatte einen Vorteil: Man lernte, die Schichten zu trennen. Läuft das Skript überhaupt? Kommt der Interpreter hoch? Sind die Variablen gesetzt? Ist die Methode korrekt? Ist die Content-Length plausibel? Kommt vor dem Header ungewollte Ausgabe? Ist ein Dateipfad relativ falsch? Ist ein Modul vorhanden? Jede Frage griff in eine andere Ebene – und genau so musste man sie behandeln.

Typischer Ablauf

Reproduzieren, Access-Log prüfen, Error-Log lesen, Skript direkt testen, Eingabedaten isolieren, Ausgabeformat kontrollieren, Rechte und Pfade verifizieren.

Typische Haltung

Weniger Vermutung, mehr Eingrenzung. Weniger Oberfläche, mehr tatsächlicher Laufweg. Genau dort wurde frühe Webarbeit ernst.

„Debugging hieß oft nicht, auf eine IDE zu warten. Es hieß lesen, was wirklich passiert war.“

[cgi/security]

Frühe Sicherheitsprobleme und naive Skripte

Frühes CGI war nicht nur praktisch, sondern auch berüchtigt für naive Lösungen. Gerade Mail-Skripte, Gästebücher, Suchmasken, Dateioperationen oder Shell-nahe Hilfsskripte wurden oft schnell geschrieben und schlecht abgesichert. Das war nicht bloß ein historischer Schönheitsfehler. Es war echte Angriffsfläche.

Typische Probleme waren ungeprüfte Eingaben, blindes Durchreichen an Shell-Aufrufe, Header-Injection in Mail-Skripten, Dateipfade mit unzureichender Kontrolle, schreibbare Verzeichnisse ohne klare Trennung oder schlicht ein zu großes Vertrauen in das, was „der Benutzer schon nicht absichtlich falsch machen wird“.

  • Ungereinigte Eingaben: schon einfache Sonderzeichen konnten Logik und Mailformate beschädigen.
  • Shell-Nähe: wer Eingaben an Systemkommandos weiterreichte, brauchte saubere Trennung und Filterung.
  • Offene Formmail-Skripte: wurden schnell zu Spam-Werkzeugen oder Missbrauchspunkten.
  • Rechtechaos: zu großzügige Schreib- oder Ausführungsrechte machten Fehler und Missbrauch wahrscheinlicher.

Genau deshalb blieb von dieser Zeit nicht nur Webromantik, sondern auch ein dauerhafter Reflex: lieber weniger Dynamik, wenn sie nicht wirklich gebraucht wird; lieber klare serverseitige Prüfung; lieber statische Auslieferung dort, wo keine Prozesslogik nötig ist. Wer frühe CGI-Fehler gesehen hatte, brauchte keine Theorie darüber, warum unnötige Dynamik unnötige Risiken mitbringt.

[mindset/log-reading]

Warum Logfiles die eigentliche Schule waren

Rückblickend liegt ein wesentlicher Wert dieser Zeit nicht einmal nur in CGI selbst, sondern im Umgang mit Logfiles. Access- und Error-Logs zwangen zu einer Nüchternheit, die später oft verloren ging. Dort stand kein Marketing, kein freundliches Narrativ, keine weichgespülte Zusammenfassung. Dort stand, was passiert war.

Man sah Requests, Fehlercodes, Pfade, Zeitpunkte, Methoden, Warnungen, Headerprobleme und die Folgen kleinster Ausgabefehler. Genau dadurch lernte man, Systeme nicht als Story zu lesen, sondern als Ereignisketten. Diese Art zu denken bleibt nützlich – nicht nur im Web, sondern überall dort, wo Technik nachvollziehbar und wartbar bleiben soll.

Wer so gelernt hat, entwickelt fast automatisch eine Skepsis gegen unnötige Schichten, die das tatsächliche Verhalten verdecken. Nicht weil moderne Werkzeuge grundsätzlich schlecht wären, sondern weil gute Technik lesbar bleiben sollte. Ein Access-Log oder Error-Log ist brutal ehrlich. Gerade das ist sein Wert.

„Logfiles loben nichts. Genau deshalb sind sie brauchbar.“

[cgi/conclusion]

Was davon geblieben ist

CGI war nicht perfekt, nicht elegant in jedem Detail und sicher nicht frei von naiven Konstruktionen. Aber es hatte eine Stärke, die man ernst nehmen sollte: Es machte den Weg zwischen Request, Ausführung und Antwort sichtbar. Gerade deshalb war es eine gute Schule.

Wer mit CGI, Shell-Accounts und Logfiles gearbeitet hat, lernte früh, dass Webentwicklung mehr ist als sichtbares Layout. Sie ist Protokoll, Prozess, Eingabe, Ausgabe, Rechte, Pfadlogik und Fehleranalyse. Genau daraus kommt ein Teil der späteren Vorliebe für ruhige, statische, nachvollziehbare Lösungen.

Die Host- und Account-Seite darunter beschreibt shell-accounts-und-hosts.htm. Die spätere Haltung gegenüber Struktur und Werkzeugen steht auf philosophy.htm. Und die Linie der frühen Webpraxis insgesamt bleibt auf webmaster.htm sichtbar.

[CGI] in einem Satz
> Request wird Prozess
> Prozess wird Ausgabe
> Fehler landen im Log
> wer lesen kann, versteht das System
↑ Nach oben