CGI mit C++ Der Standard CGI/1.1 CGI steht für Common Gateway Interface. Die aktuelle Version deses Standards lautet CGI/1.1 und dieser Standard ist mehrere Jahrzehnte alt. Die Grundidee dahinter ist, das Protokoll HTTP von Prozessen zu trennen die dem Webserver nachgelagert ist, also einen Layer zu schaffen welcher HTTP (respektive HTTPS) transparent macht. Hierzu trennt der Webserver bei einem Request den HTTP-Header-Block vom HTTP-Request-Body, stellt die Header für den nachgelagerten CGI-Prozess in Umgebungsvariablen bereit und den HTTP-Message-Body, sofern es einen Solchen gibt, in STDIN. Ob es einen HTTP-Message-Body gibt, verrät die Request-Method POST oder PUT, insbesondere jedoch der HTTP-Header Content-Length mit der Angabe zur Anzahl der gesendeten Bytes. Ein dazugehöriger HTTP-Request-Header Content-Type gibt Auskunft über den MIME-Type des HTTP-Message-Body. Diese Angabe ist wichtig, damit der nachgelagerte CGI-Prozess den Inhalt der Sendung parsen oder anderweitig verarbeiten kann. Was das Parsen betrifft, da gibt es bisher zwei standardisierte Content-Types, multipart/form-data und application/x-www-form-urlencoded. Wir kommen später auf den Letzeren zurück. Einrichten der CGI-Schnittstelle und das erste CGI-Programm Wie bisher dargelegt, spielen für einen CGI-Prozess weder Webserver noch HTTP als Protokoll eine Rolle. Das CGI-Programm bekommt den Message-Body, sofern vorhanden aus STDIN und die dazugehörigen Header in Umgebungsvariablen. Mit der Response verhält es sich ganz ähnlich, das CGI-Programm sendet sämtliche Daten einfach nur auf STDOUT. Doch der Reihe nach, als Erstes muss ein CGI-Programm dennoch mindestens einen HTTP-Response-Header senden, nämlich den zum Body passenden Content-Type gefolgt von einer Leerzeile. Diese Leerzeile ist wichtig damit der Webserver die HTTP-Header vom HTTP-Message-Body trennen kann. Doch bevor wir das erste CGI-Programm schreiben, richten wir die CGI-Schnittstelle in einer lokalen Testumgebung ein. Beispielsweise über einen Script-Alias, siehe Beispiel untenstehend für den Apache-Webserver: NameVirtualHost 192.168.178.30 ServerName rolfrost Options +ExecCGI AllowOverride All Order allow,deny Allow from all DocumentRoot c:/var/www/vhosts/rolfrost.de/httpdocs ScriptAlias /cgi-bin/ c:/var/www/vhosts/rolfrost.de/httpdocs/cgi-bin/ ScriptAlias /cgi-cpp/ c:/var/www/vhosts/rolfrost.de/Entwicklung/ Wobei es natürlich auch mehrere Script-Aliasse geben kann. Der Script-Alias konfiguriert also einmal den virtuellen URL /cgi-bin/ unter dem das CGI-Programm über den Browser erreichbar ist und den lokalen Pfad in dem es sich befindet. Erwähnt sei noch, daß ein CGI-Programm selbstverständlich ausführbar sein muß, was nach dem Compilieren auch der Fall ist. Erstellen wir also unser erstes CGI-Programm in Cplusplus, speichern es ab und kompilieren es zu einer a.out oder a.exe (Win32): #include int main(){ cout << "Content-Type: text/plain; Charset=UTF-8\n\n"; cout << "Hallo Welt!"; return 0; } Aufgerufen wird es im Browser über http://localhost/cgi-bin/a.exe und idealerweise erscheint auch die gewünschte Ausgabe. Daten aus STDIN oder QUERY_STRING Wie bereits angemerkt, sofern es einen Request-Header Content-Length gibt, stehen für das CGI-Programm die die gesendeten Daten in STDIN bereit. Gewöhnlich wird dafür die Request-Methode POST oder PUT propagiert. Darüber hinaus kann es Daten geben die an den URL angehängt sind, Daten im sogenannten QUERY_STRING. Einen QUERY_STRING kann es bei jeder Request-Methode geben, ist also nicht auf ein GET oder HEAD-Request beschränkt. Der Enctype (Content-Type) application/x-www-form-urlencoded definiert die Art und Weise des Encodings dieser Daten, sowohl im QUERY_STRING als auch in HTTP-Message-Body. Im Grunde genommen sind die Daten Schlüssel-Werte Paare nach folgendem Schema: Name=Value&Name=Value&Vorname=Klaus+Dieter&Farbe=GrBCn usw. Schlüsselnamen können also mehrfach vorkommen, Leerzeichen sind durch ein + ersetzt und die Bytes von UTF-8-Zeichen sind Prozentkodiert, einem Prozentzeichen folgt die hexadezimale Kodierung der entsprechenden Bytewertigkeit (ü: C3 BC). Mit diesem Wissen ist es nicht weiter schwierig einen Parser zu entwickeln, wobei es sicher auch schon fertige Lösungen gibt. HTML und Encoding von Formulardaten Zum besseren Verständnis der Datenübertragung erstellen wir nun eine HTML-Seite mit einem Formular, Dateiinhalt untenstehend: Ein Formular
und speichern die Datei mit dem Namen form.html und der Kodierung UTF-8 im Wurzelverzeichnis des Webservers ab. Ein Aufruf im Browser http://localhost/form.html sollte das Formular zeigen. Wenn wir jetzt auf den Sende-Button klicken, werden die Daten der Eingabefelder URL-Encoded als QUERY_STRING angehängt: vname=Fritz+Willi&name=MBCller&key=1 Die Request-Methode ist GET und das ist auch die Default-Methode zum Senden eines Formulars wenn nicht weiter angegeben. Das können wir ändern indem wir notieren:
Nach dem Reload der Seit erfolgt dann das Senden der Eingaben via POST wobei damit auch kein QUERY_STRING angehängt wird. Der Vollständigkeit halber notieren wir noch den Enctype, der auch per Default gesetzt wurde: Formularverarbeitung im CGI-Programm Bis hierher haben wir nur das Encoding und das Senden der Formulardaten via POST oder GET betrachtet. Betrachten wir nun, wie die Daten am Server bzw. in unserem CGI-Programm ankommen. Genau genommen werden die Daten bei einem Submit immer gesendet, per Default an den URL selbst welcher die HTML-Seite mit dem Formular ausgeliefert hat. Fügen wir daher unserem Formular ein weiteres Attribut hinzu: Und wenn die main bis dahin nicht geändert wurde, sehen wir "Hallo Welt!" als Response. Zum sichtbar machen der gesendeten Daten ist jetzt die Request-Methode entscheidend, bei einem POST müssen wir die Daten aus STDIN lesen, bei einem GET hingegen finden wir die Daten in der Umgebungsvariablen QUERY_STRING. Ändern wir nun unsere main wie folgt: using namespace std; int man(){ cout << "Content-Type: text/plain \n\n"; string input = getenv("QUERY_STRING") ? getenv("QUERY_STRING") : ""; string method = getenv("REQUEST_METHOD"); // die REQUEST_METHOD gibt es immer if( method.compare("GET") == 0 ){ cout << input; } else{ unsigned int contentlength = getenv("CONTENT_LENGTH") == NULL ? 0 : std::stoi(getenv("CONTENT_LENGTH"), {}, 10); input.resize(contentlength); cin.read(&input[0], contentlength); } return 0; } Anmerkung Bisher haben wir keine Fehlerbetrachtung eingebaut. Tatsächlich kann stoi() eine Exception werfen wenn ein fehlerhaftes URL-Encoding vorliegt (invalid_arguments) oder eine Content-Length jenseits von Gut und Böse gesendet wurde (out_of_range). Wir sollten daher stets auf diese Dinge prüfen, sofern uns das die Library nicht abnimmt, die wir darüber hinaus ohnehin noch benötigen um die Eingaben zu dekodieren.

Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.