SELFHTML: Perl / Perl-Sprachelemente / CGI-typische Aufgaben in Perl
Author: Redaktion SELFHTML, selfhtml81@selfhtml.org
Description: Wie typische CGI-Aufgaben mit Perl lösbar sind.
Keywords: SELFHTML, Perl, CGI, Counter, Form-Mailer, Gästebuch, DBI, Datenbank-Front-End
Date: 2005-11-08T17:55:53+01:00

| # CGI-typische Aufgaben in Perl
| CGI-typische Lösungen in Perl Counter (Zugriffszähler) Form-Mailer Gästebuch Datenbank-Front-End

## CGI-typische Lösungen in Perl

Perl wurde, anders als etwa PHP oder ASP, nicht speziell für Web-Anwendungen konzipiert, sondern als eine offene Programmiersprache für scriptbasierte Lösungen aller Art. Es hat jedoch Eigenschaften, die es als Sprache für CGI-Scripts qualifizieren: dazu gehören die mächtigen Funktionen für Zeichenkettenverarbeitung und Datei-Handling, aber auch die vielen Unix-spezifischen Funktionen, von denen auch in Perl geschriebene CGI-Scripts, die auf typischen Web-Server-Umgebungen wie Linux und Apache zum Einsatz kommen, profitieren können. Durch das Modulkonzept von Perl gibt es mittlerweile außerdem Module, die sich speziell für die Verwendung in CGI-Scripts anbieten - allen voran das CGI-Modul, das bei jeder Standard-Installation von Perl zur Verfügung steht. Mit den Möglichkeiten dieses Moduls sollten Sie sich auf jeden Fall näher befassen, wenn Sie CGI-Scripts schreiben.

Wenn Sie noch unsicher sind in Perl und sich nicht in der Lage sehen, eine CGI-Anwendung von Grund auf nach eigenen Vorstellungen zu programmieren, dann können Sie auf zahlreiche Scripts zurückgreifen, die im Web verfügbar sind. Es gibt viele Anbieter, die Freeware-Scripts oder Shareware-Scripts zur Verfügung stellen. Um ein solches Script auf Ihrem Server oder in Ihrer lokalen HTTP-Umgebung zum Laufen zu bekommen und die Anwendung so zu gestalten, dass sie Ihren Vorstellungen entspricht, müssen Sie sich allerdings mit dem Quelltext des Scripts auseinandersetzen und die angewendete Programmierlogik des fremden Script-Entwicklers nachvollziehen können. Wenn Sie Glück haben, ist das Script Ihrer Wahl sauber dokumentiert, und es ist nicht schwer, es für eigene Zwecke anzupassen. Häufig allerdings stoßen Sie in solchen Scriptarchiven auch auf völlig veraltete Angebote! Im Link-Verzeichnis des Online-Angebots von SELFHTML aktuell finden Sie eine Übersicht mit Anbietern fertiger CGI-Scripts in Perl und anderen Sprachen:

SELFHTML-Linkverzeichnis: CGI/Perl

Im folgenden werden einige typische Anwendungsgebiete von CGI-Scripts angeschnitten. Es wird beschrieben, wie der prinzipielle Programmablauf eines entsprechenden Scripts aussieht, und welche Perl-Bestandteile dazu erforderlich sind. Die Beschreibungen enthalten Verweise zu den Stellen, an denen Sie weitere Details finden.

## Counter (Zugriffszähler)

Ein Counter ist entweder grafisch oder textbasiert. Bei einem grafischen Counter wird das CGI-Script aus HTML heraus mittels eines `<img>`-Tags aufgerufen - Beispiel (HTML):

    <img src="/cgi-bin/counter.pl" alt="Counter">

Bei einem textbasierten Counter geschieht der Aufruf meistens mit Hilfe von Server Side Includes - Beispiel (HTML):

    <!--#exec cgi="/cgi-bin/counter.pl"-->

