Zurück zur Übersicht
  

CGI-Programmierung - The hard way :-)
von A. Grupp

Bei der CGI-Programmierung gibt es eine Vielzahl von Tätigkeiten die eigentlich in jedem CGI-Skript durchgeführt werden müssen. Dies betrifft z.B. die folgenden Punkte:

Die dafür notwendigen Perl-Anweisungen sind zwar normalerweise nicht gerade kompliziert, trotzdem ist es aber eine lästige Pflichtübung diese jedesmal neu zu programmieren. Dieses Kapitel trägt die Zusatzbezeichnung "The hard way", weil es eigentlich für genau diese Pflichtübungen sehr komfortable Perl-Module gibt. Trotzdem will ich Ihnen hier die einzelnen Schritte aufzeigen die notwendig sind um ohne diese Module zum Erfolg zu gelangen. Sie können dadurch besser erkennen welche Einzelschritte das HTTP-Protokoll vorsieht und sind damit für evtl. Fehlersuchen besser gerüstet. Außerdem kann es sein, dass auf dem von ihnen verwendeten WWW-Server die notwendigen Perl-Module nicht installiert sind.

Hello world

Wie bei allen Programmierübungen soll auch hier das berühmte "Hello world" am Anfang stehen. Das nachfolgende CGI-Skript macht nichts anderes als diesen kurzen Text als HTML-Datei auszugeben. Es dient auch zum Test ob die CGI-Konfiguration ihres WWW-Servers richtig ist. Wenn dieses Skript nicht funktioniert, überprüfen Sie bitte zuerst die Konfiguration ihres Servers.

#!/usr/bin/perl -w

print "Content-type: text/html\n\n";
print "
<html>
 <head>
  <title>Hello world!</title>
 </head>
 <body>
  <h1>Hello world!</h1>
 </body>
</html>";

Beispiel 1: Hello world!

Aus Perl-Sicht besteht dieses Skript nur aus zwei print-Anweisung. Die Ausgabe selbst stellt eine einfachste HTML-Datei dar.

Einzige Unterschied zu einem ganz normalen HTML-Dokument ist die erste Zeile der Ausgabe. Diese wird bei normalen HTML-Dateien vom WWW-Server erzeugt. Selbiger erkennt an der Dateiendung "html"; (bzw. "htm") den MIME-Typ der Daten und fügt die "Content-type:"-Zeile ein. Bitte beachten Sie aber noch den Zeilenumbruch am Ende der Zeile sowie die nachfolgende Leerzeile. Diese beiden Merkmale sind unbedingt erforderlich da sie das Ende des http-Headers an den Browser übermitteln! Das Fehlen dieser beiden Merkmale ist eine der häufigsten Fehlerursachen von Anfägern in CGI-Skripten.

Die "Content-type:"-Zeile ist Bestandteil des http-Protokolls und muß als letzte Zeile des http-Headers an den Client übermittelt werden. Da unsere Perl-Skripte verschiedene Ergebnisse haben können (HTML-Code, normaler Text, dynamisch erstellte Grafiken, ...), kann der Server hierbei den MIME-Typ nicht selbst bestimmen und überläßt dies dem Skript. Die vorangegangenen Zeilen des http-Headers (z.B. Expires: oder Date:) werden hier nach wie vor vom WWW-Server erzeugt. Diese Aufgabe kann das Perl-Skript jedoch auch übernehmen.

Persönliche Begrüßung

Um die Ergebnisse des vorigen Beispiels (statische HTML-Seite) zu erhalten muß natürlich kein CGI-Skript programmiert werden. Deren Einsatzgebiet ist vielmehr die dynamische Erstellung von HTML-Seiten. Lassen Sie uns deshalb das "Hello-world";-Skript umschreiben, so daß eine persönliche Begrüßung erfolgt. Hierzu müssen wir jedoch erst denn Namen des Client-Bedieners in Erfahrung bringen. Hierzu verwenden wir ein HTML-Formular mit einem Eingabefeld.

