freeX: CGI-Programmierung mit Perl, Teil 3

Die ausschließlichen Vertriebsrechte an diesem Artikel liegen beim Computer- & Literaturverlag (C&L). Der Artikel darf nicht kopiert oder gar erneut in einer Zeitschrift oder einem Buch veröffentlicht werden ohne vorherige Erlaubnis von C&L. Der Verlag gestattet freundlicherweise die Veröffentlichung auf diesen Seiten. Wer öfter auf diesen Hinweis trifft, sollte sich überlegen, die Zeitschrift freeX zu abonnieren.

Perl hier, Perl da, Perl ist immer da!

 
Perl gehört zu den wichtigen Sprachen in der Unix-Welt. Auf einem Linux-System werden daher viele administrative Aufgaben mit Perl-Programmen erledigt. Die Sprache eignet sich jedoch für mehr. CGI-Programmierung ist ein Bereich, in dem sie häufig eingesetzt wird.

 

1. Module
2. Installation von CGI.pm
3. Einbinden von Modulen
4. Common Gateway Interface
5. CGI mit Perl
6. CGI-Variablen
7. Finger-Beispiel
8. Unterschiedliche Resultate
9. Literatur

Perl ist eine sehr mächtige Programmiersprache, die dem Entwickler zudem viele Freiheiten läßt. Ein Problem läßt sich meistens nicht nur auf eine einzige Art und Weise lösen, sondern stattdessein wird je nach Wissensstand und Erfahrung der eine oder andere Weg beschritten.

Module

Die Arbeit mit Perl wird erheblich vereinfacht, wenn zusätzliche Module verwendet werden, die einen bestimmten Aufgabenbereich abdecken und definierte Schnittstellen bieten. Sie gehören nicht zur Standard-Distribution von Perl. Weltweit werden derartige Module im CPAN gesammelt. Die Abkürzung CPAN steht dabei für "Comprehensive Perl Archive Network" und enthält über 800 MB an Quellcode und Dokumentation zu allen möglichen Themenbereichen. CPAN wird offiziell auf über hundert Servern im Netz gespiegelt und zur Verfügung gestellt.

CPAN ist das Archiv von zumeist Freier Software als Ergänzung zur normalen Perl-Distribution. Die dort zur Verfügung gestellten Module decken alle Themen ab, von Schnittstellen zu Netzwerkprotokollen, über Datenbanken und Zeitumrechnung bis hin zu Schnittstellen zu Grafikbibliotheken. Mit Net::IRC wird z.B. ein IRC-Roboter geschriebenn, mit IO::Socket realisiert man den Zugriff auf beliebige TCP/IP-Protokolle, Date::Calc berechnet die Differenz zweier Daten, mit DBD wird auf Datenbanken zugegriffen und mit Gtk wird schließlich die Gtk-Bibliothek angesprochen, so daß grafische Elemente dargestellt werden.

Das Modul, dem sich dieser Artikel vorrangig widmet, ist das CGI-Modul oder einfach »CGI.pm«. Es ist aus »cgi-lib.pl« hervorgegangen und stellt heutzutage die übliche Schnittstelle für Perl-Programme und CGI dar. CGI steht für »Common Gateway Interface« und ist die standardisierte Schnittstelle zwischen dem Webserver und einem Programm, das mit dem Benutzer interagiert. Das Programm gibt dabei Daten zurück, die der Server an den Client, also den Browser, weitergibt. Normalerweise ist das HTML-Code, jedoch sind genauso auch andere Dateiformate möglich.

[CGI]

Abbildung 1: Zusammenspiel zwischen Client (Browser), Server und CGI-Programm

Daneben bietet das CGI-Modul einfache Möglichkeiten, HTML-Code zu generierern und zurückzuliefern. Als Entwickler von CGI-Programmen muß man dadurch nicht mehr direkt HTML-Code schreiben, sondern kann sich auf CGI.pm verlassen. Interessierte Leser seien an dieser Stelle auf die Dokumentation »perldoc CGI« verwiesen.

Installation von CGI.pm

