CGI-Programmierung mit COMAL
[CGI = Common Gateway Interface]

Joachim Neuber, Dezember 2001
jneuber@foni.net

Literatur: Die m.E. beste deutschsprachige Einführung in CGI-Programmierung befindet sich in SELFHTML von selfhtml.teamone.de

Download des COMAL-Moduls STDOUT.EXE+comments.exe+comments.lst+cgicomal.htm als .ZIP-Datei (59KB)

Client-Server-Programmierung am Beispiel Browser-Webserver
-------------------------------------------------------------------------------------

Der Browser (Client) fordert beim - meist weit entfernten- Webserver ein Dokument z.B. info.htm an. Der Webserver sucht das angeforderte Dokument in seinem Webverzeichnis und liefert es an den Browser zurück. Die Regeln, nach denen sich diese Kommunikation abspielt sind im HTTP-Protokoll (HyperText-Transfere-Protocol) definiert.

Zusätzlich gibt es die Möglichkeit, per Browser Daten an den Webserver zu schicken, den Webserver zu veranlassen, auf dem Server-Rechner ein Programm zu starten, welches die Daten verarbeitet und dann eine HTML-codierte Antwort über den Webserver zurückzuschicken. Das bekannteste Beispiel für derartige Anwendungen ist sicherlich die Suchanfrage über ein Formular bei Suchmaschinen. Die Regeln nach denen Browser, Webserver und das vom Browser auf dem Webserver aufgerufene Programm interagieren, sind in der CGI-Schnittstellendefinition festgelegt. Im Prinzip können alle auf dem Betriebssystem des Serverrechners lauffähigen Programme über CGI ausgeführt werden (Näheres regelt die Konfiguration des Webservers). Aber ein für UNIX kompiliertes C-Programm läuft natürlich nicht auf einem Webserver in einer WINDOWS-Umgebung.

Immer noch wird zur Programmierung sogenannter CGI-Programme die aus der UNIX-Welt kommende Programmiersprache PERL verwendet. Sie hat mehrere Vorteile: frei verfügbar (kostenlos!) auch für Windows-Plattformen, sehr mächtig, insbesondere mächtige STRING-Verarbeitungsroutinen, aber auch ein wenig ungewohnt. Warum daher nicht bei COMAL bleiben?

Mit COMAL CGI-Programme schreiben
------------------------------------------------------
Auch COMAL bietet sehr mächtige STRING-Verarbeitungsmöglichkeiten und sollte sich daher zur CGI-Programmierung (natürlich nur unter Windows-Systemen) besonders gut eignen. Es stellt sich daher die Frage, was eine Programmiersprache außerdem 'bieten' muss, um sie zur CGI-Programmierung geeignet zu machen? CGI-Programme müssen die vom Browser übergebenen Daten entweder über die vom Webserver gesetzte(n) Umgebungsvariable(n) oder von der Standardeingabe (STDIN) lesen können. Auf beides hat der COMAL-Programmierer direkten Zugriff. Das Auslesen von Umgebungsvariablen geschieht über die Funktion environment$ des System-Packets z.B. dat$=environment$("QUERY_STRING"), die Standardeingabe kann z.B. über die Anweisungsfolge

