![]() |
Algorithmen & Datenstrukturen Programmieren 1 von Prof. Jürgen Plate |
CGI ist, wie gesagt, keine Sprache. Es ist vielmehr ein einfaches Protokoll, das der Kommunikation zwischen HTML-Formularen und einem Programm dient. Ein CGI-Script kann in jeder Sprache geschrieben sein. Diese Kurzanleitung konzentriert sich daher auch auf die Verarbeitung von HTML-Forms. Einige Details werden nicht behandelt, dafür kommt man um so schneller zum Ziel und deckt 90% der üblichen Anwendungen ab.
Ein CGI-Script führt die folgenden typischen Schritte aus:
Sofort stellt sich die Frage, "Wie kann man dem Script Anfragen mitteilen?". Dazu müssen einfach Parameter durch ein Fragezeichen getrennt an die URL angehängt werden (kann man bei Google gut studieren). Alternativ werden die Daten aus einem Formular an die Standardeingabe eines Programms geschickt. In einer Parameterzeile dürfen keine Leerzeichen stehen, weshalb sie durch ein "+"-Zeichen ersetzt werden. Sonderzeichen (Code > 127) werden durch ein Prozentzeichen (%) und den hexadezimalen Code des Zeichens ersetzt. Man kann also z. B. für ein Shop-Programm ein Bestell-Link der folgenden Form in eine Webseite einfügen:
<A HREF="http://shop.server.de/cgi-bin/shopper.pl?prodid=23481227">Bestellen</A>Genauso häufig sind Daten, die durch die Eingabe in ein Formular erzeugt werden. Wenn der Client seine Daten übermittelt (Submit-Knopf im Formular), erhält das Script alle erzeugten Daten als einen Satz von Name-Wert-Paaren. Der Name ist jeweils der, den man beim INPUT-Tag (bzw. beim SELECT- oder TEXTAREA-Tag) festgelegt hat, die Werte sind das, was der Anwender ins Formular eingetragen oder gewählt hat. Dieser Satz von Name-Wert-Paaren wird in einem einzigen langen String übermittelt, den das CGI-Programm auflösen muß. Das ist nicht sehr kompliziert, und es gibt viele fertige Routinen dazu. Der Aufbau des Strings ist recht einfach:
name1=wert1&name2=wert2&name3=wert3
Man muß den String also einfach beim &-Zeichen
zerlegen. Dann erhält man die Paare
name1=wert1 name2=wert2 name3=wert3In den entstandenen Häppchen muß man nun noch
Das kommt daher, das der übermittelte String in der URL des CGI-Programms codiert ist. Bei einer Form der Übermittlung zum Server wird er durch "?" getrennt an die URL angehängt, was Sie beispielsweise beim Bedienen einer Suchmaschine in der URL-Zeile des Browsers sehen kann. Der Anwender muß aber nach wie vor die Möglichkeit haben, &-Zeichen und Gleichheitszeichen zu übergeben - deshalb die Codierung durch "%xx". Die folgende Tabelle zeigt die Zeichencodierung:
| Zeichen | Code | Zeichen | Code | Zeichen | Code | Zeichen | Code |
|---|---|---|---|---|---|---|---|
| Leerz. | + | ! | %21 | " | %22 | # | %23 |
| $ | %24 | % | %25 | & | %26 | ' | %27 |
| ( | %28 | ) | %29 | + | %2B | , | %2C |
| / | %2F | : | %3A | ; | %3B | < | %3C |
| = | %3D | > | %3E | ? | %3F | [ | %5B |
| \ | %5C | ] | %5D | ^ | %5E | " | %60 |
| { | %7B | | | %7C | } | %7D | ~ | %7E |
| ° | %A7 | Ä | %C4 | Ö | %D6 | Ü | %DC |
| ß | %DF | ä | %E4 | ö | %F6 | ü | %FC |
Von wo das CGI-Programm den Formularinhalt erhält, hängt von der Methode ab, mit der die HTTP-Form übermittelt wurde:
http://.../cgi-bin/testcgi.pl?Text=Hallo+dies+ist+ein+Test&Zeichen=%25Diese URL wird beim Absenden der Anfrage auch durch den Browser angezeigt. Die Nachteile der GET-Methode sind leicht ersichtlich:
All das geschieht hinter den Kulissen. Für den CGI-Programmierer funktionieren GET und POST fast gleich und sind gleich einfach zu benutzen. Der Vorteil von POST ist, daß man beliebig viele Daten übertragen kann. Der Vorteil von GET ist, daß alle Daten in eine URL gebastelt sind - man kann auf sie also verweisen oder sie in die Bookmarks aufnehmen.
Hier sind noch einmal die Schritte aufgeführt, die ein CGI-Script normalerweise durchlebt, wenn es vom Benutzer aufgerufen wird:
Der Browser sendet die Anfrage an ein CGI-Programm genauso ab, wie die Anforderung eines HTML-Dokuments und er erwartet natürlich auch, daß der Server mit entsprechenden Daten antwortet. Bei CGI wird jedoch keine HTML-Seite gesendet, sondern das Programm erzeugt die Daten dynamisch. Als erstes gibt es die Zeile
Content-Type: text/htmlgefolgt von einer Leerzeile auf die Standardausgabe aus. Nun läßt es eine normale HTML-Antwort folgen, die es ebenfalls auf die Standardausgabe ausgibt. Wenn das Script beendet ist, sieht der Anwender die so erzeugte Seite. Das Script liegt in einem speziellen Verzeichnis auf dem Server (meist "cgi-bin"). Der Content-Type kann natürlich variieren, es sind alle gebräuchlichen und vom Browser akzepierten MIME-Typen erlaubt. Versuchen wir es mal mit einem einfachen Beispiel.
Das folgende Programm liefern jeweils nur ein "Hello World" als HTML-Seite.
/************************************************/
/** hello.c -- simple "hello, world" fuer cgi **/
/************************************************/
#include <stdio.h>
int main(void)
{
/** CGI response header **/
printf("Content-type: text/html\n\n");
/** HTML response page **/
printf("<html>\n");
printf("<head><title>CGI Output</title></head>\n");
printf("<body>\n");
printf("<h1>Hello, world.</h1>\n");
printf("</body>\n");
printf("</html>\n");
return(0);
}
/* CGI Environment Variablen */
#define SERVER_NAME getenv("SERVER_NAME")
#define SERVER_PROTOCOL getenv("SERVER_PROTOCOL")
#define SERVER_PORT getenv("SERVER_PORT")
#define PATH_INFO getenv("PATH_INFO")
#define PATH_TRANSLATED getenv("PATH_TRANSLATED")
#define SCRIPT_NAME getenv("SCRIPT_NAME")
#define REMOTE_HOST getenv("REMOTE_HOST")
#define REMOTE_ADDR getenv("REMOTE_ADDR")
#define CONTENT_TYPE getenv("CONTENT_TYPE")
#define REQUEST_METHOD getenv("REQUEST_METHOD")
#define CONTENT_LENGTH getenv("CONTENT_LENGTH")
#define QUERY_STRING getenv("QUERY_STRING")
Als ersten Versuch kann man diese per Script ausgeben:
#include <stdio.h>
#include <stlib.h>
#include <string.h>
/* DEFINEs wie oben */
/* Funktionsprototypen */
void PrintCGIVars(void);
int main(void)
{
/** CGI response header **/
printf("Content-type: text/html\n\n");
/** HTML response page **/
printf("<html>\n");
printf("<head><title>CGI Output</title></head>\n");
printf("<body>\n");
PrintCGIVars();
printf("</body>\n");
printf("</html>\n");
return(0);
}
void PrintCGIVars(void)
/* Einige Umgebungsvariablen ausgeben */
{
printf("SERVER_NAME = %s<br>\n",SERVER_NAME);
printf("SERVER_PROTOCOL = %s<br>\n",SERVER_PROTOCOL);
printf("SERVER_PORT = %s<br>\n",SERVER_PORT);
printf("REQUEST_METHOD = %s<br>\n",REQUEST_METHOD);
printf("PATH_INFO = %s<br>\n",PATH_INFO);
printf("PATH_TRANSLATED = %s<br>\n",PATH_TRANSLATED);
printf("SCRIPT_NAME = %s<br>\n",SCRIPT_NAME);
printf("QUERY_STRING = %s<br>\n",QUERY_STRING);
printf("REMOTE_HOST = %s<br>\n",REMOTE_HOST);
printf("REMOTE_ADDR = %s<br>\n",REMOTE_ADDR);
printf("CONTENT_TYPE = %s<br>\n",CONTENT_TYPE);
printf("CONTENT_LENGTH = %s<br>\n",CONTENT_LENGTH);
}
Kern jedes CGI-Scripts ist das Bearbeiten der Formular-Eingaben.
Die folgende Funktion CGIReadInput erledigt dies sowohl für POST
als auch für GET. Die Wertepaare (Name, Wert) aus dem Formular
werden in einer linearen Liste gespeichert, auf die dann in
anderen Programmteilen zugegriffen werden kann. Dazu wird im
folgende Struktur dfiniert:
struct LinListe
{
char *name;
char *value;
struct LinListe *next;
};
Ausserdem braucht die Funktion noch zwei Hilfsroutinen zum Decodieren
der Formulardaten. x2c() wandelt eine zweistellige Hexadezimalzahl
in das entsprechende ASCII-Zeichen um und unescape() verwendet
x2c(), um einen ganzen String von den Ersatzdarstellungen der Form
"%XX" zu befreien und in einen reinen ASCII-String umzuwandeln:
char x2c(char hex[])
{
/* Hexadezimal zu Character-Umwandlung */
char digit;
if (hex[0] >= 'A') digit = ((hex[0] & 0xdf) - 'A') + 10;
else digit = (hex[0] - '0');
digit = digit * 16;
if (hex[1] >= 'A') digit = digit + ((hex[1] & 0xdf) - 'A') + 10;
else digit = digit + (hex[1] - '0');
return(digit);
/* Das ginge sogar noch einfacher:
digit = (hex[0] >= 'A' ? ((hex[0] & 0xdf) - 'A')+10 : (hex[0] - '0'));
digit = digit * 16;
digit += (hex[1] >= 'A' ? ((hex[1] & 0xdf) - 'A')+10 : (hex[1] - '0')); */
}
void unescape(char *url)
/* Diese Funktion wandelt eine URL wieder in eine Folge
von ASCII-Zeichen um */
{
int x, y;
char hex[2];
for (x=0,y=0; url[y] != '\0'; x++,y++)
{
if (url[y] == '+') /* Leerzeichen restaurieren */
url[x] = ' ';
else if(url[y] == '%') /* Hexadezimalangabe */
{
hex[0] = url[y+1];
hex[1] = url[y+2];
y = y + 2;
url[x] = x2c(hex);
}
else
url[x] = url[y]; /* 'normales' Zeichen */
}
url[x] = '\0'; /* Stringende-Zeichen anhängen */
}
Die Listenfunktionen enthalten keine Besonderheiten, eine erklärende Beschreibung
ist in fast jedem C-Buch zu finden. Für die Verarbeitung von Formulardaten sind
folgende Funktionen definiert:
struct LinListe *CGIReadInput(struct LinListe *root); struct LinListe *Enter(struct LinListe *root, char *Name, char *Value); struct LinListe *Suche(struct LinListe *root, char *SuchName); void Drucke(struct LinListe *root);CGIReadInput() nimmt die Formulareingaben entgegen (bei GET aus der Umgebungsvariablen und bei POAST von der Standardeingabe), teilt sie in die einzelnen "Name=Wert"-Paare, decodiert die Ersatzdarstellung der ASCII-Zeichen mit den oben beschriebenen Funktionen und speichert die Paare schliesslich in der linearen Liste unter Zuhilfenahme von Enter(). Die Funktion Drucke() dient eigentlich nur dem Test uund der Fehlersuche. Mittels Suche() kann man dann innerhalb des Programms nach einem bestimmten Feldnamen des Formulars suchen und erhält dessen Wert.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
/* CGI Environment Variablen */
#define SERVER_NAME getenv("SERVER_NAME")
#define SERVER_PROTOCOL getenv("SERVER_PROTOCOL")
#define SERVER_PORT getenv("SERVER_PORT")
#define PATH_INFO getenv("PATH_INFO")
#define PATH_TRANSLATED getenv("PATH_TRANSLATED")
#define SCRIPT_NAME getenv("SCRIPT_NAME")
#define REMOTE_HOST getenv("REMOTE_HOST")
#define REMOTE_ADDR getenv("REMOTE_ADDR")
#define CONTENT_TYPE getenv("CONTENT_TYPE")
#define REQUEST_METHOD getenv("REQUEST_METHOD")
#define CONTENT_LENGTH getenv("CONTENT_LENGTH")
#define QUERY_STRING getenv("QUERY_STRING")
struct LinListe
{
char *name;
char *value;
struct LinListe *next;
};
/* Funktionsprototypen */
char x2c(char hex[]);
void unescape(char *url);
struct LinListe *CGIReadInput(struct LinListe *root);
struct LinListe *Enter(struct LinListe *root, char *Name, char *Value);
void Drucke(struct LinListe *root);
struct LinListe *Suche(struct LinListe *root, char *SuchName);
struct LinListe *root = NULL;
int main(void)
{
struct LinListe *Erg;
printf("Content-Type: text/html\r\n\r\n");
printf("<HTML><HEAD><TITLE>Formular-Eingabe</TITLE></HEAD>\n");
printf("<BODY><H1>Wiki-Eingabe</H1>\n");
root = CGIReadInput(root);
Drucke(root);
Erg = Suche(root,"Autor");
if (Erg == NULL)
printf("Nicht gefunden\n");
else
printf("Suchergebnis: %s\n",Erg->value);
printf("</BODY></HTML>\n");
return EXIT_SUCCESS;
}
struct LinListe *Enter(struct LinListe *root, char *Name, char *Value)
{
struct LinListe *Neu;
Neu = (struct LinListe *) malloc(sizeof(struct LinListe));
if (Neu == NULL)
{
printf("Speicher voll, Abbruch...\n");
exit(1);
}
Neu->name = (char *) malloc(strlen(Name)+1);
Neu->value = (char *) malloc(strlen(Value)+1);
Neu->next = NULL;
strcpy(Neu->name,Name);
strcpy(Neu->value,Value);
/* Element vor root einhaengen, root wird neues Element */
Neu->next = root;
root = Neu;
return(root);
}
void Drucke(struct LinListe *root)
{
struct LinListe *Tail = root;
while (Tail != NULL)
{
printf("%s: %s\n", Tail->name, Tail->value);
Tail = Tail->next;
}
}
struct LinListe *Suche(struct LinListe *root, char *SuchName)
{
struct LinListe *Tail = root;
while (Tail != NULL)
{
if (strcmp(SuchName,Tail->name) == 0)
return(Tail);
Tail = Tail->next;
}
return(NULL);
}
struct LinListe *CGIReadInput(struct LinListe *root)
/* Bearbeiten der CGI-Daten und Aufspalten in
die Paare Name/Wert. Es werden GET- und
POST-Requests verarbeitet. */
{
int i, j, content_length, maxlen;
short NM = 1; /* Statusvariable, NM=1: Name, NM=0: Wert */
char *input, *name, *value, *filename;
/* Eingabe prüfen */
if (REQUEST_METHOD == NULL)
{
printf("Error: REQUEST_METHOD is null\n");
exit(1);
}
if (CONTENT_LENGTH == NULL)
{
return(NULL);
}
/* Benoetigte Werte holen und Speicher reservieren */
content_length = atoi(CONTENT_LENGTH);
maxlen = content_length - 1;
/* genügend Speicher fuer die Eingabe reservieren */
input = malloc(sizeof(char) * content_length + 1);
/* Name und Wert koennen maximal so lang sein wie die Eingabe */
name = malloc(sizeof(char) * content_length + 1);
value = malloc(sizeof(char) * content_length + 1);
filename = malloc(sizeof(char) * content_length + 1);
if (!strcmp(REQUEST_METHOD,"POST"))
{ /* Daten von der Standardeingabe lesen */
if (fread(input,sizeof(char),content_length,stdin) != content_length)
{
/* consistency error. */
printf("Error: input length < CONTENT_LENGTH\n");
exit(1);
}
}
else if (!strcmp(REQUEST_METHOD,"GET"))
{ /* Daten aus der Umgebungsvariablen QUERY_STRING holen */
if (QUERY_STRING == NULL)
{
printf("Error: QUERY_STRING is null\n");
exit(1);
}
strcpy(input,QUERY_STRING);
}
else
{ /* error: invalid request method */
printf("Error: REQUEST_METHOD invalid\n");
exit(1);
}
/* Daten aufsplitten */
j = 0;
for (i = 0; i < content_length; i++)
{
if (input[i] == '=')
{ /* Name zuende, nun Wert verarbeiten */
name[j] = '\0';
unescape(name);
if (i == maxlen)
{ /* Ende der Eingabe, kein Wert */
strcpy(value,"");
/* abspeichern */
root = Enter(root,name,value);
printf ("%s --> '%s'<br>\n",name, value);
}
j = 0; NM = 0;
}
else if ((input[i] == '&') || (i == maxlen))
{ /* Wertangabe zuende */
if (i == maxlen)
{ /* Ende der Eingabe, letzter Wert */
value[j] = input[i];
j++;
}
value[j] = '\0';
unescape(value);
/* abspeichern */
root = Enter(root,name,value);
printf ("%s --> '%s'<br>\n",name, value);
j = 0; NM = 1;
}
else if (NM)
{ /* Name wird gerade gelesen */
name[j] = input[i];
j++;
}
else if (!NM)
{ /* Wert wird gerade gelesen */
value[j] = input[i];
j++;
}
}
return(root);
}
Damit stehen die wichtigsten C-Grundroutinen für CGI-Anwendungen zur Verfügung.