Das aufgerufene Script muss dann zuerst die Datei mit dem aktuellen Zählerstand auslesen, den Zählerstand um 1 erhöhen und den neuen Zählerstand wieder in die Datei zurückschreiben. Dafür gibt es in Perl die Funktionen für Ein-/Ausgabe und Lesen/Schreiben von Daten. Wenn es ein textbasierter Counter ist, kann das Script den Zählerstand z.B. mit einem HTTP-Header mit dem MIME-Typ `text/html` ausgeben, der die Zahl an der Stelle der Server-Side-Include-Anweisung einfügt. Bei einem grafischen Counter muss das Script eine Grafik vom Typ GIF, PNG oder JPEG zurückgeben, da der Browser im `<img>`-Tag etwas Entsprechendes erwartet. PNG-Grafiken lassen sich dynamisch mit Perl beispielsweise mit dem CPAN-Modul namens `GD` erzeugen.

## Form-Mailer

Ein Form-Mailer löst das leidige `mailto`-Problem bei HTML-Formularen. Ein solches Script kann ein beliebiges HTML-Formular verarbeiten und sendet die Daten per E-Mail an einen vorgesehenen Empfänger, in der Regel den Anbieter des Web-Projekts. In HTML wird das Form-Mail-Script einfach als `action`-Attribut im HTML-Formular angegeben - Beispiel:

    <form action="/cgi-bin/formmail.pl" method="post">

Das Script wird aufgerufen, wenn ein Anwender das Formular absendet. Das Script sollte die Formularverarbeitung mit dem CGI-Modul nutzen, um die übergebenen Formulardaten einzulesen. Anschließend muss es aus den Daten des Formulars eine E-Mail zusammenbauen. Dazu sind vor allem der Operator für Zeichenkettenverknüpfung und die Funktionen für Zeichenketten von Bedeutung, ferner aber auch Wissen über den Aufbau einer E-Mail und darüber, wie eine E-Mail zu versenden ist. Die typische Anweisungsfolge dazu lautet:

    my $Sendmail_Prog = "/usr/lib/sendmail";
    open(MAIL,"|$Sendmail_Prog -t") or die "Mailprogramm konnte nicht gestartet werden\n";
    print MAIL "To: $mailto\n";
    print MAIL "Subject: $subject\n\n";
    print MAIL "$mailtext\n";
    close(MAIL) or die "Fehler beim E-Mail-Versand!";

Wichtig ist bei Anwendung der open-Funktion, dass das Programm `sendmail`, das E-Mails versenden kann, auch tatsächlich verfügbar ist, und zwar unter dem angegebenen Pfad. Der Pfad im Beispiel und die Verfügbarkeit von `sendmail` beziehen sich auf typische Unix-Rechner. Fragen Sie Ihren Provider gegebenenfalls danach! Die E-Mail selbst wird dann, wie Sie dem Beispiel entnehmen können, mit `print`-Anweisungen in den bei `open` benannten Ein-/Ausgabekanal des `sendmail`-Programms geschrieben - im Beispiel der Kanal mit dem Namen `MAIL`. Wichtig ist, dass eine E-Mail eine erste Zeile mit `To:` beginnend und eine weitere mit `Subject:` beginnend enthält. Vor der Ausgabe des E-Mail-Textes, also der zuvor für die Ausgabe vorbereiteten Formulardaten, müssen zwei Steuerzeichen `\n\n` stehen, also eine Leerzeile. Die Art und Weise der Datenübergabe kann variieren. Schauen Sie auf jeden Fall in die Anleitungen und FAQs Ihrers Providers und ggf. in die Anleitung des E-Mail-Programms!

Neben dem E-Mail-Versand muss das Script natürlich auch noch eine CGI-notwendige Ausgabe an den Browser senden. Bei einem Form-Mailer ist das sinnvollerweise eine Dank-Seite, die dem Anwender bestätigt, dass sein Formular verarbeitet wurde.

Im Online-Angebot von SELFHTML aktuell finden Sie einen Artikel: Einfacher Form-Mailer, der ein vollständiges Beispiel eines Form-Mailers beschreibt.

## Gästebuch

