Aus der CGI-Trickkiste (Teil 1) (Linux-Magazin, März 98)

Wer CGI-Skripts mit use CGI anfängt, hat es leichter, denn CGI.pm, Lincoln Steins praktisches Modul, holt Query-Parameter ab, verkürzt HTML-Ausgaben, hilft beim Debuggen - ein Tausendsassa in Modulform!

CGI.pm liegt der aktuellen Perl-Distribution perl-5.004_04 bei - ein weiterer Grund, aufzurüsten! Sonst liegt es auf dem CPAN unter

    CPAN/modules/by-module/CGI/CGI.pm-2.37b12.tar.gz

CGI.pm arbeitet eigentlich mit einem CGI-Objekt:

<include file=eg/cgiobj.pl filter=eg/usenew.sed>

Über die Objektreferenz $q stehen anschließend die Dienste als Methoden zur Verfügung:

<include file=eg/cgiobj.pl filter=eg/print.sed>

gibt den Server-Header und eine H1-Überschrift etwa als

    Content-Type: text/html
    
    <H1>Überschrift</H1>

aus. Im harten Alltagsgeschäft verkompliziert das Objekt $q aber nur unnötig die HTML-Ausgaben. Mit einer Tag-Liste hinter dem use-Befehl läßt CGI.pm fünf gerade sein und importiert die Methoden als Funktionen in den Namensraum des Skripts:

<include file=eg/cgistd.pl filter=eg/cut2.sed>

Für CGI-Parameter, Formulare und Basis-HTML reicht :standard, kommen Tabellen mit ins Spiel, muß auch noch :html3 'ran. Doch noch einmal zuurrrrück zum Anfang: Richtiges HTML mit Titel, Body und allem, was dazugehört, schafft

<include file=eg/startend.pl filter=eg/printgo.sed>

Das setzt den Titel des zurückgelieferten HTML-Dokuments auf Titel und die Hintergrundfarbe auf ein neutrales Weiß. Zwar kommt ab Perl 5.003 der vordere Teil einer Key/Value-Kombination wie z.B. -title => 'abc' auch ohne sichernde Anführungszeichen aus, da aber CGI.pm eine Unmenge von Funktionen exportiert, kann es zu Verwirrungen kommen: -title könnte der Perl-Interpreter auch als negativen Rückgabewert der von CGI.pm exportierten Funktion title() verstehen. Bei angeschalteter -w-Option mault perl entsprechend, '-title' schafft hier Klarheit und Ruhe.

So gibt's für jeden HTML-Tag eine Funktion aus CGI.pm. Listing basehtml.pl zeigt ein CGI-Skript, das die wichtigsten Tags aus dem HTML-Standard-Fundus verwendet: Listen, verschiedene Schriftarten, Hyperlinks.

Ins cgi-bin-Verzeichnis des Webservers verfrachtet, bringt ein Aufruf von http://server/cgi-bin/basehtml.pl im Browser die Ausgabe aus Abbildung 1 zum Vorschein.

Listing basehtml.pl

    01 #!/usr/bin/perl -w
    02 ######################################################################
    03 # Michael Schilli, 1998 (mschilli@perlmeister.com)
    04 ######################################################################
    05 
    06 use CGI qw/:standard/;
    07 
    08 print header,
    09       start_html('-title'   => 'HTML-Tags',
    10                  '-BGCOLOR' => 'white'),
    11 
    12       h2("Bullet-Liste"),
    13       ul(
    14          li( i("kursiv") ),
    15          li( b("fett") ), 
    16          li( tt("getippt") )
    17         ),
    18 
    19       hr,   # Querstrich
    20       p,    # Absatz
    21 
    22       h2("Glossar-Liste"),
    23       dl(
    24          dt("Hyperlink"), 
    25          dd( 
    26              a( {href => 'http://www.com'}, "Hier klicken!") 
    27            ),
    28          dt("Hyperlink als Image"), 
    29          dd(
    30              a( {href => 'http://www.com'}, 
    31                 img({src => "/pic.gif"}))
    32            )
    33         ),
    34       
    35       end_html;

