Algorithmen & Datenstrukturen
Programmieren 1

von Prof. Jürgen Plate

Programmierübungen in "C"

Common Gateway Interface in C

Das Common Gateway Interface (CGI) beschreibt nur ein Protokoll, nicht eine bestimmte Programmiersprache. CGI-Programme lassen sich somit in jeder auf dem Server verfügbaren Programmiersprache erstellen. Häufig wird jedoch die Sprache Perl verwendet, da sie plattformübergreifend verfügbar ist und sich besonders zur Manipulation von Texten eignet - und das ist meist die Hauptanwendung für CGI-Programme. Ein CGI-Programm (wegen der Interpretersprache Perl auch oft "CGI-Script" genannt) ist nichts besonderes. Es ist ein Programm, das von der Standardeingabe (stdin) lesen und auf die Standardausgabe (stdout) schreiben kann. Wer in der Lage ist, ein solches Programm zu schreiben, kann auch CGI-Scripts erstellen.

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:

  1. Lesen der vom Clienten erzeugten Daten (entweder von der Parameterzeile des Programms oder von der Standardeingabe.
  2. Verarbeiten der Daten
  3. Schreiben einer Antwort in HTML auf die Standardausgabe.
Eine URL für ein CGI-Script sieht genau wie eine normale URL aus. Wie weiß der Server aber, daß es sich um eine ausführbare Datei und nicht um eine normale HTML-Seite handelt? Das ist ganz einfach. In der Konfigurationsdatei wurde eine Exec-Regel definiert. Sie zeigt auf das Verzeichnis, in dem sich die Scripte befinden.

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=wert3
In 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:

ZeichenCodeZeichenCode ZeichenCodeZeichenCode
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:

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:

Damit sind die wesentlichsten Merkmale eines CGI-Scriptes erklärt. Alles was man tun muß, um selber ein CGI-Script zu schreiben, ist diese Punkte umzusetzen. Wie man das am besten tun kann, soll in den folgenden Abschnitten näher erklärt werden.

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/html
gefolgt 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);
  }

Formulareingaben bearbeiten

Das Bearbeiten von Formulardaten ist der Kern einer jeden Webanwendung. Um sich das Leben leichter zu machen, kann man die wichtigsten Environment-Variablen über Makros zugänglich machen (spart das ständige getenv() im Quelltext):
/* 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.

Zum Inhaltsverzeichnis


Copyright © FH München, FB 04, Prof. Jürgen Plate
Letzte Aktualisierung: 02. May 2006