Qu'est-ce qu'un C.G.I ?

CGI est l'acronyme de Common Gateway Interface. En pratique, le terme est utilisé pour désigner un programme, écrit dans un langage quelconque, interprété ou compilé, qui est lancé par le serveur WEB et qui a pour mission de générer du HTML qui sera renvoyé à l'utilisateur du serveur. Bien sûr, un CGI peut avoir un effet de bord quelconque. Le CGI reçoit du serveur WEB d'éventuels paramètres encodés par l'utilisateur, et d'autres relatifs au système d'information lui-même.

Un exemple typique de CGI concerne la recherche d'informations dans une base de données. Il n'est pas toujours possible ni intéressant de mettre toutes les informations sous forme HTML avec les hyper-liens. Ce peut être un travail de titan ou tout simplement impossible en pratique parce que les informations sont dynamiques. Il est souvent nécessaire d'aller puiser les informations directement là où elles se trouvent et de les mettre en forme HTML au moment où l'utilisateur demande leur visualisation.

Un autre exemple classique est l'entrée d'informations via le serveur WEB. Une page peut présenter un formulaire et permettre à l'utilisateur d'encoder des informations. Le CGI sera invoqué pour placer ces informations où elles doivent être stockées.

Comment utilise-t-on un CGI ?

Dans une page WEB, on peut définir un formulaire dont le début est signalé par le tag form. Chaque formulaire dispose d'un bouton d'envoi ou de soumission. Ce bouton indique une action à effectuer. C'est là qu'intervient le CGI: c'est lui l'action. Voici un extrait d'une page WEB qui invoque le CGI infocli.exe:

<form action="/cgi-bin/infocli.exe" method="POST">

En recevant cette requête, le serveur WEB lancera le programme exécutable spécifié et lui passera les informations du formulaire, récupérera la sortie du CGI et la transmettra au client . Un exemplaire du CGI est lancé pour chaque requête client. L'exécution du CGI ne dure que le temps de faire la requête.

Comment écrit-on un CGI ?

Un CGI est un programme comme un autre. Il n'y a pas de différence fondamentale entre un CGI et n'importe quel autre programme. La particularité se trouve peut-être dans la manière dont le serveur WEB passe les informations au CGI et récupère ce qu'il génère.

On écrit donc le CGI avec n'importe quel langage. La seule exigence est que ce langage puisse employer la méthode de communication utilisée pour échanger les informations avec le serveur WEB et bien entendu qu'il soit adapté au travail à effectuer.

C'est le langage C qui est le plus souvent utilisé. Mais on voit aussi dans le monde Unix de simples shell-scripts ou en langage Perl. Rien n'empêche d'utiliser un langage plus moderne comme Object Pascal et d'employer un environnement de développement visuel comme Delphi.

Comment communiquent le CGI et le serveur WEB ?

Il existe deux grands standards de communication entre le serveur et le CGI: la norme CGI proprement dite et la norme WINCGI.

La norme CGI est simple: le programme reçoit du serveur WEB toutes les données en entrée via l'entrée standard (stdin) et transmet ses sorties via la sortie standard (stdout). Quelques renseignements annexes sont passés par le biais de variable d'environnement.

La norme WINCGI est un détournement de la norme CGI: les mêmes informations sont passées via un fichier INI au lieu des entrées/sorties standard et des variables d'environnement. Cette adaptation était nécessaire par le passé parce Windows 3 n'avait pas d'entrée sortie standard ! Mais à l'heure actuelle, Windows 95 et Windows NT disposent du mécanisme des entrées/sorties standard et Delphi 2 peut facilement les employer. Il n'y a donc aucune raison de passer par un fichier intermédiaire. A mon avis la norme WinCGI est appelée à disparaître au profit de la norme CGI. Il est ainsi possible de porter un programme Unix sans aucune modification du code C, ou de transposer à Object Pascal (Delphi) le code C.

