[ zurück ]

Ein Beispiel-Win-CGI-Programm (mit Delphi1)

(Dieses Projekt ist ausführlicher in der Zeitschrift PC-Magazin DOS , Heft 10/97, Seite 224 ff und im Delphi-Special, Heft 6/98, Seite 28ff beschrieben.)

[ Trouble-Shooting zum Artikel ]


Inhaltsübersicht

Zum Nachvollziehen des Projekts sind erforderlich:


1. Delphi-Freeware Komponenten nutzen

Das CGI-Script muß während seiner Arbeit aus dem Inputfile alle interessierenden Daten lesen. Das ist nicht weiter problematisch, da das Inputfile im INI-Stil vorliegt. Die Delphi-Unit "IniFiles" stellt alle nötigen Instrumentarien zur effektiven Behandlung solcher Dateien bereit. Noch effektiver ist aber die Verwendung einer speziellen CGI-Komponente, die als Freeware verfügbar ist ( cgi.zip ). Die Komponente liegt zwar als 16-bit-Variante vor, aber da der Quelltext vorhanden ist, kann jeder daraus auch eine 32-bit-Version erstellen. Bei Problemen diesbezüglich schauen Sie bitte auf der Seite "Trouble-Shooting zum Artikel" nach.

Die Hauptkomponente "TCGI" aus dieser Komponentensammlung von M. B. Klein stellt über ihre Eigenschaft "Profile" fast alle Informationen aufbereitet bereit, die aus dem CGI-Inputfile lesbar sind. Außerdem verfügt die Komponente über eine Methode "Send", mit der die Ausgabedaten des Scripts ressourcenschonend in den Ausgabedatenstrom geschrieben werden. Die Verwendung der ohnehin gut dokumentierten Komponente "TCGI" erklärt sich am besten am Beispiel. Die nicht ganz so ernst zu nehmenden Verweigerungsseite dieses Beispiel-Scripts verwendet dabei die Instanz "CGI1" der Komponente "TCGI".
procedure Fehler;
begin
 if (CGI1.Method=rmPost) and (CGI1.FormFields.Values['Name']='Hacker') then
 with CGI1 do begin
  Send('<HTML><HEAD><TITLE>Fehlermeldung</TITLE></HEAD>');
  Send('<BODY>');
  Send('<H2>FEHLER !</H2><HR>');
  Send('Die Telefondatenbank ist gesperrt.<BR>');
  Send('Melden Sie sich beim ');
  Send('<A HREF="mailto:' + sAdmin + '">Administrator</A>.');
  Send('</BODY></HTML>');
 end;
end;

Die Prozedur "Fehler" überprüft, mit welcher Methode das Script aufgerufen wurde. Stammt der Aufruf aus einem Formular mit der Anforderungsmethode "POST" und ist im Formularfeld "Name" der Herr Hacker zu identifizieren, dann sendet das Script eine Verweigerungsseite an den Aufrufer zurück.

"CGI1.FormFields" ist dabei ein von "TStringList" abstammender Typ und beinhaltet alle Paare der Formulardaten. Die Ausgaben in das Outputfile erledigt die Methode "CGI1.Send". Sie speichert die im HTML-Format anzugebenden Ausgaben zunächst in einen "TMemoryStream", der bei Beendigung des Scripts automatisch in die durch "CGI1.Profile.Outputfile" bezeichnete Datei im temporären Verzeichnis übertragen wird. Um solche Details kümmert sich aber die Komponente "CGI1" selbständig.


2. Projektbeschreibung "Telefonverzeichnis"

Beginnen wir einfach ein Projekt für ein firmeninternes Web-Telefonverzeichnis, in das sich jeder Mitarbeiter selbst eintragen kann. Außerdem soll jeder Nutzer eine Suchanfrage formulieren oder sich den ganzen Inhalt als Liste auf seinen Browser holen können. Der Einfachheit halber verzichten wir bei diesem Projekt auf jegliche Schutzmechanismen für die Daten. Auch ohne Autorisierung kann vorerst jeder unerkannt den Datenbestand des Firmenverzeichnisses beliebig ergänzen.

Unser Script soll "CgiTelBu.exe" heißen und nach Fertigstellung im Serververzeichnis "/cgiwin/" liegen. Als Datenbank wird im Demo eine Textdatei verwendet, die im Verzeichnis "C:\Dat" angelegt wird. Diese Angaben können im Kopfteil der Programmdatei "CgiTUnit.pas" geändert werden.
Const
 sAction='/cgi-win/CgiTelBu.exe';   {logischer Pfad zum Script}
 sTeleBuch='C:\Dat\TeleBuch.txt';   {physischer Pfad zur Datenbank}
 sAdmin='Telefonverwalter@net';