<html>
 <head>
  <title>Wie heist du denn?</title>
 </head>
 <body>
  <form action="bsp2.pl" method=get>
   <input type=text name="vorname" size=30>
   <input type=submit value="Ich haben fertig!">
  </form>
 </body>
</html>

Das Formular verwendet die Methode "get"; um die Formulardaten an das Perl-Skript "bsp2.pl"; zu übermitteln. Dabei werden die Formularwerte durch ein Fragezeichen getrennt an die URL angehängt. Wird das Formularfeld z.B. mit "Michael"; ausgefüllt, so wird anschließend vom Browser die URL bsp2.pl?vorname=Michael aufgerufen. Der URL-Teil nach dem ? wird mit Hilfe der Umgebungsvariablen QUERY_STRING an das CGI-Skript übergeben. Damit hat diese Variable folgenden Wert:

QUERY_STRING => vorname=Michael

Sie finden in diesem Übergabewert die Bezeichnung des Formularfelds und den vom Anwender eingegebenen Wert wieder. Man könnte auch sagen: die Bezeichnung des Formularfelds ist der Variablenname, der eingegebene Wert ist der Variableninhalt. Allerdings stehen diese beiden Angaben einem Perl-Skript nun nicht einzeln zur Verfügung. Sie befinden sich vielmehr innerhalb einer einzelnen Zeichenkette - der Environment-Variablen QUERY_STRING. Uns ist aber schon bekannt, dass das "="-Zeichen die Grenze zwischen den beiden Angaben bildet. Somit können wir die Perl-Funktion split() verwenden um die beiden Teile zu extrahieren.

$qrystrg = $ENV{'QUERY_STRING'};
($varname, $varwert) = split(/=/, $qrystrg);

Die Funktion split() trennt eine Zeichenkette (das 2. Argument) anhand eines Suchmusters (1. Argument) auf. Das Suchmuster darf auch mehrfach auftreten. Allerdings liefert split() dann auch weitere Werte als Ergebnis. Der Rückgabewert von split() ist ein Array. Im obigen Beispiel wissen wir ja aber schon wieviele Rückgabewerte von split() zu erwarten sind. Deshalb wird gleich ein anonymisiertes Array (die zwei runden Klammern) verwendet. Es besteht fest aus den beiden Variablen $varname und $varwert und kann somit nur zwei Elemente aufnehmen. Unmittelbar nachdem das Array durch split() gefüllt wurde, existiert es mangels eines Namens auch schon nicht mehr. Die beiden Einzelvariablen überleben dies aber und stehen auch im weiteren Skriptverlauf zur Verfügung.

Damit könnte das personalisierte "Hello world"-Skript folgendermaßen aussehen:

#!/usr/bin/perl -w

$qrystrg = $ENV{'QUERY_STRING'};

($varname, $varwert) = split(/=/, $qrystrg);

print "Content-type: text/html\n\n";
print "
<html>
 <head>
  <title>Hallo $varwert</title>
 </head>
 <body>
  <h1>Hallo $varwert</h1>
 </body>
</html>";

Beispiel 2: Persönliche Begrüßung

Die einzelnen Schritte:

  1. Umgebungsvariablen stehen Perl-Skripten im vordefinierten Hash %ENV zur Verfügung. Damit kann auf die übergebenen Formulardaten zugegriffen werden.
  2. Der Variableninhalt wird mit Hilfe der split-Funktion gewonnen.
  3. Nun folgt die bereits bekannte Erzeugung der HTML-Seite. Anstelle von "world"; wird aber jeweils der übergebene Vorname in die Ausgabe eingefügt.

Ein universelles Feedback-Skript

Das Formular aus dem vorigen Beispiel hatte nur ein einziges Eingabefeld und stellt somit eher einen Sonderfall dar. Bei Formularen mit mehreren Eingabefeldern wird der Übergabewert für das CGI-Skript ähnlich wie im vorigen Beispiel gebildet. Auch in so einem Fall wird für jedes Formularelement die obige Kombination aus Elementname und Elementwert gebildet (z.B. vorname=Michael und nachname=Schuhmacher). Diese Kombinationen werden anschließend mit Hilfe des &-Zeichens verkettet. Das sieht dann folgendermaßen aus:

vorname=Michael&nachname=Schuhmacher

Unser Skript muß nun also noch einen zusätzlichen split()-Lauf einlegen um die &-Verkettung wieder aufzulösen.

#!/usr/bin/perl -w
# -w => ausfuehrlichere Warnungen/Fehlermeldungen

# Perl-Pragma um u.a. Variablendeklaration zu erzwingen
# Insbesondere in Hinblick auf Apache-httpd mit mod_perl
# zu empfehlen.
use strict;

# Variablendeklaration
my ($daten, @formulardaten, $formfeld, $name, $wert, %feld);

$daten = $ENV{'QUERY_STRING'};

# Aufspaltung der Formulardaten in die einzelnen Felder
@formulardaten = split(/&/, $daten); 

Das Array @formulardaten enthält nun die einzelnen Formularfelder wieder in getrennter Form.

$formulardaten[0] enthält vorname=Michael
$formulardaten[1] enthält nachname=Schuhmacher

Nun muß jedes einzelne Arrayelement wieder wie bereits weiter oben anhand des "="-Zeichens aufgesplittet werden. Die erhaltenen Werte werden im Hash %feld abgelegt um in weiteren Skriptverlauf jederzeit auf die Übergabewerte zugreifen zu können.

# Jedes Formularfeld einzeln bearbeiten und Ergebnis
# im Hash $feld ablegen.
foreach $formfeld (@formulardaten){
   # Aufspaltung eines Felds in Feldname und -wert
   ($name, $wert) = split(/=/, $formfeld);
   $feld{$name}=$wert;
}  

Der Hash-Inhalt sieht damit wie folgt aus

Key Inhalt
vorname Michael
nachname Schuhmacher

und auf die einzelnen Werte kann z.B. via $feld{"vorname"} oder $feld{"nachname"} zugegriffen werden. Anschließend noch der Rest des Skripts um die Formulardaten als Feedback in einer HTML-Seite zurückzuliefern.

# Content-Type und Headerende des http-Headers erstellen
print "Content-Type: text/html\n\n";

# Eroeffnende HTML-Anweisungen ausgeben
print "<html><head><title>CGI-Test</title></head><body>\n";
print "<h1>&Uuml;bergebene Formulardaten</h1>\n";

# Die Daten jedes Formularfelds in eigener Zeile ausgeben
print "<table border=1><tr><th>Feldname</th><th>Feldwert</th>\n";
foreach $name (keys(%feld)){
   print "<tr valign='top'><td><pre>$name</pre></td>
             <td><pre>$feld{$name} </pre></td></tr>\n";
}
print "</table>\n";

# HTML-Ausgabe abschliessen
print "</body></html>\n";   

Beispiel 3: Erste Form des Feedback-Skripts

Soweit sieht das dann ja schon recht gut aus. Versuchen sie aber mal die Eingabe von Wörtern mit nationalen Sonderzeichen (ä, ß, ...). Das Skript liefert für die Sonderzeichen den Hexadezimalcode des Zeichens mit einem vorangestellten "%"-Zeichen. Ein großes "Ö" wird somit als %D6 dargestellt. Grund dafür ist die ursprügliche Begrenzung des Internets auf den 7-Bit-ASCII-Code. Alle Sonderzeichen benötigen noch das achte Bit und können somit nicht übertragen werden. Deshalb werden sie vor der Übertragung in Hex-Code konvertiert. Ähnliche Probleme gibt es bei Leerzeichen. Da die Formulardaten bei der GET-Methode an die URL angehängt werden, können diese auch nicht unkonvertiert bleiben. Leerzeichen in einer URL sind nämlich nicht zulässig. Ein Leerzeichen wird vor der Übertragung in ein "+"-Zeichen umgewandelt. Wird in unser obiges Formular also der Wert Karl Österle eingetragen, so wird bei der Übertragung daraus Karl+%D6sterle.