Où trouve-t-on les informations ?

Faire un CGI, vous en serez convaincu après la lecture de ce document, est en fait assez simple. Le principal problème pour celui qui doit en réaliser un premier est de rassembler l'information nécessaire. Voici quelques pistes:

Le document essentiel à se procurer est: "The WWW Common Gateway Interface Version 1.1". On peut le trouver sur divers sites WEB, notamment à http://www.ast.cam.ac.uk/~drtr/cgi-spec.html . Ce document de 16 pages décrit tout ce qu'il faut savoir sur la manière de communiquer entre le serveur WEB et le CGI, en entrée et en sortie. Vous trouverez pas mal d'information et liens vers d'autres sites à http://hoohoo.ncsa.uiuc.edu/docs/cgi.

Pour employer un CGI, il faut bien entendu réaliser une page WEB qui y fait référence. La syntaxe HTML pour réaliser ces pages se trouve décrite dans le document RFC1866 que l'on trouve facilement sur toutes sortes de sites qui reprennent tous les documents décrivant les standards Internet. Le principal étant : ftp://ds.internic.net/rfc.

Un bon point d'entrée pour toutes sortes de documentations à propos d'Internet est : http://www.microsoft.com/workshop/resource/specs.htm

Pour commencer :

Un programme CGI est obligatoirement un programme mode console (Menu- Projet - Options - Lieur - Générer application console; ou en tête de projet {$APPTYPE CONSOLE}) puisqu'il est nécessaire d'avoir accès aux entrées/sorties standards.

Dans les applications Delphi en mode console, on a accès aux entrées/sorties standard en utilisant les fonctions et procédures standard Read, Write, Readln et Writeln; ou en utilisant l'API Win32. A l'usage, il s'avère plus pratique d'utiliser Write ou Writeln pour les sorties et l'API Win32 pour les entrées. Mais c'est essentiellement une question de préférence du programmeur.

Le fait qu'un programme ait une interface graphique est indépendant de sa qualité de programme en mode console. Un CGI n'a pas besoin d'interface utilisateur. Personne n'est là pour voir ce qui se passe à l'écran pendant son exécution ! Cela dit, dans la programmation Delphi, il est parfois, souvent même, plus facile d'employer une interface graphique simplement pour pouvoir utiliser des composants visuels (VCL) qui effectuent un certain travail. On dispose alors une simple fenêtre qui reçoit ces composants. Fenêtre qui peut ne jamais être montrée à l'écran.

Pour entrer dans le vif du sujet, voici le code source d'une unité source Delphi qui implémente un tout petit CGI qui ne prend aucun paramètre en entrée et qui génère en sortie une page HTML avec le message "Hello from Delphi 2.0, CGI World!". Pour rédiger ce code source, il faut créer une nouvelle application et rédiger le code ci-dessous pour l'événement FormShow de l'unique fenêtre du projet.

{$APPTYPE CONSOLE}
unit cgi1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TForm1 = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormShow(Sender: TObject);
const
      FirstTime : Boolean = TRUE;