Wer Linux benutzt, darf diesen Abschnitt wahrscheinlich getrost überspringen. Bei den meisten Linux-Distributionen sowie neueren Perl-Installationen befindet sich das CGI-Modul bereits im normalen Lieferumfang und wird bei einer normalen Installation automatisch installiert. Die Eingabe des Befehls »perldoc CGI« zeigt, ob das Modul installiert ist oder nicht.

Wenn es nicht installiert ist oder die neueste Version verwendet werden soll, wird das gewünschte Archiv aus CPAN geholt, z.B. von dieser Adresse.

Das Archiv wird ausgepackt mit dem Befehl

   tar xvfz CGI.pm-2.66.tar.gz

Anschließend muß das Paket konfiguriert und compiliert werden. CPAN-Module werden mit folgendem Befehl konfiguriert:

   perl Makefile.PL

Anschließend wird das Modul mit »make« compiliert. Bis hierhin werden die Befehle als normaler Benutzer ausgeführt. Die eigentliche Installation muß jedoch mit Superuser-Berechtigung durchgeführt werden, da in Verzeichnissen geschrieben wird, auf die ein normaler Benutzer keinen Schreibzugriff hat.

Als »root« wird das Modul mit folgendem Befehl installiert:

   make install

Einbinden von Modulen

Module werden mit dem Perl-Befehl »use« in Perl-Programme eingebunden bzw. auf der Kommandozeile mit »-M«. Wie sie verwendet werden und welche Funktionen sie zur Verfügung stellen, geht normalerweise aus der Dokumentation hervor: z.B. »perldoc Modulname«. Teilweise müssen die Funktionen der Module im Programm explizit bekannt gemacht werden. Das geschieht mit der Funktion »qw()« bzw. »qw(funktionsname)«. Fortan ist die Funktion direkt verfügbar und kann mit ihrem Namen angesprochen werden. Ohne Globalisierung der Namen, werden Funktionen mit »Modulname::Funktion« aufgerufen.

Common Gateway Interface

CGI stellt eine dokumentierte Schnittstelle zwischen dem Webserver und einem Programm dar, das über eine URL aufgerufen wird. Solche Programme werden üblicherweise CGI-Programme genannt und liegen normalerweise in einem speziellen »cgi-bin«-Verzeichnis. Ein CGI-Programm ist oftmals mit einer Eingabemaske bzw. einem HTML-Formular verbunden. Dort eingegebene Werte werden über die CGI-Schnittstelle an das CGI-Programm weitergegeben, und beeinflussen es.

Obwohl CGI ursprünglich für CGI-Programme entwickelt wurde, wird diese Technik auch bei "embedded" Programmcode verwendet. Beim Einsatz von PHP und ePerl gibt es keine expliziten Programme mehr, sondern der Programmcode wird direkt in die HTML-Dateien eingebaut. Bevor der Webserver die Seite jedoch an den Browser zurückliefert, wird sie von einem Programm interpretiert. Dieses filtert die Programmfragmente heraus und interpretiert sie entsprechend. Die so berechnete Seite wird an den Webserver übermittelt, der sie an den Browser weitergibt. Für den normalen Benutzer ist die Verwendung von "embedded" Programmen transparent realisiert, d.h. er sieht nicht, ob die Seite direkt aus dem Dateisystem stammt oder ob sie von einem Programm interpretiert wurde.

Daten vom Browser werden auf zwei Arten zum Webserver übertragen: per »GET« und per »POST«. Bei der ersten Methode werden die Daten in die URL-Zeilen angehängt. Die Das Fragment »/foo.pl?nr=1« besagt, daß dem Programm »foo.pl« die Variable »nr« mit dem Wert »1« übergeben werden soll. Beim »POST«-Request werden die Daten nicht in der URL kodiert sondern separat übermittelt. Für längere Texte oder Daten, die nicht aus der URL ersichtlich sein sollen, ist die zweite Methode sinnvoller.

Zusätzlich sind in der Spezifikation mehrere Umgebungsvariablen beschrieben, die vom Webserver gesetzt werden und vom CGI-Programm verwendet werden dürfen. Abhängig vom verwendeten Server stehen weitere Variablen zur Verfügung. Über ihre Verwendung sollte man jedoch genau nachdenken. Wenn man sich darauf verläßt, daß sie existieren und korrekte Werte enthalten, ist das CGI-Programm abhängig vom verwendeten Webserver und kann nicht mehr ohne weiteres mit einem anderen Server zusammenarbeiten.