Damit unser Skript auch ordentliche Ergebnisse bringt, müssen diese Konvertierungen natürlich alle rückgängig gemacht werden. Die nachfolgenden Zeilen erledigen dies und filtern dabei auch noch unerlaubte Eingaben (in diesem Fall eXtended Server Side Includes) aus den Übergabewerten heraus.

   # MIME-Kodierung der Leerzeichen aufheben
   $wert =~ tr/+/ /;
   # MIME-Kodierung von Sonderzeichen aufheben
   $wert =~ s/%([a-fA-F0-9].)/pack("C", hex($1))/eg;
   # XSSI-Anweisungen entfernen! Sicherheit!!!
   $wert =~ s/<!--(.|\n)*-->//g; 

Tja, und dann gibt es da nochmal ein klitzekleines Problem :-). Im Formular gibt es nämlich eine Auswahlliste die eine Mehrfachauswahl erlaubt. Im QUERY_STRING taucht in so einem Fall der Name des Formularelements mehrfach auf. Im obigen Beispiel handelt es sich um das Formularelement opt_learn_time. Werden hier die beiden Punkte "Am Morgen" und "Am Abend" ausgewählt, dann sieht das in der URL so aus:

opt_learn_time=Am+Morgen&opt_learn_time=Am+Abend

Auch das wird bislang von unserem Skript nicht berücksichtigt. Der jeweils letzte Parameter überschreibt im Hash %feld den Vorgängerwert. Die nachfolgenden Zeilen verhindern dies und sorgen dafür, daß alle ausgewählten Zeilen in unserer Ausgabe erscheinen.

   if(exists($feld{$name})){
     $feld{$name} .= " / " . $wert;
   }
   else{
     $feld{$name}=$wert;
   } 

Nachdem unser Skript um diese beiden Codeblöcke ergänzt wurde, verhält es sich wie im Beispiel 4.

Beispiel 4: Zweite Form des Feedback-Skripts

Nun verbleibt eigentlich nur noch ein einziger Punkt zu Klären. Bislang haben wir die Formulardaten immer mit Hilfe der GET-Methode an das CGI-Skript übermittelt. Dabei werden ja wie erläutert die Daten in einem speziellen Format an die URL angehängt. Die Formuladaten können aber auch mit Hilfe der POST-Methode übertragen werden. In diesem Fall erhält das Skript die Formulardaten über die Standardeingabe vom Server byteweise übermittelt. Der Anwender sieht die Daten dann nicht mehr. Die Anzahl der Bytes erfährt das Skript über die Environment-Variable CONTENT_LENGTH. Damit wird in diesem Fall folgender Code verwendet um an die Daten zu gelangen:

read(STDIN, $daten, $ENV{'CONTENT_LENGTH'} );

Das Skript kann mit Hilfe der Umgebungsvariablen REQUEST_METHOD sogar selbst in Erfahrung bringen mit welcher Methode es aufgerufen wurde. Soll das Skript also wie gewünscht ein ganz universelles Verhalten aufweisen, so wird der nachfolgende Codeblock verwendet.

# Je nach Formular-Method (GET/POST/PUT) werden die Formulardaten
# in die skalare Variable $daten eingelesen.
if($ENV{'REQUEST_METHOD'} eq 'GET'){
   # Formular hat GET-Methode verwendet
   $daten = $ENV{'QUERY_STRING'};
}
else{
   # Formular hat POST-Methode verwendet
   read(STDIN, $daten,  $ENV{'CONTENT_LENGTH'} );
} 

Beispiel 5: Endgültiges Feedback-Skript (mit POST-Methode)

Und hier das ganze Skript in der Übersicht

Zurück zur Übersicht

 Lokal bzw. Remote-Zugriffe seit dem 8. Juni 1999