anzahl#:=environment$("CONTENT_LENGTH")
OPEN FILE 1, "CON:", READ
dat$:= GET$(1, anzahl#)
CLOSE FILE 1


eingelesen werden. Das einzige Problem, das COMAL einem CGI-Programmierer beschert ist die Datenausgabe. CGI-Programme müssen selbst eine HTML-codierte Antwort über den Webserver an den Browser zurückschicken. Das Programm muss dazu auf die Standardausgabe STDOUT schreiben, die der Webserver wiederum auf sich umlenkt. Die COMAL-Programmierer haben damals aus Geschwindigkeitsgründen die PRINT-Anweisung so implementiert, dass sie entweder direkt in den Bildschirmspeicher schreibt, oder die BIOS-Funktionen zum Schreiben auf den Bildschirm benutzt, nicht jedoch die Standardausgabe des Betriebssystems DOS-Interrupt 21 benutzt. Daher kann der Webserver die PRINT-Ausgaben eines COMAL-Programms nicht auf sich umlenken. Die Lösung des Problems ist jedoch einfach: Ein kleines Assembler-Modul stdout enthält als einzige Prozedur PROC printout(a$), die den STRING a$ über den DOS-Interrupt 21 an die Standardausgabe STDOUT schreibt. Benutzt man statt der COMAL-eigenen PRINT-Anweisung die Prozedur printout() aus dem stdout-Modul, kann der Webserver die Ausgaben auf sich umlenken und an den anfragenden Browser weiterleiten.

Beipiel-Anwendung: Kommentar-Formular
--------------------------------------------------------

Ein Beispiel zeigt die Interaktion zwischen Browser, Webserver und CGI-Programm. Auf dem Webserver befindet sich in seinem Dokumentenverzeichnis die Seite kommentar.htm, die ein Formular enthält, in das der Anwender seinen Namen und seinen Kommentar zum hoffentlich gut gemachten Webangebot einer Firma machen kann:

Das HTML-FORMULAR:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Kommentarseite</title>
</head>
<body bgcolor="#E0E0E0">
<h1>Ihr Kommentar</h1>
<form action="/cgi-bin/comments.exe" method="post">
<p>Name:<br><input size="40" maxlength="40" name="AnwenderName"></p>
<p>Text:<br><textarea rows="5" cols="50" name="Kommentartext"></textarea></p>
<p><input type="submit" value="Absenden"></p>
</form>
</body>
</html>

Das Formular enthält im einleitenden TAG <form> die Anweisung action="/cgi-bin/comments.exe" method="post", die den Webserver anweist, in seinem Verzeichnis /cgi-bin/ das COMAL-Programm comments.exe auszuführen. Die Daten an das Programm werden mit der Methode POST übergeben, d.h. dass das Programm comments.exe die Daten von der Standardeingabe lesen muss und die Anzahl der übergebenen Bytes in der Umgebungsvariablen CONTENT_LENGTH enthalten sind.

Das COMAL-HAUPTPROGRAMM:

0080 USE system
0090 USE stdout
0100
0110 e$:=CHR$(13)+CHR$(10)+"$" // Endkennung fuer printout!!
0120
0130 DIM dat$ OF 1000
0190 getdata(dat$)
0200
0210 html_begin("comments.cml")
0220 eingabedaten_verarbeiten
0230 print_("<h1>Vielen Dank fr Ihren Kommentar</h1>")
0240 print_("<br>")
0250 print_("<p>Hier zur Kontrolle Ihre Eingaben:</p>")
0260 print_("<b>"+name1$+": </b>"+wert1$+"<br>")
0270 print_("<b>"+name2$+": </b>"+wert2$+"<br>")
0280 print_("<br>")
0290 print_("<i>"+DATE$+" "+TIME$+"</i><br>")
0300 print_("<i>"+environment$("SCRIPT_NAME")+"</i><br>")
0310
0320 html_end

Die PROZEDUREN:

1520 PROC html_begin(title$)
1530 // printout schreibt auf STDOUT
1540 // e$ muss wg der Endkennung $ (DOS-Konvention) angehängt werden
1550
1560 print_("content-type: text/html")
1570 print_("")
1580 print_("<html>")
1590 print_("<head>")
1600 print_("<title>"+title$+"</title>")
1610 print_("</head>")
1620 print_("<body>")
1630 ENDPROC html_begin
1640
1650 PROC html_end
1660 print_("</body>")
1670 print_("</html>")
1680 ENDPROC html_end
1690
1700 PROC getdata(REF dat$)
1710 rm$:=environment$("REQUEST_METHOD")
1720 rm$:=UPPER$(rm$)
1730 CASE rm$ OF
1740 WHEN "POST"
1750 // Daten von Standardeingabe lesen
1760 ct$:=environment$("CONTENT_LENGTH")
1770 ct#:=VAL(ct$)
1780 OPEN FILE 1,"CON:",READ
1790 dat$:=GET$(1,ct#)
1800 CLOSE FILE 1
1810 WHEN "GET"
1820 dat$:=environment$("QUERY_STRING")
1830 OTHERWISE
1840 dat$:="Es wurden keine Daten Übergeben!"
1850 ENDCASE
1860 ENDPROC getdata
1870
1880 PROC print(a$)
1890 // printout schreibt per DOS-Interrupt auf STDOUT
1900 // DOS erwartet als Endkennung das $-Zeichen
1910 // alles vor dem $-Zeichen wird ausgegeben, daher
1920 // muss an den STRING a$ CHR$(13)+CHR$(10) als Endkennung
1930 // für einen COMAL-DOS-String und das $-Zeichen angefügt werden.
1940 e$:=CHR$(13)+CHR$(10)+"$" // Endkennung fuer printout!!
1950 printout(a$+e$)
1960 ENDPROC print_

Einige Erläuterungen:
Die Prozedur 1700 PROC getdata(REF dat$) liest den Inhalt der vom Webserver gesetzten Umgebungsvariablen "REQUEST_METHOD" aus. Diese enthält die im HTML-Dokument gesetzte Datenübergabemethode. Sämtliche übergebenen Daten befinden sich danach im STRING dat$, der natürlich vorher ausreichend DIMensioniert worden sein muss.
dat$ könnte z.B. folgenden Inhalt haben:


AnwenderName=Neuber&Kommentartext=Hallo+alter+Meister%0D%0AKlappt+es+nun+doch%3F%3F

Der Inhalt des Textfeldes mit dem Namen AnwenderName ist Neuber und der Inhalt des Textfeldes mit dem Namen Kommentartext ist Hallo...
Das COMAL-Programm muss nun diesen Datenstrom weiter bearbeiten, d.h. in die einzelnen Bestandteile zerlegen und die Urlcodierung der Sonderzeichen rückgängig machen. Dazu dienen die Prozeduren split bzw. urldecode:


1880 PROC eingabedaten_verarbeiten
1890 split(dat$,"&",links$,rechts$)
1900 split(links$,"=",name1$,wert1$)
1910 split(rechts$,"=",name2$,wert2$)
1920 url_decode(wert1$)
1930 url_decode(wert2$)
1940 ENDPROC eingabedaten_verarbeiten

0550 PROC split(a$,trenn$,REF b$,REF c$)
0560 // teilt den String a$ am Trennzeichen trenn$ auf
0570 // und speichert die beiden Teile in b$ und c$
0580
0590 pos#:=trenn$ IN a$
0600 IF pos# THEN
0610 b$:=a$(1:pos#-1)
0620 c$:=a$(pos#+1:)
0630 ENDIF
0640 ENDPROC split
0650

0850 PROC url_decode(REF a$)
0860 // Ersetzt URL-codierte Sonderzeichen
0870 // wie z.B. %0D=CHR$(13) und +=" "
0880 LOOP
0890 asc$:=3*" "
0900 pos#:="%" IN a$
0910 EXIT WHEN pos#=0
0920 links$:=a$(1:pos#-1)
0930 asc$:=a$(pos#:pos#+2)
0940 rechts$:=a$(pos#+3:)
0950 asc$(:1:):="$"
0960 asc$:=CHR$(VAL(asc$))
0970 a$:=links$+asc$+rechts$
0980 ENDLOOP
0990
1000 LOOP
1010 pos#:="+" IN a$
1020 EXIT WHEN pos#=0
1030 a$(:pos#:):=" "
1040 ENDLOOP
1050 ENDPROC url_decode