Unabhängig davon, in welcher Sprache CGI-Programme geschrieben werden, muß dem Webserver mitgeteilt werden, um welche Art von Daten es sich bei den zurückgegebenen handelt. Der Typ wird mit MIME-Typen kodiert, wobei der häufigste »text/html« ist, z.B. wenn HTML-Text zurückgegeben wird. Die erste Zeile muß bei HTML-Text die folgende sein:

   Content-type: text/html

Es dürfen weitere Zeilen folgen, mit denen z.B. eingestellt wird, wie lang die Antwort ist oder wie lange die Antwort im Cache vorgehalten werden dürfen. Wichtig ist, daß »type« mit einem kleinen »t« geschrieben wird, da die Zeile sonst unter Umständen nicht verstanden wird und der Server stattdessen die Daten aufgrund von ungültigen Headerwerten ablehnt. Wird eine Länge übermittelt, haben Browser die Möglichkeit, anzuzeigen, wieviel Prozent bereits geladen wurde. Nach diesen Angaben muß eine Leerzeile folgen, bis der eigentliche Inhalt der Seite zurückgegeben wird.

Umgebungsvariablen für CGI-Programme
AUTH_TYPEAuthentifizierungs-Methode, falls Authentifizierung nötig.
CONTENT_LENGTHLänge des Datenstroms, der an das CGI-Programm als »stdin« übergeben wurde. Wird beim »POST«-Request verwendet.
CONTENT_TYPEMIME-Type der übermittelten Daten, optional.
DOCUMENT_ROOTLokales Verzeichnis, das als »/« für den Webserver dient.
GATEWAY_INTERFACEGibt die Version der Schnittstelle an, heutzutage meistens »CGI/1.1«.
HTTP_ACCEPTDurch Kommas getrennte Liste von MIME-Typen, die der Browser akzeptiert.
HTTP_FROME-Mail-Adresse des Benutzers, wird von den meisten Browsern nicht unterstützt.
HTTP_REFERERDie URL, von der auf diese Adresse verwiesen wurde.
HTTP_USER_AGENTName, Version und Bibliotheken des Browsers, der die Anfrage sendet.
PATH_INFOZusätzliche Pfad-Komponente (s.u.)
PATH_TRANSLATEDÜbersetzte Pfad-Komponente der URL, relativ zum lokalen Dateisystem, wird nicht von allen Servern unterstützt.
QUERY_STRINGZusätzliche Argumente an das Programm (siehe »?foo=bar« weiter unten). Die Variable ist leer, falls keine Argumente angegeben wurden.
REMOTE_ADDRIP-Nummer des anfragenden Rechners.
REMOTE_HOSTVollständiger Name des anfragenden Rechners.
REMOTE_USERBenutzername, ist nur dann gesetzt, falls sich der Benutzer authentifizieren mußte (».htaccess«)
REQUEST_METHOD»GET« oder »POST«
SCRIPT_NAMEDer Pfad-Anteil an der URL, der auf das Programm zeigt.
SCRIPT_FILENAMEDer reelle Pfad des Skripts (Apache).
SERVER_NAMEDer Name des Servers gemäß Konfiguration.
SERVER_PROTOCOLName und Version des Protokolls, mit dem die Anfrage übermittelt wurde: »HTTP/1.0« oder »HTTP/1.1«.
SERVER_PORTDer TCP/IP-Port auf dem der Webserver läuft.
SERVER_SOFTWAREName und Version der Server-Software

CGI mit Perl

Für CGI-Programme ist normalerweise ein eigenes Verzeichnis vorgesehen. Alle dort enthaltenen Programme werden vom Webserver als CGI-Programme angesehen. Das Verzeichnis ist meistens »/usr/lib/cgi-bin« oder »/usr/local/httpd/cgi-bin«. Wenn dort ein CGI-Programm abgelegt wird, muß darauf geachtet werden, daß es vom Web-Benutzer auch gelesen und ausgeführt werden darf. Der Befehl »chmod a+rx datei« setzt die Berechtigung derart, daß es von jedem Benutzer aufgerufen werden darf.