Es gibt mehrere Wege, ein Gästebuch zu programmieren. Einer besteht darin, die Gästebucheinträge in eine statische HTML-Datei schreiben zu lassen, die angezeigt wird, wenn der Anwender das Gästebuch aufruft. Die Datei könnte ein Formular enthalten, in das der Anwender seinen eigenen Eintrag schreiben kann, sowie die bereits vorhandenen Einträge. Das Script, das das Gästebuch verwaltet, würde dann aufgerufen, wenn der Anwender das Formular mit einem eigenen Eintrag absendet. Dazu ist im einleitenden `<form>`-Tag ein entsprechender Aufruf notiert - Beispiel:

    <form action="/cgi-bin/guestbook.pl" method="post">

Das Script sollte die Formularverarbeitung mit dem CGI-Modul nutzen, um die übergebenen Formulardaten einzulesen. Angenommen, das Formular hat zwei Felder: eines für den Namen des Gästebuchschreibers (`name="Username"`), und eines für den Text (`name="Usertext"`). Dann wäre nach dem Einlesen mit dem CGI-Modul in Perl beispielsweise mit einem Konstrukt wie `$cgi->param($Username)` ein Zugriff auf den Inhalt des Feldes `Username` möglich.

Weiterhin muss das Script die HTML-Datei mit den bisherigen Einträgen einlesen. Mit der open-Funktion beispielsweise lässt sich die Datei dann öffnen und kann danach etwa in einen Array eingelesen werden. Jedes Element des Arrays ist dann eine Zeile der HTML-Datei.

Damit das Script sich im eingelesenen HTML-Code zurechtfindet und weiß, wo es die neuen Daten einfügen muss, sollte die Datei an der betreffenden Stelle in einer eigenen Zeile eine "geheime Marke" enthalten, was mit einem HTML-Kommentar lösbar ist - Beispiel:

    <!-- NEU -->

Es macht aber auch Sinn, wenn die HTML-Datei vor jedem vorhandenen Eintrag in einer eigenen Zeile noch eine interne Notiz für das Script bereithält. Auch das ist mit Kommentaren möglich. Beispiel eines Eintrags in der HTML-Datei:

    <!-- EINTRAG [Winfried Wachtelmann] [27.07.2001]-->
    <table border="1"><tr>
    <th>von:</th><td>Winfried Wachtelmann</td>
    <th>am:</th><td>27.07.2001</td>
    </tr><tr>
    <td colspan="2">Diese wunderbare Seite hat mein Leben ver&auml;ndert usw.</td>
    </tr></table>

Das Script tut sich dann leichter mit eventuellem Suchen in der Datei. Um die Formulardaten, die das Script übergeben bekommen und eingelesen hat, einzufügen, muss es aus den Formulardaten erst einmal den für einen Gästebucheintrag üblichen HTML-Code zusammenbauen und die Formulardaten darin einfügen. Im Beispiel würde das Script also eine HTML-Tabelle zusammen- und in den dafür vorgesehenen Zellen die Werte aus den Formulardaten einbauen. Am sinnvollsten ist es, wenn das Script dazu einen Array deklariert und diesem für jede Zeile des HTML-Codes ein Element zuweist mit dem entsprechenden HTML-Code. Am Ende jedes Eintrags im Array sollte ein `\n` für "neue Zeile" stehen. Dazu benötigen Sie die Funktion push und den Operator für Zeichenkettenverknüpfung - Beispiel:

    my @NeuerEintrag;
    push(@NeuerEintrag, "<!-- EINTRAG [".$cgi->param($Username)."] [".$Datum."]-->\n");
    push(@NeuerEintrag, "<table border=\"1\"><tr>\n");
    push(@NeuerEintrag, "<th>von:</th><td>".$cgi->param($Username)."</td>\n");
    push(@NeuerEintrag, "<th>am:</th><td>".$Datum."</td>\n");
    push(@NeuerEintrag, "</tr><tr>\n");
    push(@NeuerEintrag, "<td colspan=\"2\">".$cgi->param($Usertext)."</td>\n");
    push(@NeuerEintrag, "</tr></table>\n");

Das in `$Datum` gespeicherte Tagesdatum muss das Script natürlich zuvor ermitteln. Dazu gibt es die Funktionen für Datum und Uhrzeit.