Unser Beispielscript soll alle notwendigen Browserseiten dynamisch erzeugen. Auch die Formulare zur Eintragung der Suchanfrage und der neuen Teilnehmer sollen vom Script generiert werden. Natürlich könnten diese auch als statische HTML-Seiten auf dem Server hinterlegt sein. Das Projekt soll aber zeigen, daß eine kompakte Anwendung wie unser Telefonverzeichnis in einer einzigen Script-Datei realisierbar ist.

So senkt man beachtlich den Administrationsaufwand für eine Webanwendung, während der Programmieraufwand nicht wesentlich steigt. Im "Power-Netz" wäre höchstens die damit verursachte höhere Serverbelastung zu berücksichtigen.


3. Ein formularloses Delphi-Programm erzeugen

Unser Script läuft auf dem Server immer dann für wenige Augenblicke, wenn es durch einen Mitarbeiter über dessen Browser aufgerufen wird. Nach Pflichterfüllung wird es wieder beendet. Deshalb braucht ein Windows-CGI-Script kein eigenes Fenster, denn auf dem Server arbeitet niemand per Bildschirm mit ihm. Delphi ermöglicht die Erzeugung "formloser" Programme, wenn wir das Projekt auf etwas besondere Art beginnen.

Starten Sie also Delphi und schließen Sie alte Projekte. Erzeugen Sie über Datei - Neue Unit ein leeres Programmiermodul und ändern Sie die Kopfzeile
unit Unit1

in
program CgiTelBu.

Löschen Sie dann den Rest des vorhandenen Quelltextes und tragen Sie den fürs erste ausreichenden Projekt-Quelltext ein. Er wird später noch ausgebaut.
program CgiTelBu;
uses CGI;
begin
 CGI1:=TCGI.Create(nil);
 case CGI1.Method of
  rmPost:         ; {Aktionen bei Methode Post}
  rmGet: FrmSuchen; {Abfrageformular senden}
  else            ; {Aktionen bei illeg. Aufruf}
 end;
 CGI1.SendContent;  {Ausgabefile abschicken}
 CGI1.Free;
end.

Speichern Sie jetzt diese Datei im beabsichtigten Verzeichnis mit Datei - Datei speichern unter - CgiTelBu.PAS.

Erzeugen Sie nun die eigentliche Programm-Unit mit Datei - neue Unit und ändern Sie den vorgegebenen Inhalt wie folgt ab:
unit CgiTUnit;
interface
uses CGI, WinProcs, SysUtils, Classes;
procedure FrmSuchen;  {Formular Suchanfrage senden}
var CGI1: TCGI;
implementation
procedure FrmSuchen;
begin end;
end.

Speichern sie das neue Modul über Datei - Speichern unter - CgiTUnit.PAS und schließen sie beide Dateien. Öffnen Sie das Projekt mit Datei - Projekt öffnen - Dateityp *.PAS - CgiTelBu.PAS und melden Sie mit Datei - Datei hinzufügen - CgiTUnit.PAS die Programm-Unit an. Jetzt sollten Sie das Projekt speichern.


4. Alle Telefonbuchaktionen implementieren

Die Prozedur "FrmSuchen" soll bei Aufruf des Scripts über die Methode "GET" ein Suchformular an den Client senden. Diese Situation entsteht beim Eintragen von "http://.../cgi-win/CgiTelBu.exe" in die Adreßzeile des Browsers oder beim Klick auf einen üblichen Link. Erfüllen Sie dazu den Körper der Routine "FrmSuchen" wie im abgedruckten Listing mit Leben.
procedure FrmSuchen;   {Formular Suchabfrage senden}
begin
 with CGI1 do begin
  Send('<HTML><HEAD><TITLE>Suchformular</TITLE></HEAD>');
  Send('<BODY>');
  Send('<H3>Suchanfrage f&uuml;r unser Telefonbuch</H3><HR><BR><BR>');
  Send('<FORM Method="POST" Action="/cgi-win/CgiTelBu.exe?Suchen">');
  Send('<PRE>');
  Send(' Es soll nach ');
  Send('<INPUT Name="Frage" Size=20>');
  Send(' gesucht werden.     ');
  Send('<INPUT Type="submit" Value="Suchen"> ');
  Send('<INPUT Type="reset"  Value="Verwerfen">');
  Send('</PRE></FORM><HR>');
  Send(DateToStr(Date));
  Send('</BODY></HTML>');
 end;