Wie bereits erwähnt, arbeitet das CGI-Modul mit Objekten. Für jedes CGI-Programm wird daher zuerst ein neues Objekt angelegt. Das geschieht mit dem »new«-Befehl. Auf die Funktionen und Variablen des CGI-Moduls wird fortan über den Namen des Objekts zugegriffen. So wird der benötigte MIME-Header z.B. mit »$objektname->header()« erzeugt. Weiter unten wird beschrieben, wie so auf übergebene Variablen zugegriffen wird.

Das kleinste CGI-Programm in Perl sieht wie folgt aus. Es soll als »test-freeX« in das oben erwähnte CGI-Verzeichnis gelegt werden.


   use CGI;

   $cgi = new CGI;
   print $cgi->header();
Einfaches CGI-Programm in Perl

Wird nun die Adresse »http://localhost/cgi-bin/test-freeX« im Browser (Lynx, Netscape etc.) eingegeben, sieht man das Resultat: eine leere Seite. Das gleiche Resultat erzielt man, wenn man eine leere Datei »index.html« (z.B. mit »touch index.html«) anlegt und sie über den Webserver angefordert wird.

Eine »print«-Anweisung ist im Programm vorhanden, sie gibt jedoch nur den Header für den Webserver aus, keine zusätzlichen Informationen. Wird in die nächste Zeile

   print "Hallo freeX!";

geschrieben, wird der Text auch vom Browser angezeigt. Die Ausgabe, die ein CGI-Programm erzeugen soll, ist die normale Programmausgabe. Somit lassen sich informative Seiten sehr einfach gestalten.

Daß es sich bei diesem Programm um ein CGI-Programm und nicht um ein normales Programm handelt, sieht man an einer Besonderheit dieser CGI-Bibliothek. Wenn das Progamm direkt aufgerufen wird, also nicht über den Webserver, besteht die Möglichkeit, Variablen direkt einzugeben. Mit dieser Methode lassen sich CGI-Programme ohne Webserver leicht testen. So wird relativ einfach sichergestellt, daß das Programm korrekt arbeitet.

Die CGI-Bibliothek erwartet die Eingabe der Variablen als »name=wert«-Paare. Pro Zeile wird ein solches Paar angegeben. Wurden alle benötigten Paare eingegeben, muß der Datenstrom abgeschlossen werden. Das geschieht durch Eingabe von »Strg-D« (»Ctrl-D«). Erst wenn die Eingabe abgeschlossen ist, wird das eigentliche Programm abgearbeitet. Der Datenstrom wird automatich beendet, wenn die Zeilen nicht manuell eingegeben werden, sondern aus einer Pipe stammen, wie folgendes Beispiel verdeutlicht:

   echo "foo=bar"|./test-freeX

CGI-Variablen

Die Angabe von Variablen, z.B. in der URL »http://localhost/cgi-bin/test-freeX?foo=bar« ändert an der Ausgabe dieses einfachen Programms nichts.

Mit dem CGI-Modul ist jedoch eine sehr einfache Schnittstelle gegeben, um auf die an das Programm übergebenen Variablen zuzugreifen. Der Programmentwickler muß sich dabei keine Gedanken mehr über die verwendete Kodierung machen. Das wird vom CGI-Modul automatisch übernommen. Das Modul unterstützt sowohl »GET«- als auch »POST«-Aufrufe. Beim Erzeugen des neuen Objekts werden die übergebenen Wertepaare dekodiert und intern gespeichert.

Die Funktion »param()« gibt den Wert einer Variablen zurück. Das obige Beispiel wird zu folgendem erweitert.


   use CGI;

   $cgi = new CGI;
   print $cgi->header();

   printf "foo ist %s.", $cgi->param('foo');
Textausgabe im CGI-Programm

Wird jetzt die URL oben erneut im Browser eingegeben, dann sieht man, daß das CGI-Programm tatsächlich die Variable »foo« erkennt und ihren Wert ausgibt. Meistens stammen die übergebenen Variablen aus Formularen, daher sei hier zusätzlich das äquivalente Formular gegeben, das später weiter ausgebaut werden kann:

   <form action="http://localhost/cgi-bin/test-freeX" method=post>
   <input type=text name=foo value=bar>
   <input type=submit>
   </form>