Abb.1: HTML-Tags aus dem Standard-Fundus

Die Funktionen aus CGI.pm, die Texte HTML-formatiert zurückgeben, heißen wie die zugehörigen HTML-Tags: Aus <P> wird p(), aus <UL> (Bullet-Liste) wird ul(), mit dem Unterschied, daß HTML einen Ausdruck mit einem öffnenden und einem schließenden Tag umrahmt, während <CGI.pm> verschachtelte Funktionenaufrufe erfordert, um die gewünschte Struktur zu erzeugen. So liefert dl(ul('a'), ul('b')) etwa

    <DL><UL>a</UL><UL>b</UL></DL>

Um Attribute für ein HTML-Tag zu setzen, also z. B. mit dem SRC-Attribut die Quelldatei für ein Image-Tag festzulegen, erhält die entsprechende CGI.pm-Funktion einfach einen anonymen Hash als ersten Parameter, der Wertepaare der Form Attributname/Wert enthält, für einen Hyperlink also beispielsweise

    a({href => 'http://anywhere.com'}, "Hier klicken!");

was

    <A HREF=http://anywhere.com>Hier klicken!</A>

zurückliefert.

Tabellen

Tabellen enstehen mit den Funktionen table, TR, th und td, die den HTML-Tags TABLE, TR, TH und TD entprechen (daß die TR-Funktion großgeschrieben wird, liegt nur daran, daß es schon eine Perl-Funktion namens tr gibt).

<include file=eg/tabman.pl filter=eg/cut2.sed>

Die Newlines wären zwar aus HTML-Sicht nicht notwendig, erleichtern aber das Lesen der Ausgabe:

    <TABLE BORDER="1" BGCOLOR="beige">
     <TR><TH>Spalte1</TH> <TH>Spalte2</TH></TR> 
     <TR><TD>Feld 1/1</TD> <TD>Feld 1/2</TD></TR> 
     <TR><TD>Feld 2/1</TD> <TD>Feld 2/2</TD></TR> 
    </TABLE>

Stellt Perl-Code den Tabelleninhalt dynamisch zusammen, ergibt sich öfter das Problem, daß man eigentlich alle Tabellendaten innerhalb eines table(...)-Aufrufs sammeln möchte, aber es geht nicht, weil deren Erzeugung so kompliziert ist! Teilen wir die Arbeit doch auf:

<include file=eg/tabloop.pl filter=eg/cut2.sed>

Perls map-Funktion bewältigt schleifen-typische Aufgaben auch ohne Schleife. Das nachfolgende Snippet macht aus einer Liste von Spaltenüberschriften (@head) und einer LoL (List of Lists, enthält die Tabellenzeilen als Unter-Listen) ohne viel Federlesens eine Tabelle:

<include file=eg/tablol.pl filter=eg/cut2.sed>

Die erste Zeile im table()-Aufruf umschließt jeden Eintrag in @head mit <TH>...</TH> und rahmt das Ganze mit <TR>...</TR> ein - fertig ist der Tabellenkopf. Der map-Befehl liefert ja bekanntlich eine neue Liste zurück, indem er jedes Element der ihm überreichten Liste durch den in geschweiften Klammern entstehenden Ausdruck ersetzt.

Bei der Zeile mit den zwei map-Befehlen, die direkt darunter steht, wäre mir fast der Hirnkasten zersprungen, so angestrengt mußte ich nachdenken. Der Trick ist der: Der äußere map-Befehl ackert @lol durch, läßt die gefundenen Unter-Listen (@$_) vom inneren map-Befehl verarbeiten, schreibt sein <TR>...</TR> drumherum und gibt's zurück. Der innere map-Befehl rahmt jeden Unter-Listen-Eintrag mit <TD>...</TD> ein. Alles klar?

Weitere Tags

CGI.pm beherrscht alle HTML-Tags, auch wenn sie nicht unbedingt in der mitgelieferten Dokumentation (erscheint mittels perldoc CGI) stehen. Der HTML-Tag in Kleinbuchstaben ergibt meist die entsprechende Funktion aus CGI.pm. Attribute (wie z.B. das SRC-Attribut im IMG-Tag) setzt ein anonymer Hash als erstes Argument. Da diese Funktionen meist nicht mit dem :standard-Tag exportiert werden, hilft ein vorangestellter Modul-Bestimmer wie CGI::. So ändert

