|
Das Common Gateway Interface (CGI)
|
Zu jeder Community gehört sicherlich ein WWW-Server. Er kommt auf jeden Fall bei der Veröffentlichung von Chat-Terminen oder als Download-Server für interaktive Java-Applets zum Einsatz. Darüber hinaus besteht natürlich die Möglichkeit den WWW-Server selbst als interaktives Instrument zu benutzen. Neben dem üblichen Abrufen von vorgefertigten HTML-Seiten kann der Server nämlich, mit Hilfe des "Common Gateway Interface (CGI)", auch eine interaktive Kommunikation mit dem Anwender durchführen.
Um das CGI besser von normalen HTML-Seiten unterscheiden zu können, werden die beiden Varianten nachfolgend getrennt voneinander betrachtet.
Häufig handelt es sich bei den Inhalten des WWW um nicht dynamische Informationen. Diese Informationen wurden vom jeweiligen Autor als statische Datei im HTML-Format erzeugt und im Dateisystem des Webservers abgelegt. Der Webserver selbst ist ein Rechner auf dem die Webserver-Software, auch http-Dämon (httpd) genannt, gestartet wurde. Diese Serversoftware wartet auf Anfragen von Clients aus dem Internet und reagiert entsprechend ihrer Konfiguration.
Einzelschritte beim Abruf einer statischen HTML-Seite:
Über den Browser des
Clients wird per http eine Anfrage an einen Webserver abgeschickt.
Der Benutzer selbst gibt dabei eine URL an die vom Browser in eine
http-Anfrage verpackt wird.
Beispiel einer http-Anfrage durch den
Browser (das kann man z.B. mit einem beliebigen Telnet-Client
über Port 80 des Servers nachvollziehen):
GET /smv/index.html HTTP/1.1 Host: www.nowhere.com
Anmerkung: Die angeforderte Ressource (hier /smv/index.html)
muss mindestens aus einem Slash "/" bestehen. Die Ressource darf
nicht wie in einem URI mit Protokollinformationen (http://)
und dem Hostnamen versehen sein. Die Host-Angabe
beinhaltet nur den Rechnernamen - keine Protokollinformationen!
Der Webserver überprüft
anhand seiner Konfiguration, ob es sich um eine "normale"
HTML-Datei handelt. Im vorliegenden Szenario soll das erst mal der
Fall sein. Entsprechend sucht der Webserver die angeforderte
Information (als Datei) in seinem Dateisystem.
Die HTML-Datei wird vom
Webserver eingelesen.
Anschließend wird die ursprüngliche Anfrage beantwortet. Zu einer vollständigen Antwort gehört der http-Header (gibt z.B. den Typ der nachfolgenden Datei oder das "Haltbarkeitsdatum" der Information an) und die eigentliche Information.
Aus Sicht des Clients bleibt alles wie gehabt. Er stellt eine Anfrage an den Webserver und erhält eine entsprechende Rückantwort im HTML-Format. Beim Server liegen die Dinge aber anders. Im Gegensatz zum vorigen Abschnitt, gibt es die angefordert HTML-Datei nämlich gar nicht. Die angeforderte Seite ist in Wirklichkeit der Name eines Programms das ebenfalls im Dateisystem des Webservers abgelegt ist. Der Webserver schickt nun aber nicht das Programm an den Client. Der könnte damit normalerweise auch nichts anfangen. Er startet vielmehr das Programm und stellt diesem eine entsprechende Arbeitsumgebung zur Verfügung. Das Programm wurde natürlich für eine spezielle Aufgabe programmiert, die es nun erfüllt. Zusätzlich gibt das Programm Informationen über sein Arbeitsergebnis aus. Im Unterschied zu einem normalen Programm formatiert es jedoch seine Ausgabe mit Hilfe von HTML-Anweisungen. Diese Ausgabe empfängt nun der Webserver. Um eine gültige HTTP-Antwort (Response) zu erzeugen, wird vom Server zuerst ein HTTP-Header an den Client gesendet. Anschliessend wird die vom Programm erzeugte Ausgabe an den Client weitergeleitet.
So richtig interaktiv kann dieser Vorgang natürlich nur werden, wenn der Benutzer des Clients dem Programm auch Eingabe- bzw. Verarbeitungsdaten übermitteln kann. Dies erfolgt im WWW z.B. mit Hilfe eines HTML-Formulars. Nachdem der Benutzer das Formular ausgefüllt hat, wird eine Schaltfläche zum Absenden betätigt. Technisch gesehen handelt es sich, wie bereits in den obigen Fällen, um eine http-Anfrage. Allerdings werden zusätzlich die vom Benutzer ausgefüllten Formulardaten an den Webserver übermittelt:
Beispiel 1 (bei Formularen mit GET-Methode durch Anhängen der Daten an den URL):
GET /cgi-bin/test.pl?nn=Meier&vn=Rolf HTTP/1.1 Host: www.nowhere.com
Beispiel 2 (bei Formularen mit POST-Methode durch Anfügen eines Entity-Bodys an den HTTP-Header):
POST /cgi-bin/test.pl HTTP/1.1 Host: www.nowhere.com Content-Length: 16 nn=Meier&vn=Rolf
Der Ablauf im Überblick:

Betrachten wir nun den Ablauf nochmals in Einzelschritten:
Der Benutzer erhält
während seines Aufenthalts im WWW ein Formular angezeigt. Er
füllt das Formular aus und betätigt eine Schaltfläche
zum Absenden des ausgefüllten Formulars. Diese Schaltfläche
hat ähnliche Eigenschaften wie ein normaler Hyperlink.
Der Browser erstellt eine
http-Anfrage an den Webserver und übermittelt zusätzlich
die vom Benutzer erstellten Formulardaten.
Der Webserver erhält die
Anfrage. Wie bereits weiter oben beschrieben überprüft er
mit Hilfe seiner Konfigurationsdaten, ob es sich um eine normale
HTML-Datei handelt. Im vorliegenden Fall ist die angeforderte Seite
ein CGI-Programm. Insofern prüft der Webserver hier die
Existenz des angeforderten Programms im Dateisystem.
Der Webserver startet das
Programm auf Betriebssystemebene und stellt im eine entsprechende
Arbeitsumgebung zur Verfügung. Diese beinhaltet u.a.:
Eine Fülle an
Umgebungsvariablen (Environment-Variablen) wie man sie aus DOS oder
aus Shells kennt. Damit erfährt das Programm beispielsweise
die IP-Nummer des Clients, den vom Client verwendeten Browser, die
Formulardaten (oder bei Verwendung der POST-Methode zumindest die
Länge der Formulardaten in Bytes), ...
Ein Eingabemedium (entspricht
der Tastatur eines normalen Programms) und ein Ausgabemedium
(entspricht dem Bildschirm eines normalen Programms).
Das Programm erledigt nun die
ihm zugedachte (einprogrammierte) Aufgabe. Es verwendet dazu
zusätzlich die Informationen aus den vorhandenen
Umgebungsvariablen bzw. des Eingabekanals. Die Rückmeldungen
versieht das Programm mit HTML-Anweisungen und gibt sie über
das Ausgabemedium aus.
Das Ausgabemedium ist aber der Webserver. Er empfängt die mit HTML angereicherte Ausgabe des Programms und leitet sie, mit vorangestelltem HTTP-Header, an den Client weiter.
Nachfolgend der Start einer derartigen HTTP-Antwort bis zum
<HTML>-Tag (Rest der HTML-Datei wurde weggelassen).
HTTP/1.1 200 OK Date: Wed, 05 Apr 2000 14:49:50 GMT Server: Apache/1.3.11 (Unix) PHP/3.0.12 mod_perl/1.21 Expires: Wed, 05 Apr 2000 15:50:37 GMT Transfer-Encoding: chunked Content-Type: text/html ee5 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"> <HTML>
Prinzipiell kann jedes Programm oder Skript auch im CGI-Bereich verwendet werden. Das Programm muß nur in der Lage sein
vom Standardeingabekanal zu lesen,
auf die Standardausgabe zu schreiben und
auf Umgebungsvariablen zugreifen zu können.
Damit sind z.B. folgende Formen von Programmen verwendbar:
Programme als Binärdatei. Diese wurden vorher mit Hilfe eines Compilers aus einer Programmiersprache erstellt.
Programme in einer Skriptsprache wenn der zugehörige Interpreter oder Just-in-time-Compiler im System verfügbar ist. Dies betrifft beispielsweise Perl, Python, Visual Basic, ...
Shellskripte die nur die in einer Betriebssystem-Shell verfügbaren Kommandos und Utilities benutzen. Diese sind am ehesten mit einer Batch-Datei unter DOS vergleichbar.
Die am häufigsten verwendete Lösung im CGI-Bereich sind Perl-Skripte. Dies liegt an den Fähigkeiten der Programmiersprache Perl. Sie besitzt sämtliche Elemente der populären Hochsprachen, hat eine einfache Art der Variablenverwaltung zu bieten und ist seit Version 5 auch objektorientiert programmierbar. Perl ist zudem unter der GNU Public License als Freeware verfügbar und zwischenzeitlich auf beinahe allen Betriebssystemen im WWW-Bereich kostenlos verfügbar (z.B. sämtliche Unix-Varianten, Microsoft NT, Win9x, MacOS, ...).
Nach der Installation von Perl im Betriebssystem ist in Zusammenarbeit mit einem beliebigen Texteditor die Entwicklungsumgebung bereits vollständig. Der Quellcode wird als normale ASCII-Datei verfaßt und abgespeichert. Bei Betriebssystemen die den Inhalt einer Datei an der Dateiendung erkennen, sollte .pl als Endung verwendet werden (z.B. test.pl). Bei allen Unix-Varianten wird die bereits von Shell-Skripten bekannte Art verwendet. Dazu wird in der ersten Zeile des Programms eine spezielle "Kommentar"-Anweisung untergebracht. Für Perl wäre dies:
#!/usr/bin/perl
Auch beim Programmieren auf anderen OS-Plattformen sollte dieser Syntax verwendet werden. Derartige Perl-Skripte sind nämlich ohne jegliche Änderung auch auf beliebigen anderen Systemen verwendbar. Außerdem sind zusätzliche Interpreter-Steuer-Schalter in dieser Zeile angebar die auch von nicht Unix-Systemen eingelesen werden.
Nun gilt es noch ihren Webserver für die Verwendung von CGI zu konfigurieren, die Perl-Skripte an der richtigen Stelle im Dateisystem abzulegen und evtl. noch mit den richtigen Rechten zu versehen.
Jedes CGI-Perl-Skript (ohne Verwendung externer Bibliotheksmodule) hat denselben Grundaufbau. Das Skript muß nämlich zuerst die übermittelten Formulardaten einlesen, diverse MIME-Dekodierungen rückgängig machen, Sicherheitschecks durchführen und die so aufbereiteten Formulardaten in einer geeigneten Variablen abspeichern.
Nachfolgend die einzelnen Bestandteile des Skripts in kleinen Häppchen.
#!/usr/bin/perl -w -T # -w => ausfuehrlichere Warnungen/Fehlermeldungen # -T => Sicherheitseinstellung: Tainted Mode |
Wie bereits weiter oben erläutert wird der Perl-Interpreter in Unix-Systemen auf diese Art aufgerufen. Die Pfadangabe wird von Nicht-Unix-Systemen ignoriert. Nicht so die zusätzlichen Schalter -w und -T. Der Schalter -w sorgt für etwas ausführlichere Warnungen und Fehlermeldungen. Der Schalter -T sorgt dafür, daß der Perl-Interpreter im tainted Mode läuft. Kurz und nicht ausreichend kann man sagen, daß der Perl-Interpreter in diesem Modus den Benutzereingaben mißtraut. Er reicht solche Parameter nicht an Betriebssystembefehle weiter. Der Schalter kann das Programmieren erschweren ist aber aus Sicherheitsgründen zu empfehlen. Achtung: Verwenden sie den Schalter -T nicht falls ihre Skripte über mod_perl abgearbeitet werden. In diesem Fall müssen sie den Tainted Mode über die WWW-Server-Konfigurationsdatei einstellen.
# Perl-Pragma um u.a. Variablendeklaration zu erzwingen # Insbesondere in Hinblick auf Apache-httpd mit mod_perl # zu empfehlen. use strict; |
Eigentlich muß man in Perl keine Variablen deklarieren (einen Variablentyp gibt es sowieso nicht). Um aber Nebeneffekte durch zu große Lebensdauer der Variablen oder Mehrfachverwendung des gleichen Variablennamens zu vermeiden, empfiehlt sich auch hier der Einsatz dieser Einschränkung. Zunehmend werden auch Webserver mit integriertem Perl-Interpreter (z.B. Apache httpd mit mod_perl) eingesetzt. Diese Webserver übersetzen den Perl-Code nur beim ersten Aufruf und halten ihn anschließend im Hauptspeicher um die Ausführungsgeschwindigkeit zu erhöhen. Leider werden dabei auch die Variableninhalte zwischen unabhängigen Aufrufen gespeichert. Dies ist nur durch die Einschränkung der Variablenlebensdauer zu vermeiden.
# Variablendeklaration my ($daten, @formulardaten, $formfeld, $name, $wert, %feld); |
Die Variablen werden mit my nach dem obigen Syntax deklariert. Außerdem wird damit ihre Lebensdauer auf den einmaligen Skriptablauf beschränkt.
# 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'} );
} |
Jedes HTML-Formular kann zum Versenden seiner Formulardaten die Methoden GET oder POST verwenden.
Bei GET werden die Formulardaten an den URL angehängt. Trennzeichen ist ein "?". Der Server trennt den URL und die Formulardaten und liefert letztere in der Umgebungsvariablen "QUERY_STRING". Bei dieser Art ist die Größe der übermittelbaren Daten auf 256 Zeichen beschränkt.
Bei POST werden die Formulardaten über die Standardeingabe STDIN an das Skript geliefert. Das Skript muß deshalb von der Standardeingabe die richtige Datenmenge einlesen. Die Anzahl an einzulesenden Bytes wird dem Skript in der Umgebungsvariablen "CONTENT_LENGTH" mitgeteilt.
Welche Methode das Formular verwendet hat, erfährt das Skript über die Umgebungsvariable "REQUEST_METHOD".
Üblicherweise stammt das HTML-Formular aus der gleichen "Feder" wie das zugehörige Verarbeitungs-Skript. Insofern muß der Programmierer nur eine Methode implementieren. Die obige Variante ist also eine Universallösung die selbständig zwischen den beiden Formen unterscheiden kann.
# Aufspaltung der Formulardaten in die einzelnen Felder @formulardaten = split(/&/, $daten); |
Unabhängig von der verwendeten Methode, übermittelt der Client-Browser die Formulardaten in folgendem Format:
<Feldname1>=<Feldwert1>&<Feldname2>=<Feldwert2>& ...
Mit Hilfe der Split-Anweisung werden die einzelnen Felder nun aus diesem String herausgelöst und in das eindimensionale Array @formulardaten geschrieben. Der Inhalt des Arrays sieht dann folgendermaßen aus:
$formulardaten[1]= <Feldname1>=<Feldwert1> $formulardaten[2]= <Feldname2>=<Feldwert2> ...
# Jedes Formularfeld einzeln bearbeiten und Ergebnis
# im Hash $feld ablegen.
foreach $formfeld (@formulardaten){
# Aufspaltung eines Felds in Feldname und -wert
($name, $wert) = split(/=/, $formfeld);
# 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;
# Neue Hashzeile mit Feldname und gefiltertem Feldwert
$feld{$name}=$wert;
} |
Die einzelnen Werte des Arrays $formulardaten müssen nun noch in Feldname und Feldwert aufgetrennt werden. Um das ganze Array elegant abzuarbeiten wird die Perl-Kontrollstruktur foreach() verwendet. Dabei wird jeweils ein Arrayelement aus dem Array extrahiert und hier der Variablen $formfeld zugewiesen. Danach wird der Schleifenkörper abgearbeitet. Innerhalb des Schleifenkörpers werden die folgenden Einzelschritte durchgeführt:
Die <Feldname1>=<Feldwert1> -Werte werden mit der split-Funktion anhand des "=" getrennt und die "Spaltergebnisse den Variablen $name und $wert zugewiesen.
In $wert sind jetzt aber aus übertragungstechnischen Gründen die Leer- und Sonderzeichen MIME-kodiert. Die nächsten beiden Anweisung machen diese Kodierung rückgängig.
Ein versierter Angreifer kann versuchen unser Skript mit eXtended Server Side Includes (XSSI) zu füttern. Damit könnte er z.B. versuchen Dateien einzusehen auf die er keine Rechte hat. Ein derartiger Angriff ist relativ einfach indem die XSSI-Anweisung in ein Formularfeld eingegeben wird. Damit unser Skript diese Anweisung jetzt aber nicht in den Ausgabestrom einfließen läßt, wird sie herausgefiltert.
Als letztes wird in dem assoziativen Feld (Hash) %feld eine neue Zeile angelegt. Sie enthält den Feldname als Zugriffs-Schlüssel und den Feldwert.
# 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>Übergebene Formulardaten</H1>\n";
# Die Daten jedes Formularfelds in eigener Zeile ausgeben
foreach $name (keys(%feld)){
print "Inhalt des Felds $name ist $feld{$name}<BR>\n";
}
# HTML-Ausgabe abschliessen
print "</BODY></HTML>\n";
|
Nun wird noch der letzte Teil des http-Headers (Content-Type gefolgt von einer Leerzeile) erzeugt und die HTML-formatierte Ausgabe erzeugt. Die verwendeten Perl-Anweisungen sind bis auf keys() bereits erklärt. keys() extrahiert aus einem assoziativen Feld (Hash) die Schlüsselspalte und liefert als Rückgabewert ein eindimensionales Feld.
Bis auf den letzten Teil sieht jedes Perl-Skript das keine Bibliotheken verwendet identisch aus. Der letzte Teil ist von der Aufgabe des Perl-Skripts abhängig und muß individuell erstellt werden. Denkbar sind hier eine Fülle von Aufgaben:
Gästebuch-Darstellung und erstellen neuer Einträge
Datenbankeinträge und -Abfragen
WWW-basierte Diskussionsforen
...
Eine Fülle derartiger Skripte ist im Internet als Free-, Share- und Löhnware verfügbar.
Request for Comments: 2068; Hypertext Transfer Protocol -- HTTP/1.1
c't Magazin für Computertechnik, Ausgaben 24/25 1998, Interaktiv im Web, CGI-Programmierung für den Hausgebrauch
Die deutsche Ausgabe von "Learning Perl", O'Reilly & Associates. Sehr gut strukturiertes Einführungsbuch. Dadurch auch im Programmierunterricht verwendbar.
http://www.teamone.de/selfhtml/ SELFHTML von Stefan Münz das allumfaßende Rundum-Werk. Insbesondere als Nachschlagewerk prima geeignet
http://www.perl.com Offizieller Perl Server mit Linksammlung zu allen Themen rund um Perl
http://www.worldwidemart.com/scripts/ Matt's Skriptesammlung mit einigen Beispielskripten
Lokal bzw.
Remote-Zugriffe seit dem 17. April 1999