Nun muss das Script den neuen Eintrag einfach an der Stelle in der bisherigen Datei einfügen, wo mit `<!-- NEU -->` die Marke zum Einfügen steht. Eine mögliche Variante ist es, einen neuen Array zu bauen, der aus den Zeilen der alten Datei und den Zeilen des neuen Eintrags besteht. Dazu kann das Script in einer foreach-Schleife den Array mit den Zeilen der eingelesenen Datei abarbeiten und mit Hilfe eines regulären Ausdrucks nach `/^<!-- NEU -->$/` suchen und an der betreffenden Stelle den Array mit dem neuen Eintrag einfügen. Der Code könnte etwa so aussehen:

    my @NeueZeilen;
    my $Zeile;
    foreach $Zeile (@Zeilen) {               # @Zeilen = zuvor eingelesene Zeilen der Datei
      if($Zeile =~ /^<!-- NEU -->/) {        # Aha, Eintrag hier einfügen
        foreach(@NeuerEintrag) {             # @NeuerEintrag = zuvor zusammengebaute HTML-Zeilen
          push(@NeueZeilen,$_);              # Zeile für Zeile hinzufügen
        }
        push(@NeueZeilen,"<!-- NEU -->\n");  # fürs nächste mal ...
      }
      else {                                 # jede alte Zeile, die nicht /^<!-- NEU -->/ enthält
        push(@NeueZeilen,$Zeile);            # einfach in den neuen Array kopieren
      }
    }

Der neue Array `@NeueZeilen` enthält dann also die Zeilen der neuen Fassung der Gästebuch-HTML-Datei. Dieser Array muss nun einfach noch in die gleiche Datei zurückgeschrieben werden, die zuvor eingelesen wurde. Das geht mit schreibendem Öffnen mit open und Beschreiben mit print ins Datei-Handle der geöffneten Datei.

Am Ende darf das Script natürlich nicht vergessen, CGI-notwendige Ausgaben an den Browser zu senden. Am einfachsten ist es wohl, einfach einen HTTP-Kopf und `@NeueZeilen` auszugeben. Das sind zwei `print`-Befehle, und schon sieht der Anwender, der das Formular abgesendet hat, das Gästebuch mit seinem eigenen neuen Eintrag.

Natürlich gibt es noch viele weitere wichtige Dinge, die das Gästebuch erledigen sollte. Beispielsweise sollten Dateien, die ein CGI-Script öffnet, grundsätzlich gegen versehentlichen Mehrfachzugriff geschützt werden - denn es könnten ja zwei Anwender gleichzeitig ein Formular mit einem neuen Eintrag absenden. Dann würden sich zwei Prozesse mit dem gleichen Script in die Quere kommen. Das so genannte File-Locking für den Zugriffsschutz geht mit der Funktion flock.
Weiterhin sollte das Script reagieren, wenn die HTML-Datei mit den Gästebucheinträgen zu groß wird. In diesem Fall könnte das Script einen Teil des Arrays mit älteren Einträgen in eine zweite Datei schreiben und diese in der Datei mit den aktuellen Einträgen verlinken, d.h. also in die erste Datei einen HTML-Link auf die zweite mit einbauen.
Und dann sollte das Script auch prüfen, ob eventuell angegebene mail-Adressen gültig (valide) sind und Benutzernamen nicht aus unsinnigen Zeichenfolgen wie `@~Der Kobold~@` bestehen. Solche Aufgaben lassen sich am besten mit regulären Ausdrücken innerhalb bedingter Anweisungen lösen.

## Datenbank-Front-End