end;

An dieser Stelle steht einem ersten Compilieren und Testen des Scripts nichts mehr im Wege. Die CGI-Komponente muß natürlich im Bibliothekspfad des Delphi-Compilers zu finden sein. Es ist jedoch für dieses Projekt nicht erforderlich, sie in der VCL zu registrieren

Mit einem Start des Programms sollten Sie sich aber noch etwas gedulden. Aus der Delphi-IDE heraus würden Sie nur eine Fehlermeldung präsentiert bekommen.

Achtung: Das Script benötigt zur korrekten Ausführung der CGI-Aktion Kommandozeilenparameter mit den temporären Dateinamen. Diese bekommt es nur bei Aufruf durch den Webserver.

Also starten Sie Ihren Webserver, nutzen den Browser und geben in dessen Adreßzeile die Aufruf-URL des Scripts ein (http://.../cgi-win/CgiTelBu.exe). Sorgen Sie zuvor dafür, daß "CgiTelBu.exe" auch wirklich im richtigen Verzeichnis liegt (/cgi-win ist ein virtuelles Verzeichnis auf dem Server!). Das Ergebnis könnte so aussehen:

Bild: "Das erzeugte Suchformular" (18 KByte)

Natürlich dürfen Sie das Formular bereits ausfüllen und absenden. Das Formular hat ja als Aktion die Angabe "Action='/cgi-win/CgiTelBu.exe?Suchen'". Aber Sie werden nur von einer leeren Antwortseite beglückt. Unser Script braucht dringend eine Routine, die es bei der Aufrufmethode "POST" mit dem Query-String "Suchen" ausführt. Die entsprechende Prozedur in der Unit "CgiTUnit" heißt einfach "Suchen" und ist dem Gesamtlisting zum Projekt zu entnehmen.

Nachfolgend kommen nun noch weitere Routinen dazu. Das ist beispielsweise eine Prozedur namens "FrmEintrag" zum Senden eines Formular. Hierin erfolgt das Eintragen neuer Mitarbeiter. Eine Routine "Eintragen" erledigt das Speichern der Angaben in der Datenbank. Eine weitere Prozedur "Liste" sorgt für die Ausgabe des Gesamtverzeichnisses an den Neugierigen. Außerdem wird die weiter oben beschriebene Routine "Fehler" in abgewandelter Form implementiert. Sie ist die Quittung auf unzulässige Aufrufe des Script.

Damit alle diese Aktionen in der richtigen Situation ablaufen, überarbeiten wir jetzt auch den Projektquelltext noch einmal geringfügig. Seine endgültige Variante lautet:
program CgiTelBu;  {Projektdatei}
uses CGI, CgiTUnit;
var sQuery: String;
begin
 CGI1:=TCGI.Create(nil);
 sQuery:=CGI1.Profile.QueryString;
 case CGI1.Method of
  rmPost: if sQuery='Eintrag' then Eintrag else
           if sQuery='Suchen' then Suchen else Fehler;
  rmGet:  if sQuery='Eintrag' then FrmEintrag else
           if sQuery='Suchen' then FrmSuchen else Liste;
  else Fehler;
 end;
 CGI1.SendContent;
 CGI1.Free;
end.

Die verwendete "case"-Anweisung selektiert dabei alle Fälle für die unterschiedlichen Aufrufmöglichkeiten. Alle "GET"-Aufrufe kommen aus der Browseradreßzeile oder von einem Link. Alle "POST"-Aufrufe stammen aus einem vom Script selbst erzeugten Formular.

AufrufmethodeQuerystringProzedurAktion
GET"Eintrag"FrmEintragsendet Formular für neuen Teilnehmer
GET"Suchen"FrmSuchensendet Formular für Suchanfrage
GETbeliebigListesendet komplette Telefonliste
POST"Eintrag"Eintragprüft die Daten auf Vollständigkeit, trägt neuen Teilnehmer in die Datenbank ein und sendet Bestätigung
POST"Suchen"Suchensucht Teilnehmer laut Anfrageformular und sendet Ergebnisliste
sonst Fehlersendet Fehlermeldung mit Hinweisen

Die im vorgestellte Projekt verwendete Datenbank ist eine einfache Textdatei. Jeder Datensatz wird als eine Zeile in der Form "FName|VName|Tel|Fax|eMail|Zimmer|" mit einem "|" als Trennzeichen gespeichert. Das sieht dann etwa so aus:
 Mustermann|Max|20-794|20-799|max@net|134|
 Hirsch|Harry|20-000|20-009|hhirsch@net|010|
 Paul|Paula|20-487||pp@net|102|
 Mensch|Eva|20-084||emensch@net|035|
 Reich|Robert|20-033|20-039|reich@net|102|
 Wichtig|Heinz|20-082||hichtig@net|124|
 Murx|Reiner|20-555|20-556|murx@net|055|

Für eine Ausgabe der Teilnehmerdaten wird die Datei in eine Instanz vom Typ "TStringList" eingelesen und die Eigenschaft "Sorted" auf wahr gesetzt. Damit erfolgen die Ausgaben in alphabetischer Reihenfolge, wie die Routine "Liste" und die Abbildung "alphabetisch sortierte Telefonliste" (23 Kbyte) zeigt.
procedure Liste;  {komplettes Verzeichnis senden}
var Liste: TStringList;
begin
 Liste:=TStringList.Create;
 if FileExists(sTeleBuch) then Liste.LoadFromFile(sTeleBuch);
 SendHeader('Telefonliste');
 CGI1.Send('<H3>unser komplettes Telefonverzeichnis</H3>');
 Liste.Sorted:=true;
 SendListe(Liste);
 Liste.Free;
end;


5. verbesserte Datenbankanbindung

Eine derartig simple Datenquelle ist zugegebenermaßen nur ein Notbehelf. Gerade Delphi bietet mit seiner Datenbankschnittstelle beste Voraussetzungen, um eine leistungsfähige Lösung auch bei großem Dateiumfang zu programmieren. Da aber unser Programm kein Fensterformular hat, kann nicht einfach eine "TTable"- oder "TQuery"-Komponente visuell eingefügt werden. Trotzdem können Sie diese Komponenten voll nutzen.

Erzeugen wir doch einfach eine Datenbankkomponente "TTable" oder "TQuery" über Programmcode zur Laufzeit des Programmes mit deren Methode "Create". Das folgende Listing der Prozedur "Suchen" demonstriert zur Anregung eine Variante, die nicht in der vorliegenden Programmversion realisiert ist.
procedure Suchen;
var Q: TQuery; sTx: String; i: Integer;
begin
 sTx:=CGI1.FormFields.Values['Frage'];
 SendHeader('Telefon-Auskunft');
 CGI1.Send('<H3>Ergebnis Ihrer Datenbankanfrage</H3>');
 CGI1.Send('Gesucht wurde nach:' + LowerCase(sTx)+'<HR>');
 if sTx>' ' then begin
  Q:=TQuery.Create(nil);
  with Q do begin
   DatabaseName:='C:\Dat';
   SQL.Add('Select * from "TELEFBU.DBF"');
   SQL.Add('Where FNAME Like "%'+sTx+'%"');
   Open;
  end;
  if Q.EOF then
   CGI1.Send('Leider kein passender Eintrag gefunden!<BR>')
  else begin
   CGI1.Send('<TABLE Border=5>')
   CGI1.Send('<TR><TH>Name</TH><TH>Vorname</TH><TH>Telefon</TH></TR>');
   repeat
    CGI1.Send('<TR>');
    for i:=0 to 2 do begin
     sTx:=Q.Fields[i].AsString;
     CGI1.Send('<TD>' + sTx +' </TD>');
    end;
    CGI1.Send('</TR>');
    Q.Next;
   until Q.EOF;
   CGI1.Send('</TABLE><BR>');
  end;
  Q.Close; Q.Free;
 end;
 SendFooter;
end;

Zu Beginn wird ein Kopf im HTML-Format für die Antwortseite der Datenbankabfrage ausgegeben. Danach wird eine "TQuery"-Komponente erzeugt und die Eigenschaften "DatabaseName" und "SQL" gesetzt. Mit dem Öffnen der "Query" wird eine Ereignismenge mit allen passenden Datensätzen von der Borland-Database-Engine zur Verfügung gestellt. Diese wird dann Satz für Satz mit den ersten drei Feldern "FName", "VName" und "Telefon" als HTML-formatierte Tabelle ausgegeben. Die dynamische Webseite wird mit einem eigenen Fuß abgeschlossen.

Vergessen Sie auf keinen Fall das Freigeben der selbst erzeugten "Query"-Komponente vor Abschluß der Routine mit deren Methode "Free".


Anhang: Programmlistings

(Bei Problemen schauen Sie doch bitte auch auf meine Seite Trouble-Shooting).

Viel Erfolg bei eigenen Versuchen!


[ Seitenanfang ] [ Win-CGI ]

J. Hummel,   1997