<include file=eg/font.pl filter=eg/cut2.sed>

die Fontgröße und -farbe in HTML mit

    <FONT SIZE="+1" COLOR="red">Achtung, Rot!</FONT>

Fehler

Tritt bei einem CGI-Skript ein schwerwiegender Fehler auf, so schwer, daß man am liebsten alles hinwerfen würde und das Skript abbrechen, stellt sich das Problem, daß vor der eigentlichen Fehlermeldung ein CGI-Header kommen muß, sonst zeigt der Browser ein unschönes Internal Server Error an und das heißt: Amateur am Werk!

Steht die Header-Ausgabe am Anfang und der kritische Teil des Skripts inmitten eines eval-Konstrukts, kann nichts schiefgehen: Läuft das Skript auf eine die-Anweisung, springt es aus dem eval-Block heraus und hinein! in die nachfolgende if-Bedingung, denn $@ führt in diesem Fall den Wortlaut der Fehlermeldung:

<include file=eg/erroreval.pl filter=eg/cut2.sed>

Um den eval-Block zu sparen, kann man auch die Pseudo-Signale __DIE__ und __WARN__ abfangen, und einen Handler für den Fehlerfall definieren:

<include file=eg/errorsig.pl filter=eg/cut2.sed>

Alle Ausgaben schleust dieser Code durch cgiprint, einer print-Funktion, die lediglich beim ersten Aufruf der Ausgabe den Header voranstellt. cgiprint 'merkt' sich diesen Zustand in der globalen Variablen header_printed.

Debugging

Bevor ein CGI-Skript im cgi-bin-Verzeichnis irgendwelchen Unsinn treibt, sollte es zumindest einmal fehlerfrei auf dem Trockenen laufen: Ein Perl-Skript mit eingebundenem CGI.pm 'weiß', ob es von der Kommandozeile oder vom Server aufgerufen wurde. Falls es im ersten Fall irgendwo CGI-Eingabe-Parameter verarbeitet, stoppt es vorher und nimmt Wertepaare der Form Key=Value entgegen:

    (offline mode: enter name=value pairs on standard input)
    a=b
    c=d
    CTRL-D

Anschließend fährt es normal mit der Bearbeitung fort, nur daß die Query-Parameter a und c die Werte b bzw. d enthalten.

Um in cgi-bin schnell mal auszuprobieren, welche Parameter ein Skript zugespielt bekommt, eignet sich das Skript

<include file=eg/dump.pl filter=eg/cut2.sed>

Die as_string()-Funktion gibt alle empfangenen Query-Parameter als Glossar-Liste in HTML aus, egal ob sie per GET- oder POST-Methode angelangten. Kritische Zeichen, die der Browser zur eindeutigen Übermittlung im Format %xx maskierte, entpackt CGI.pm dabei wieder vorschriftsmäßig.

Die Zeile mit dem map-Befehl schreibt noch sämtliche Umgebungsvariablen aus dem ENV-Hash schön formatiert in die Ausgabe. Der Server nutzt die Environment-Variablen als Schnittstelle, um dem Skript bestimmte Variablen zu übermitteln (z.B. den Original-Querystring, die IP-Adresse des Absenders, eventuell vorhandene Request-Header etc.).

Ins cgi-bin-Verzeichnis des Web-Servers gestellt und mit

    http://localhost/cgi-bin/dump.pl?a=b&c=d

aufgerufen, zeigt dump.pl im Browser etwa ein Bild nach Abbildung 2.

Abb.2: Ausgabe von dump.pl

Wie gelangen die Eingabedaten normalerweise zum CGI-Skript? Formulare, Formulare, Formulare heißt die Antwort. Wie man sie (natürlich mit CGI.pm) erstellt und Benutzer-Eingaben auswertet (natürlich auch mit CGI.pm), zeige ich in der nächste Folge. Habe die Ehre, Freunde!

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.