Sie brauchen nicht für jede Anwendung, bei der ein Web-Seitenbesucher dynamisch Daten abfragen kann, eine Datenbank. Datenbestände von bis zu ein paar Megabyte Größe, die nicht zu intensiv genutzt werden, lassen sich durchaus auch in Textdateien (z.B. kommasepariert oder in einer XML-Struktur) halten, die dann mit CGI-Scripts in Perl verwaltet werden können. Ein öffentliches "Front-End" (also eine Web-Anwendung) zum Abfragen des Datenbestandes sowie ein nur intern zugängliches Front-End zum Verwalten des Datenbestandes (auch eine Web-Anwendung, die das Hinzufügen, Ändern und Löschen von Datensätzen mittels Formular möglich macht) brauchen Sie sowieso in jedem Fall. Eine richtige Datenbank sollten Sie dann wählen, wenn der Datenbestand sehr intensiv gepflegt wird (sehr viele Änderungen im Datenbestand - ein Fall für "Table-Locking"), oder wenn die Datenstruktur sehr stark relational ist und mit vielen Lookups arbeitet, oder wenn der Datenbestand wirklich sehr groß ist und die Pi-mal-Daumen-Größe von ein paar Megabyte übersteigt. Auf dem Server muss dann natürlich eine entsprechende Datenbank-Software installiert sein - z.B. das beliebte Freeware-Produkt MySQL. Letzteres ist mittlerweile so dominant im Web, dass es für relationale Datenbanken bis zu mittlerer Größe praktisch konkurrenzlos ist. Fragen Sie gegebenenfalls Ihren Provider, ob Ihnen MySQL dort, wo Sie Speicherplatz im WWW gemietet haben, zur Verfügung steht, bzw. berücksichtigen Sie dies bei der Wahl des Providers, wenn Sie eine Datenbankanbindung brauchen. MySQL wird zwar vorwiegend gemeinsam mit PHP genutzt, aber über Perl ist es genauso möglich, Datenbank-Front-Ends zu schreiben, die mit MySQL kommunizieren können.

Bevor Sie ein Datenbank-Front-End in Perl schreiben, müssen Sie sich zuerst intensiv mit MySQL, Datenbankschnittstellen und der Abfragesprache SQL auseinandersetzen. Dazu gibt es Dokumentationen und Bücher. Im Link-Verzeichnis des Online-Angebots von SELFHTML aktuell finden Sie entsprechende Links:

SELFHTML-Linkverzeichnis: Datenbankanbindung

Dieser kurze Abschnitt hier kann nur einige allgemeine Details aus Sicht von Perl behandeln. In einem Perl-CGI-Script, das mit einer MySQL-Datenbank kommunizieren möchte, müssen zu Beginn Zeilen wie die folgenden stehen:

    use CGI;
    use DBI;

    my $DB_NAME = "Produkte";
    my $DB_DSN = "DBI:mysql:database=$DB_NAME";
    my $DB_USER = "";
    my $DB_PASSWD = "";

    my $dbh = DBI->connect($DB_DSN, $DB_USER, $DB_PASSWD) or die "Fehler bei Datenbankverbindung: $!";

`DBI` ist ein CPAN-Modul, das Sie in der Regel benötigen, um mit Perl auf Datenbanken zugreifen zu können. Es stellt eine datenbankunabhängige Schnittstelle zu verschiedenen datenbankabhängigen Treibern dar. Ihr Perl-Script kommuniziert also mit der Datenbank in der Form, dass es die Funktionen bzw. Methoden des DBI-Moduls nutzt.

Im Beispiel-Code deklariert das Script einige Skalare, die für das Modul und die Zusammenarbeit mit der Datenbank erforderlich sind. `$DB_NAME` ist der Name der Datenbank. `DB_DSN` ist eine Information für das DBI-Modul, welcher Treiber zum Verbindungsaufbau mit der Datenbank (im Beispiel: `mysql`) benutzt und welche Datenbank geöffnet werden soll (im Beispiel die Datenbank, die in `$DB_NAME` gespeichert ist). Da der SQL-Server von MySQL, der die Datenbankzugriffe und die Ausgabe an extern aufrufende Prozesse überwacht, Daten aus Datenbanken nur gegen Anmeldung mit Benutzerkennwort und Passwort herausrückt, sind diese ebenfalls bei der Datenbankverbindung anzugeben. Im Beispiel erhalten die entsprechenden Skalare `$DB_USER` und `$DB_PASSWD` einen leeren Inhalt zugewiesen. Bei autorisierter Nutzung einer Datenbank müssen Sie dort jedoch die Kenndaten eingeben.