Finger-Beispiel

Das nächste Beispiel soll jetzt derart ausgebaut werden, daß die Informationen angezeigt werden, die auch das Programm »finger« anzeigt. Dazu muß einerseits der Benutzername übermittelt werden und andererseits muß das »finger«-Programm aufgerufen werden. Für den Benutzernamen wird die Variable »login« erwartet, die wie oben beschrieben übergeben wird.

Die Ausgabe des »finger«-Programms über einen normalen Dateideskriptor abgefangen. Initialisiert wird er mit dem »open«-Befehl. Wichtig ist dabei das Pipe-Zeichen am Ende des "Dateinamens". Anschließend wird in einer Schleife solange gelesen, bis der Datenstrom abbricht bzw. das Programm sich beendet hat. Das folgende Fragment erledigt dieses:

   if (open (IN, "finger login|")) {
      while (<IN>) {
         print;
      }
      close (IN);
   }

Bevor das Beispiel zusammengesetzt wird, sind noch einige Dinge zu beachten: Das Programm soll nur dann Informationen über den Benutzer ausgeben, wenn ein Login übermittelt wurde. Den Fall, in dem kein Login übertragen wurde, werden wir später behandeln. Die einfachste Möglichkeit, herauszufinden, ob ein Parameter übergeben wurde, ist die Überprüfung der Länge des Rückgabewertes von »param()«.

Anschließend wird der Variablen »$login« der übermittelte Parameter zugewiesen. Geschieht dieses nicht, würden die Adresse von »$cgi« und die Zeichenkette »->param« zusammengesetzt werden. Das Ergebnis unterscheidet sich vom übergebenen Benutzernamen. Der interessierte Leser mag dieses mit der Anweisung »print "$cgi->param('login')";« nachvollziehen. Abschließend wird die Ausgabe in HTML-Code eingefaßt, damit die Seite vernünftig gerendet wird.


   use CGI;

   $cgi = new CGI;
   print $cgi->header();

   if (length ($cgi->param('login')) > 0) {
      print "<html><body><pre>\n";
      $login = $cgi->param('login');
      if (open (IN, "finger $login|")) {
	 while (<IN>) {
	    print;
	 }
	 close (IN);
      }
      print "</pre></body></html>\n";
       
   }
Finger-Beispiel als Perl-CGI

Unterschiedliche Resultate

Wenn kein Login an das Programm übergeben wird, soll die Liste der Benutzer auf dem System zurückgegeben werden. Die Liste soll in ein Formular eingebunden werden, so daß der gewünschte Benutzer ausgewählt werden kann. Dabei soll der richtige Name angezeigt werden und nicht das Login. Das Login muß jedoch an das CGI-Programm übergeben werden.

Mit Perl stellt das kein Problem dar. Wir bedienen uns dazu am Beispiel aus dem letzten Artikel. Die Liste der Benutzer wird aus der Paßwortdatei extrahiert. Das Namensfeld muß alledings erneut mit »split« aufgeteilt werden, da es eine Komma-separierte Liste mit Namen und Telefonnummer sein kann. Damit wird das Beispiel erweitert zu:


   use CGI;

   $cgi = new CGI;
   print $cgi->header();

   if (length ($cgi->param('login'))) {
      print "<html><body><pre>\n";
      $login = $cgi->param('login');
      if (open (IN, "finger $login|")) {
	 while (<IN>) {
	    print;
	 }
	 close (IN);
      }
      print "</pre></body></html>\n";
       
   } else {
        if (open (P, "/etc/passwd")) {
            print "<html><body><pre>\n";
            print "<form method=post action=/cgi-bin/test-freeX>\n<select name=login>";
            while (<P>) {
                @array = split (/:/);
                next if ($array[2] < 100);
                @name = split (/,/, $array[4]);
		printf "<option value=%s>%s\n", $array[0], $name[0] if (length ($name[0]));
            }
            close (P);
            print "<input type=submit></form>\n";
            print "</pre></body></html>\n";
        }
   }
