CGI - Ein Tutorial
CGI-Tutorial von Daniel Schwamm (28.03.1998)
Inhalt
Im Gegensatz zu HTML, VRML und JavaScript kommt man beim Common Gateway Interface
(CGI) i.d.R. nicht nur mit einem ASCII-Editor aus; man benötigt eine Programmiersprache,
die entweder CGI-Scripts interpretieren oder eigenständige Applikationen (EXEs) bzw.
Dynamic Link Libraries (DLLs) kompilieren kann. CGI-Scripte sind im Zusammenhang mit
Perl unter UNIX sehr beliebt. Auch für NT findet man kostenlose Perl-Interpreter,
jedoch wollen wir diese hier nicht weiter betrachten. Unter NT scheinen mir
EXEs bzw. DLLs sinnvoller zu sein, zumal sie auch ein deutlich schnelleres
Antwortverhalten an den Tag legen. Der Einfachheit halber betrachten wir hier
nur EXEs, obwohl DLLs sicherlich die bessere Alternative darstellen (DLLs werden
nämlich nur einmal in den Speicher geladen und verbleiben dann dort resistent,
d.h., sie müssen im Gegensatz zu EXEs nicht bei jeder CGI-Anfrage neu geladen
und gestartet werden).
Als begeisterter Delphi-Programmierer werden die im Folgenden vorgestellten
CGI-Applikationen in dieser Sprache programmiert sein. CGI-Applikationen
können aber im Prinzip auch in jeder anderen Programmiersprache entwickelt
werden, die eigenständig laufende (Windows-)Programme generieren können,
wie z.B. C, C++, Assembler, usw.
CGI-Anwendungen bestehen i.d.R. aus zwei Teilen: aus dem HTML-File,
über welches die CGI-Applikation auf dem Webserver aufgerufen wird, und
aus der CGI-Applikation selbst, die, um ein gültiges CGI sein zu können,
bestimmte Formalismen erfüllen muss. Wir werden uns nun beide Teile
einzeln näher betrachten.
Widmen wir uns zuerst der statischen HTML-Datei, die der Anwender in seinen
Webbrowser (Client) einlädt. Man nehme dazu einen x-beliebigen ASCII-Editor,
z.B. Notepad in Windows, und tippe Folgendes ein:
00001
00002
00003
00004
00005
00006
00007
00008
00009
<html>
<body>
<center>
<a href='/cgi-bin/cgikurs/cgikurs1.cgi'>
Klick mich, um CGI aufzurufen
</a>
</center>
</body>
</html>
(=> cgikurs1.html)
Man sieht hier eine typische HTML-Referenz (einen Link), die im Gegensatz zu sonst
meist üblich kein statisches HTML-File adressiert, sondern ein ausführbares Programm
namens "cgikurs1.cgi", welches im "/cgi-bin/cgikurs/"-Verzeichnis liegt.
Mit etwas Fantasie und Programmierer-Know-how kann man sich vielleicht
bereits vorstellen, was diese ominösen CGI-Programme eigentlich so anstellen:
Sie liefern unserem freundlichen Webseitenbesucher eine HTML-Seite zurück - eine
HTML-Seite aber wohlgemerkt, die zum Zeitpunkt des Aufrufs i.d.R. noch gar nicht
existiert, sondern vom CGI-Programm erst selbst erzeugt und dann auf die
Standardausgabe des Betriebssystems geschrieben wird.
Und wie sieht nun der Source-Code von "cgikurs.cgi" in Delphi 2.0 aus?
Also von dem Teil der CGI-Applikation, der auf Webserverseite ausgeführt wird?
Let's take a look!
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
program cgikurs;
{$apptype console}
uses
windows,sysutils,classes;
begin
writeln('Content-type: text/html');
writeln;
writeln;
writeln('<html><body>');
writeln('Hello, world!');
writeln('</body></html>');
end.
Na, ist das nicht einfach? "{$apptype console}" sorgt dafür, dass unser
Delphi-Programm eine Konsolen-Applikation ist - wie oben erwähnt müssen wir
nämlich unsere dynamisch erzeugte HTML-Datei auf die Standardausgabe schreiben
(und nicht in ein Formular oder sonst wohin), was Windows-Programme von Hause
aus nicht machen (die schreiben i.d.R. nur ins eigene Fenster). Dann kommen
jene drei magischen Zeilen, ...
00001
00002
00003
writeln('Content-type: text/html');
writeln;
writeln;
... ohne die gar nix geht. Mein lieber Schwan! Haben die mich bei meinen
ersten CGI-Versuchen Nerven gekostet; nur eine Leerzeile weniger, die
durch "writeln" realisiert wird, und der Browser meldet nur noch
Bullshit zurück. Nun ja, der Rest ist aber eigentlich klar. Über ...
00001
00002
00003
writeln('<html><body>');
writeln('Hello, world!');
writeln('</body></html>');
... produzieren wir das klassische HTML-File ...
00001
00002
00003
<html><body>;
Hello, world!;
</body></html>
... welches - abgespeichert z.B. als "tst.htm" - mit jedem Webbrowser
auch offline betrachtet werden kann.
Klar, das ist alles noch wenig beeindruckend. Aber das Potenzial dahinter
ist bereits zu ersehen: Statt dem klassischen "Hello, world"-File
kann hier jedes x-beliebige HTML-File generiert werden. HTML-Files
z.B., die ihrerseits wiederum CGI-Applikationen referenzieren können. Oder
aber auch statische HTML-Dateien, wie wir gleich sehen werden.
Wie das vorherige Beispiel demonstrierte, können per CGI dynamische Webseiten
generiert und an den Webbrowser-Benutzer zurückgeschickt werden. Es ist
aber natürlich auch das Einfachste der Welt, auf herkömmliche Weise HTML-Seiten
zu entwickeln, also statisch, und diese dann in eine Delphi-CGI-Applikation
einzubinden. Ihr wollt Beweise? Sollt ihr bekommen.
Hier haben wir zunächst wieder das CGI-aufrufende HTML-File,
über welches wir uns diesmal allerdings zwei verschiedene
statische HTML-Dateien anzeigen lassen können.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
<html>
<body>
<center>
<a href='/cgi-bin/cgikurs/cgikurs2.cgi?tst1.htm'>
Klick mich, um HTML-Datei 'tst1.htm' anzuzeigen
</a>
<br>
<a href='/cgi-bin/cgikurs/cgikurs2.cgi?tst2.htm'>
Klick mich, um HTML-Datei 'tst2.htm' anzuzeigen
</a>
</center>
</body>
</html>
(=> cgikurs2.html)
Da hätten wir einmal die statische HTML-Datei namens "tst1.htm":
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
<html>
<body bgcolor='#000000' text='#6060ff' link='#ffff00' vlink='#c0c000'>
<center>
<font size='6'>
Ich bin die Datei <strong>'TST1.HTM'</strong>
</font>
<br>
<a href='/cgi-bin/cgikurs/cgikurs2.cgi?tst2.htm'>
Klick mich, um HTML-Datei 'tst2.htm' anzuzeigen
</a>
</center>
</body>
</html>
Und zum anderen die statische HTML-Datei namens "tst2.htm":
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
<html>
<body bgcolor='#6060ff' text='#000000' link='#ffff00' vlink='#c0c000'>
<center>
<font size='6'>
Ich bin die Datei <strong>'TST2.HTM'</strong>
</font>
<br>
<a href='/cgi-bin/cgikurs/cgikurs2.cgi?tst1.htm'>
Klick mich, um HTML-Datei 'tst1.htm' anzuzeigen
</a>
</center>
</body>
</html>
Das Delphi-CGI-Programm schliesslich, welches unsere beiden statischen
HTML-Dateien einladen und zum Webbrowser-Benutzer zurückschicken kann,
ist folgendermassen aufgebaut:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
program cgikurs2;
{$apptype console}
uses
forms,windows,sysutils,classes;
VAR
homedir,command,s:string;
tf:textfile;
begin
command:=paramstr(1);
homedir:=extractfilepath(application.exename);
writeln('Content-type: text/html');
writeln;
writeln;
assignfile(tf,homedir+command);
reset(tf);
while not eof(tf) do begin
readln(tf,s);
writeln(s);
end;
closefile(tf);
end.
Im Quellcode von "cgikurs2.html" (Beispiel II, erster Teil) sehen wir,
wie durch das Anhängsel "?[parameter]" in der Link-Referenz einem CGI-Programm
ein Parameter übergeben werden kann. In unserem Fall verwenden wir als Parameter
naheliegenderweise die Dateinamen unserer beiden statischen HTML-Dateien
"tst1.htm" und "tst2.htm". Durch die Programmzeile ...
00001
command:=paramstr(1);
... holt sich das Delphi-CGI-Programm eben diesen Parameter in die Variable
"command". Anschliessend wird das Verzeichnis ermittelt, in dem die
CGI-Applikation ausgeführt wird. Nun muss nur noch die gewünschte Datei im
Textmodus geöffnet, zeilenweise eingelesen, nach Standard-Output geschrieben,
und so an den Web-Client übermittelt werden.
Eben lernten wir, wie man einen einzelnen Parameter an ein CGI-Programm
übergeben kann. In diesem Beispiel wollen wir aber gleich mehrere Parameter
an unsere "cgi.cgi"-Applikation übergeben. Ausserdem sollen diese
Parameter vom Webseitenbenutzer auch noch selbst vorgegeben werden können.
Zu diesem Zweck eignen sich die HTML-Tags "<FORM>" und
"<INPUT>", die in die CGI-aufrufende Webseite einzubauen sind.
Unsere sogenanntes Webformular, also eine Webseite mit integriertem FORM-Tag,
über das ein Benutzer nahezu beliebige Inhalte an den Webserver übermitteln kann,
ist folgendermassen aufgebaut:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
<html>
<body>
<center>
<form action='/cgi-bin/cgikurs/cgikurs3.cgi' method='post'>
Vorname <input type='text' name='Vorname' value='D.'><br>
Nachname <input type='text' name='Nachname' value='S.'><br>
<input type='submit' value='Sende zu CGIKURS3.cgi'><br>
</form>
</center>
</body>
</html>
(=> cgikurs3.html)
Man beachte das Attribut "method='post'" im "<FORM>"-Tag.
Dadurch werden alle User-Eingaben auf der Webseite als Standardeingabe-Strom
an die CGI-Applikation gesendet, statt sie in die auf 255 Zeichen beschränkte
Parameterliste abzulegen (dies liesse sich durch "method='get'" erreichen).
Das folgende Delphi-CGI-Programm ist in der Lage, die vom Benutzer eingegebenen
und an den Webserver gesendeten Formulardaten auszulesen, und dann individuell
darauf reagieren zu können:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
program cgikurs3;
{$apptype console}
uses
forms,windows,sysutils,classes;
VAR
s,p:string;
cl,c:integer;
ch:char;
pc:array[0..10]of char;
procedure GetInput(var s:string);
begin
getenvironmentvariable('CONTENT_LENGTH',pc,255);
cl:=strtoint(strpas(pc));
for c:=1 to cl do begin
read(ch);
s:=s+ch;
end;
end;
begin
GetInput(s);
c:=pos('&',s);
writeln('Content-type: text/html');
writeln;
writeln;
writeln('<html><body><center>');
writeln(copy(s,1,c-1)+'<br>');
writeln(copy(s,c+1,length(s)-c)+'<br>');
writeln('</center></body></html>');
end.
Der Source ist schon etwas anspruchsvoller. Zunächst wird die Länge des
Standardeingabe-Stroms über eine vom Webserver zur Verfügung gestellten
Umgebungsvariable namens "CONTENT_LENGTH" ermittelt. Dies geschieht
über die Zeilen:
00001
00002
getenvironmentvariable('CONTENT_LENGTH',pc,255);
cl:=strtoint(strpas(pc));
Danach wird bewusster Standardeingabe-Strom Zeichen für Zeichen in die
String-Variable "s" eingelesen. Die Länge der gesamten Eingabe
haben wir ja zuvor ermittelt. Die einzelnen Benutzereingaben (in unserem
Fall der Inhalt zweier Input-Felder) sind gemäss der CGI-Spezifikation in
der String-Variablen "s" jeweils durch ein "&"-Zeichen
voneinander getrennt. Die exakte Position von "&" zwischen unseren
beiden Eingabefeldern wird nun mit mithilfe der "pos()"-Funktion
von Delphi über die Programmzeile ...
... ermittelt. Anschliessend werden beide Inputs über ...
00001
00002
writeln(copy(s,1,c-1)+'<br>');
writeln(copy(s,c+1,length(s)-c)+'<br>');
... getrennt voneinander auf den Standardausgabe-Strom ausgegeben und
damit an den Web-Client zurückgesandt.
Gut - kommen wir nun zu einem Beispiel mit praktischerem Nutzen
als dem bisher Gezeigtem: eine Suchmaschine! Nun ja, eine Minisuchmaschine
zumindest.
Gehen wir von folgender Datenbank namens "db.txt" aus, die pro Eintrag
aus zwei Zielen besteht. Die jeweils erste Zeile eines Eintrages nennt den Namen
einer Programmiersprache. Und in der zweiten Zeile steht eine subjektive Schulnote,
die Auskunft gibt über die Qualität der zugehörigen Programmiersprache, wobei
gilt: 0 Punkte mies bis 15 Punkte top.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
C
10
C++
12
COBOL
4
Delphi
15
FORTRAN
6
Das Suchformular erlaubt es dem Benutzer, einmal einen Buchstaben vorzugeben,
der in der zu suchenden Programmiersprache enthalten sein muss. Zum anderen
lässt sich die Schulnote vorgeben, die die gesuchte Programmiersprache
mindestens innehaben sollte. Der Quellcode dazu sieht so aus:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
<html>
<body>
<center>
<strong>Programmiersprachen</strong><br>
<form action='/cgi-bin/cgikurs/cgikurs4.cgi' method='post'>
Sprachname enthaelt <input type='text' name='Name' value='c'><br>
Sprache hat mehr Punkte als
<SELECT NAME='Mindestpunkte'>
<OPTION>0</OPTION>
<OPTION>5</OPTION>
<OPTION>10</OPTION>
<OPTION>15</OPTION>
</SELECT>
<br>
<input type='submit' value='Selektiere aus DB.TXT'><br>
</form>
</center>
</body>
</html>
(=> cgikurs4.html)
Zuletzt betrachten wir den diesmal schon recht umfangreichen Quellcode des
Delphi-CGI-Programms, welches die eigentliche Sucharbeit in der Datenbank
gemäss der per CGI übergebenen Kriterien ausführt und dann das Ergebnis,
also die gefundenen Treffer, an den Benutzer zurückschickt:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
program cgikurs4;
{$apptype console}
uses
forms,windows,sysutils,classes;
const
_dbfn='db.txt';
VAR
homedir,s,pname,mpunkte,dbname,dbpunkte:string;
cl,c:integer;
ch:char;
pc:array[0..10]of char;
tf:textfile;
procedure GetInput(var s:string);
begin
getenvironmentvariable('CONTENT_LENGTH',pc,255);
cl:=strtoint(strpas(pc));
for c:=1 to cl do begin
read(ch);
s:=s+ch;
end;
end;
begin
homedir:=extractfilepath(application.exename);
GetInput(s);
c:=pos('&',s);
pname:=copy(s,1,c-1);
mpunkte:=copy(s,c+1,length(s)-c);
writeln('Content-type: text/html');
writeln;
writeln;
writeln('<html><body><center>');
writeln(
'Suche nach: <strong>'+pname+' '+mpunkte+
'</strong> Gefunden wurden:<br>'
);
writeln('<table border=1 cellpadding=5 cellspacing=0>');
writeln(
'<tr bgcolor=a0a0a0><th>Sprache</th><th>Punkte</th><tr>'
);
pname:=uppercase(copy(pname,pos('=',pname)+1,length(pname)));
mpunkte:=copy(mpunkte,pos('=',mpunkte)+1,length(mpunkte));
assignfile(tf,homedir+_dbfn);
reset(tf);
while not eof(tf) do begin
readln(tf,dbname);
readln(tf,dbpunkte);
if
((pos(pname,uppercase(dbname))<>0)or(pname=''))and
(strtoint(dbpunkte)>=strtoint(mpunkte))
then
writeln(
'<tr bgcolor=d0d0d0><th>'+dbname+'</th>'+
'<th>'+dbpunkte+'</th></tr>'
);
end;
closefile(tf);
writeln('</Table></center></body></html>');
end.
Ja, das ist schon ein etwas dickerer Brocken. Zunächst filtern wir
die beiden Eingabe-Variablen des Benutzers aus dem Standardeingabe-Strom.
Wir greifen dazu auf die bewährte Methode des vorangegangenen Beispiels
zurück und suchen nach dem Trennzeichen "&". In "pname"
steht dann der Buchstabe, der in allen Treffern enthalten sein muss.
Und in "mpunkte" die Schulnote, die alle Treffer mindestens
erreichen müssen.
00001
00002
00003
00004
GetInput(s);
c:=pos('&',s);
pname:=copy(s,1,c-1);
mpunkte:=copy(s,c+1,length(s)-c);
Diese Werte stehen in den Strings "pname" und "mpunkte"
allerdings nicht isoliert, sondern noch im Verbund mit dem Namen
der HTML-Formular-Variablen, und zwar in der Form "formname=forminhalt".
Indem wir jetzt also im Anschluss nach dem Trennzeichen "=" suchen,
können wir die genauen Einzelwerte herausfiltern:
00001
00002
pname:=uppercase(copy(pname,pos('=',pname)+1,length(pname)));
mpunkte:=copy(mpunkte,pos('=',mpunkte)+1,length(mpunkte));
Anschliessend öffnen wir die Datenbank "db.txt" und durchlaufen sie
vollständig. Je Doppelzeile, eingelesen in die Variablen "dbname"
und "dbpunkte", wird dabei überprüft, ob die Suchkriterien
"pname" und "mpunkte" erfüllt wurden:
00001
00002
((pos(pname,uppercase(dbname))<>0)or(pname=''))and
(strtoint(dbpunkte)>=strtoint(mpunkte))
Wenn ja, wird der Eintrag ausgegeben, ansonsten ignoriert. Und um
das Ganze optisch etwas ansprechender zu machen, wird noch eine
HTML-Table um die Ausgabe verpackt. That's all.