Mit `DBI->connect(...)` wird die Verbindung zur Datenbank aufgebaut. Der Rückgabewert wird in einer Datenbank-Handle-Variablen gespeichert, im Beispiel `$dbh`. Über dieses Handle können Sie im weiteren Verlauf des Scripts die Funktionen bzw. Methoden des DBI-Moduls ansprechen, wie in diesem Beispiel:

    my @DB_Felder = $dbh->list_fields('produktdaten');
    $dbh->disconnect;

`list_fields` ermittelt die Feldnamen einer bestimmten Tabelle der Datenbank, im Beispiel die der Tabelle `produktdaten`. Mit `$dbh->disconnect` wird eine Datenbankverbindung am Ende wieder geschlossen. Die eigentliche Kommunikation mit der Datenbank erfolgt jedoch über entsprechende SQL-Befehle. Das folgende Beispiel zeigt, wie Sie in einem Perl-Script einen solchen Befehl notieren können und eine anschließende Datenbankabfrage starten:

    my $SQL_Statement = "SELECT nummer, name, beschreibung FROM produktdaten ".
                        "WHERE produktdaten.nummer >= ".$cgi->param($von_Nummer).
                        " AND produktdaten.nummer <= ".$cgi->param($bis_Nummer).
                        " ORDER BY ".$cgi->param($sortier_feld);

    my $Abfrage = $dbh->prepare($SQL_Statement);
    $Abfrage->execute();

    my @Datensatz;
    while(@Datensatz = $Abfrage->fetchrow_array()) {
      DatensatzVerarbeiten(@Datensatz);
    }

Für die Datenbankabfrage müssen Sie einen SQL-Befehl zusammenstellen und in einem Skalar speichern. Dazu müssen Sie die Syntax von SQL kennen. Normalerweise wird es so sein, dass Ihr CGI-Script wie im Beispiel die Parameter der Abfrage aus einem HTML-Formular bezieht, das der Anwender, der das Script mit dem Absenden des Formulars aufgerufen hat, ausgefüllt hat. Dazu sollte das Script die Formularverarbeitung mit dem CGI-Modul nutzen, um die übergebenen Formulardaten einzulesen. Eine dynamische Konstruktion eines SQL-Befehls aus Formulardaten ist dann wie gezeigt mit dem Operator für Zeichenkettenverknüpfung möglich. Anschließend wird mit `$Abfrage = $dbh->prepare($SQL_Statement)` dem DBI-Modul der Abfragewunsch mitgeteilt. Und danach kann dann mit `$Abfrage->execute()` der Abfragebefehl gestartet werden. Die Datenbank gibt nun der Reihe nach alle gefundenen Datensätze zurück. Jeder Datensatz besteht aus mehreren Feldern. Jeder Datensatz wird einzeln als Array zurückgegeben. In einer while-Schleife, die so lange läuft, wie der Aufruf von `$Abfrage->fetchrow_array()` einen neuen Datensatz liefert, wird im Beispiel eine Subroutine namens `DatensatzVerarbeiten` aufgerufen, die als Parameter den aktuellen Datensatz-Array übergeben bekommt. Diese Subroutine könnte beispielsweise den HTML-Code für die Ausgabe eines Datensatzes vorbereiten. Denn abschließend muss das Script natürlich CGI-notwendige Ausgaben zusammenstellen.

Bei der Verwaltung der Datenbank über ein HTML-Formular/Perl-Script-Front-End ist der Vorgang ganz ähnlich, nur mit zusätzlichen SQL-Befehlen zum Hinzufügen oder Zurückschreiben geänderter Datensätze in die Datenbank. Wichtig ist, dass ein solches Front-End für die interne Datenbankpflege gegen öffentliche Zugriffe geschützt ist. Dies kann zweistufig geschehen: zum einen sollte ein solches Front-End-Formular nur in einem htaccess-geschützten Verzeichnis liegen, und zum anderen sollte das Formular auch Eingabefelder für Benutzernamen und Passwort zum schreibenden Zugriff auf die Datenbank enthalten.

| Funktionen für Zeichenketten
| Objektorientiertes Programmieren

© 2007 Impressum