Komfortable Variante des Finger-Beispiels

Bei diesem Beispiel ist zu beachten, daß einige Browser nicht zwischen »GET«- und »POST«-Requests unterscheiden. Wenn bereits mit POST auf eine URL zugegriffen wurde, kann sie nicht anschließend mit »GET« geladen werden. So äußert es sich bei Lynx.

Daher werden für verschiedene Aufgaben gerne unterschiedliche Programme verwendet oder Techniken, mit denen URLs unterschiedlich aussehen. HTML bietet nicht nur die Möglichkeit, hinter den eigentlichen Pfadnamen Variablen anzugeben, sondern auch den Pfad virtuell zu erweitern. So wird z.B. PHP-Unterstützung nachgebildet, wenn der Webserver selbst die Dateien nicht an PHP weiterreichen kann.

Derart angegebene Erweiterungen werden vom Webserver in der Umgebungsvariablen »PATH_INFO« gespeichert. Es ist die Ergänzung zum normalen Pfad. Wenn das CGI-Programm z.B. »/cgi-bin/test-freeX« lautet, die URL jedoch auf »/cgi-bin/test-freeX/list« endet, wird »/list« in der Variablen »PATH_INFO« gespeichert.

Das hat den Nebeneffekt, daß die URLs für den Browser unterschiedlich aussehen, die oben erwähnten Überschneidungen daher nicht mehr auftreten. Das Programm soll jetzt derart erweitert werden, daß die Liste der Benutzer nur dann ausgegeben wird, wenn »/list« an die URL angehängt wird. Dazu wird der Abschnitt, in dem das Formular ausgegeben wird, in folgende Verzweigung eingefaßt:

   if ($ENV{'PATH_INFO'} eq "/list") {
   }

Die normale Anzeige soll mit dem Zusatz »/show« erzeugt werden und wenn kein Zusatz angegeben ist, soll auf die »/list«-Seite verwiesen werden. Dazu bietet HTTP ein sogenanntes "Redirect" an. Es wird dabei keine HTML-Seite zurückgegeben, sondern nur ein Verweis. Der Browser wird diesem Verweis folgen und die neue Seite anfordern.

Auf Protokoll-Ebene sieht ein solches "Redirect" sehr einfach aus. Anstelle des Headers "Content-type" werden "Status" und die neue URL zurückgegeben. Der Statuswert entspricht dem "HTTP Return Codes" und ist 301 oder 302. Das CGI-Modul bietet von Hause aus eine einfache Möglichkeit, derartige Verweise zu generieren. Die Funktion »redirect()« gibt die entsprechenden Header zurück. Lediglich die Ziel-URL muß angegeben werden. Das bisherige Programm wird daher um folgende Zeile ergänzt:

   print $cgi->redirect('/cgi-bin/test-freeX/list');

Damit ist das Beispiel vollständig. Im nebenstehenden Kasten ist es noch einmal komplett abgebildet.


   use CGI;

   $cgi = new CGI;

   if ($ENV{'PATH_INFO'} eq "/list") {
       print $cgi->header();
       if (open (P, "/etc/passwd")) {
           print "<html><body><pre>\n";
	   print "<form method=post action=/cgi-bin/test-freeX/show>\n<select name=login>";
	   while (<P>) {
               @array = split (/:/);
	       next if ($array[2] < 100);
	       @name = split (/,/, $array[4]);
	       printf "<option value=%s>%s\n", $array[0], $name[0] if (length ($name[0]));
	   }
	   close (P);
	   print "<input type=submit></form>\n";
	   print "</pre></body></html>\n";
       }
   } elsif (($ENV{'PATH_INFO'} eq "/show") && (length ($cgi->param('login')) > 0)) {
       print $cgi->header();
       print "<html><body><pre>\n";
       $login = $cgi->param('login');
       if (open (IN, "finger $login|")) {
	  while (<IN>) {
	     print;
	  }
	  close (IN);
       }
       print "</pre></body></html>\n";

   } else {
       print $cgi->redirect('/cgi-bin/test-freeX/list');
   }
Vollständiges CGI-Beispiel-Programm

Literatur

Martin Schulze
Quelle: freeX 4/00