begin
      if not FirstTime then
          Exit;
      FirstTime := FALSE;

      WriteLn('Content-type: text/html');
      WriteLn;
      WriteLn('');
      WriteLn('');
      WriteLn('Hello from Delphi 2.0, CGI World!');
      WriteLn('');
      WriteLn('');
      WriteLn('

Hello from Delphi 2.0, CGI World!

'); WriteLn(''); WriteLn(''); Application.Terminate; end; end.

Il s'agit comme on le voit de l'unité qui correspond à une application ayant une seule fenêtre, laissée vide. On peut faire mieux en oubliant cette fenêtre et en générant une application console pure. C'est un rien plus difficile dans la mesure où Delphi n'offre pas de possibilité directe de générer un tel projet. Il n'empêche qu'il est facile de le faire. Voici la marche à suivre:

Menu - Fichier - Nouvelle application
Menu - Voir - Source du projet
Ajouter en première ligne {$APPTYPE CONSOLE}
Remplacer "uses Forms" par "uses Windows"
Enlever la ligne "{$R *.RES}"
Menu - Projet - Supprimer du projet - Unit1 (ne pas sauver unit1)
Menu - Fichier - Enregistrer sous - cgisimple.dpr
Supprimer "Application.Initialize; Application.Run;" entre le "begin" et le "end."
Ajouter le code pour obtenir le listing comme ci-dessous:

{$APPTYPE CONSOLE}
program cgisimple;

uses
    Windows;

begin
    WriteLn('Content-type: text/html');
    WriteLn;
    WriteLn('');
    WriteLn('');
    WriteLn('Hello from Delphi 2.0, CGI World!');
    WriteLn('');
    WriteLn('');
    WriteLn('

Hello from Delphi 2.0, CGI World!

'); WriteLn(''); WriteLn(''); end.

Pour tester ce CGI, il faut créer une page WEB qui invoque le CGI. Par exemple:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"&qt <html&qt <head&qt Test CGI

Test CGI

Bien sûr, il faut copier l'exécutable généré par Delphi dans le répertoire cgi-bin du serveur Web. Lorsque l'utilisateur verra cette page Web et appuiera sur le bouton "Envoi", le serveur Web lancera l'exécution de cgisimple.exe qui générera le HTML suivant:

Content-type: text/html



Hello from Delphi 2.0, CGI World!


Hello from Delphi 2.0, CGI World!

La première ligne générée doit être: " Content-type: text/html" pour indiquer au serveur WEB que le résultat est du texte au format HTML. Le fonctionnement interne du CGI est on ne peut plus simple: juste une série de Writeln qui écrivent le texte HTML qui constitue le résultat du CGI. L'exécutable version graphique occupe 179KB tandis que la version console pure n'occupe que 8KB. On voit immédiatement l'intérêt de réaliser des programmes non graphiques...

Avec des données en plus

Voici maintenant un CGI un peu plus élaboré. Cette fois, le CGI va utiliser les données transmises par le serveur WEB et s'en servir pour générer la page en retour. Pour faire simple, l'exemple choisi est une page toute élémentaire contenant une zone de saisie et un bouton d'envoi. L'utilisateur encode son nom dans la zone de saisie et clique sur le bouton d'envoi. Le CGI lui répond en générant une page Web qui lui dit "Hello Untel" où le Untel est évidemment remplacé par le nom qu'il a encodé.

Ce n'est pas beaucoup plus compliqué que l'exemple simple ci-dessus. Il n'y a que la récupération de l'encodage de l'utilisateur en plus. Comme je l'ai mentionné plus haut, le serveur Web transmet les données encodées par l'utilisateur à travers l'entrée standard du CGI. Traduit en API Win32, cela signifie qu'il faut utiliser ReadFile pour obtenir ces données et passer à ReadFile l'identificateur (handle) de l'entrée standard, ce que l'on obtient avec la fonction GetStdHandle(STD_INPUT_HANDLE).

Pour savoir la quantité de données à lire, il faut consulter la variable d'environnement CONTENT_LENGTH dont la valeur numérique indique le nombre de caractères envoyés par le serveur Web. Traduit en API Win32, il faut invoquer GetEnvironmentVariable pour obtenir la valeur de la variable d'environnement. Puis invoquer StrToInt pour convertir cette valeur de chaîne de caractères en nombre entier.

Une fois que les données transmises par le serveur ont été récupérée, il faut en extraire l'information utile. Le serveur Web transmet au CGI les données sous forme de couples variable=valeur. Chaque couple est séparé du précédent par le caractère '&'. Tous les espaces sont remplacés par le caractère '+'. Le '+', le '=', le '&', le '%' ainsi que tous les caractères non imprimables sont remplacés par une séquence de trois caractères: le signe '%' et deux chiffres hexadécimaux donnant le code ASCII du caractère. Sachant cela, il devient simple de balayer les données transmises par le serveur Web et de les analyser en faisant les substitutions inverses. C'est ce que fait GetValue dans le code ci-dessous pour trouver la valeur associée à une variable.

Les variables dont il est question ci-dessus sont constituées par les gadgets graphiques de la page Web: zones de saisie, cases à cocher, liste déroulante, etc. Lors de la constitution de la page, un nom est donné à chaque gadget. C'est ce nom que le serveur utilise. La valeur étant la valeur encodée par l'utilisateur ou une valeur prédéfinie, par exemple pour les cases à cocher.

Voici le code source complet:

{$APPTYPE CONSOLE}
program CgiSimpleData;

uses
    Windows, SysUtils;

function isxdigit(Ch: char) : Boolean; forward;
function xdigit(Ch: char) : Integer; forward;
function htoin(value: PChar; len: Integer) : Integer; forward;
function GetValue(Msg: String; Name: String; var Val: String): Boolean;

   forward;

procedure Cgi;
var
    Len               : Integer;
    Count             : Integer;
    Buffer            : array [0..2047] of char;
    NumberOfBytesRead : Integer;
    Nom               : String;
begin
    WriteLn('Content-type: text/html');
    WriteLn;
    WriteLn('');
    WriteLn('');
    WriteLn('Hello !');
    WriteLn('');
    WriteLn('');
    WriteLn('

'); Len := GetEnvironmentVariable('CONTENT_LENGTH', Buffer, SizeOf(Buffer)); Buffer[Len] := #0; if Len = 0 then Count := 0 else Count := StrToInt(Buffer); if Count = 0 then NumberOfBytesRead := 0 else ReadFile(GetStdHandle(STD_INPUT_HANDLE), Buffer, Count, NumberOfBytesRead, nil); Buffer[NumberOfBytesRead] := #0; GetValue(Buffer, 'Nom=', Nom); WriteLn('

Hello ', Nom, ' !



'); WriteLn('CONTENT_LENGTH=', Count, '
'); WriteLn('NumberOfBytesRead=', NumberOfBytesRead, '
'); writeln('Content: ', '''', Buffer, '''
'); WriteLn('

'); WriteLn(''); WriteLn(''); end; function isxdigit(Ch : char) : Boolean; begin Result := (ch in ['0'..'9']) or (ch in ['a'..'z']) or (ch in ['A'..'Z']); end; function xdigit(Ch : char) : Integer; begin if ch in ['0'..'9'] then Result := ord(Ch) - ord('0') else Result := (ord(Ch) and 15) + 9; end; function htoin(value : PChar; len : Integer) : Integer; var i : Integer; begin Result := 0; i := 0; while (i < len) and (Value[i] = ' ') do i := i + 1; while (i < len) and (isxDigit(Value[i])) do begin Result := Result * 16 + xdigit(Value[i]); i := i + 1; end; end; function GetValue(Msg: String; Name: String; var Val: String): Boolean; var I : Integer; NameLen : Integer; Ch : Char; begin Val := ''; NameLen := Length(Name); I := Pos(Name, Msg); if I = 0 then begin Result := FALSE; Exit; end; I := I + NameLen; while (Msg[I] <> #0) and (Msg[I] <> '&') do begin Ch := Msg[I]; if Ch = '%' then begin Ch := chr(htoin(@Msg[I + 1], 2)); I := I + 2; end else if Ch = '+' then Ch := ' '; Val := Val + Ch; Inc(I); end; Result := TRUE; end; begin Cgi; end.

Le code source est disponible à partir de la page WEB: http://www.rtfm.be/fpiette et sur le BBS OverByte 04/365.13.95.

François Piette
EMail : francois.piette@ping.be Fidonet : 2:293/2202


Retour à la page principale de En-Ligne

E-Mail WebMaster

Dernière modification de cette page : 30 octobre 1997