menu
  Home  ==>  articles  ==>  web  ==>  cgi_form   

Tutorial: Formulaire CGI - John COLIBRI.


1 - Introduction

Cet article va présenter la construction d'une page Web permettant à vos visiteurs de remplir un bon de commande.

Pour cela nous allons utiliser une page web qui envoie la commande au visiteur, et un programme CGI qui récupère sa réponse et lui retourne un accusé de réception.

Ce programme sera testé en local en utilisant Personal Web Server, puis testé chez le fournisseur d'accès.


2 - Pages statiques et dynamiques

2.1 - Pages statiques

Les premières pages web, les plus nombreuses d'ailleurs, sont "statiques":
  • l'internaute fournit l'URL de la page demandée. Par exemple: 123.45.67.89
  • le Web cherche le serveur et envoie la page demandée
Schématiquement nous avons donc
  • le client envoie l'URL:

  • le Web cherche le PC serveur à l'aide de cette adresse:

  • Sur ce serveur le programme serveur (IIS, Netscape, Apache...) cherche la page demandée (par défaut INDEX.HTML) et renvoie la page au client:

Ce type de requête est appelé statique car le Serveur ne fait que transmettre la page sans la modifier.



2.2 - Les pages Web Dynamiques

Le second type de pages HTML sont des pages "dynamique". Prenons l'exemple d'un bon de commande. Supposons que notre bon de commande contienne deux boîtes d'édition et le bouton "envoi":

Bon de commande

Veuillez remplir ce bon et cliquer "envoi"   :

Livre:
 
Quantité:
 

Envoi

Cette page Web comporte des champs à remplir, ainsi qu'un bouton que l'internaute cliquera lorqu'il aura rempli les champs.

  • le client envoie l'URL pour recevoir le bon de commande. Cette étape est donc similaire à la première étape ci-dessus

  • le serveur envoie la page contenant le formulaire de commande: ici aussi, tout se passe comme ci-dessus

  • le client remplit ce bon. Supposons qu'il choisisse 2 livres. Il clique ensuite un bouton "envoi". Son navigateur envoie alors les paramètres de la commande au serveur ("deux livres"):

  • le serveur reçoit ces paramètres. Il traite alors la commande (vérification du stock, émission de bordereau de livraison etc). Il renvoie une réponse au client, par exemple un accusé de réception ("Ok"):

Ce qui distingue donc ce type de requête est:
  • la réception par le serveur de paramètres ("2 livres")
  • le traitement réalisé sur ces paramètres au niveau du serveur (non représenté ici)
  • la construction à la volée de la page de réponse ("Ok")
Parmi les exemples simples de pages dynamiques, citons:
  • les compteurs qui indiquent combien de visiteurs on vu la page. Ce compteurs peuvent être visibles ou simplement incrémenter une valeur dans un fichier du serveur
  • les livres d'or: fournit une appréciation et le serveur retourne le livre mis à jour
  • les bons de commande: toute la gamme entre la page simple et la commande avec un caddy et payement en ligne
  • les réservations (voyages, hôtels, stages)
  • les recherches textuelles dans un site (dans notre site, SiteSearchor, qui est accessible par Menu | Chercher) ou sur le web
Nous nous intéressons ici uniquement à la réception par le serveur de paramètres qui sont utilisés pour construire une page réponse adaptée aux paramètres reçus du client.

Pour cela, il faut:

  • que le client reçoive une page HTML comportant une balise <FORM>. Ce type de page est appelé un "formulaire" HTML, et contient en général des contrôles de saisie (boîtes d'édition, boutons radio, mémo etc)
  • que le serveur traite les paramètres correspondant aux paramètres envoyés par le client

3 - Le formulaire HTML

3.1 - Définition

Le formulaire HTML est page HTML normale (avec des titres, des images, des liens...) comportant en plus les éléments suivants:
  • une balise <FORM>...</FORM> qui permet de préciser justement qu'il s'agit d'un formulaire et non pas d'une page ordinaire. C'est la balise <FORM> qui contient l'adresse du serveur
  • un ou plusieurs contrôle permettant au client de fournir des données
  • un contrôle particulier, "submit", qui provoque l'envoi vers le serveur des paramètres


3.2 - Un exemple simple

Nous utiliserons comme exemple le formulaire présenté ci-dessus. Le code HTML corresponant est le suivant:

<H2>Bon de commande</H2>
Veuillez remplir ce bon et cliquer "envoi":
<P>
<FORM METHOD="POST" ACTION="http://www.jcolibri.com/scripts/jcommande.exe">
<P>
livre:       <INPUT TYPE="edit" NAME="livre"><BR>
quantité: <INPUT TYPE="edit" NAME="quantite">
<P>
<INPUT TYPE="submit" VALUE="Envoi"><BR>
</FORM><BR>

Voyons ce code HTML plus en détail.



3.3 - La balise <FORM>

La balise <FORM> permet de délimiter les contrôles du formulaire.

Mais elle contient surtout le type de requête et l'adresse du serveur et le programme que le serveur doit exécuter lorsqu'il recevra la requête.

Il y a deux type de requêtes

  • GET: l'en-tête de la requête contient la question
  • POST: le texte de la requête se trouve après l'en-tête
Dans notre cas, c'est POST qui nous intéresse, car la question peut être arbitrairement longue et complexe. C'est l'attribut METHOD qui permet de spécifier le type de requête.

En ce qui concerne l'adresse et le programme à exécuter:

  • le serveur a pour URL http://www.jcolibri.com
  • le programme à exécuter est jcommande.exe
  • ce programme se trouve dans le répertoire local scripts/
L'URL, le chemin et l'exécutable sont désignés par l'attribut ACTION.

La balise <FORM> est alors la suivante:

<P>
<FORM METHOD="POST" ACTION="http://www.jcolibri.com/scripts/jcommande.exe"><BR>



3.4 - Les boîtes d'édition

Pour placer une boîte d'édition sur le formulaire, nous ajoutons simplement une balise INPUT dont l'attribut TYPE a la valeur "edit". De plus, pour que le serveur puisse identifier les différentes boîtes d'édition, nous ajoutons un identificateur par l'attribut NAME.

Voici le code HTML qui présente la boîte d'édition:

<P>
<INPUT TYPE="edit" NAME="livre"><BR>

Notez que le label que le formulaire présente avant la boîte d'édition ne fait pas partie du contrôle. Ce label est affiché comme n'importe quel texte dans une page HTML



3.5 - Le bouton d'envoi

Une fois que le client a rempli son formulaire, il doit envoyer ce qu'il a tapé vers le serveur. Pour cela le formulaire comporte un bouton "envoi".

Ce contrôle:

  • est spécifié par une balise <INPUT>
  • a l'attribut TYPE ayant la valeur "submit"
  • a l'attribut VALUE ayant la valeur "envoi" pour afficher le titre du bouton
Voici le code HTML pour ce bouton:

<P>
<INPUT TYPE="submit" VALUE="Envoi"><BR>

Notez que Submit est optionnel: la frappe de Entrée dans une boîte d'édition suffit pour envoyer la requête au serveur (ce que nous utilisons pour SiteSearchor).



3.6 - L'envoi vers le serveur

Supposons, à titre d'exemple, que l'utilisateur tape:

Bon de commande

Veuillez remplir ce bon et cliquer "envoi"   :

Livre:
Delphi (nouveau)
Quantité:
2

Envoi

Lorsque le client clique "envoi" ou frappe "entrée", le navigateur (Internet Explorer, Netscape, Mosaic...) expédie vers le serveur (Internet Information Server, Netscape Server, Apache...) un message HTTP contenant ce que l'utilisateur a tapé.

Pour savoir ce qui est envoyé, le plus simple est de se mettre à la place du serveur et d'afficher ce que le serveur reçoit. Nous avons utilisé pour cela un petit programme à base de Sockets (non fourni ici) qui nous fournit le résultat suivant:

POST /scripts/jcommande.exe HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, */*
Accept-Language: fr
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
Host: pol-500
Content-Length: 37
Connection: Keep-Alive

livre=delphi+%28nouveau%29&quantité=2

Ce message HTTP contient deux parties:

  • l'en tête
  • le texte du message
L'en-tête contient des parties permettant d'identifier le message et ce qui est demandé. En particulier
  • POST précise
    • qu'il s'agit d'un message POST (et pas GET)
    • que le programme qui traitera le message est scripts/jcommande.exe
    • que le protocole est HTTP/1.1
  • CONTENT-LENGTH indique la taille du texte envoyé
Le texte quant à lui a l'allure suivante:

livre=delphi+%28nouveau%29&quantité=2

Ce texte:

  • contient bien ce que l'utilisateur a saisi: "delphi", "nouveau" et "2"
  • les deux parties sont séparées par "&"
  • chaque partie comporte un couple mot_clé = valeur, où
    • mot_clé est l'identificateur que nous avons placé dans l'attribut NAME de la balise INPUT de la boîte d'édition
    • valeur est le texte tapé par l'utilisateur
    De plus certaines ponctuations utilisées pour l'analyse de l'en-tête HTTP sont codées. Ainsi:
    • l'espace est transformé en +
    • les symboles (, ), etc sont codé %_valeur_hexadécimale. Par exemple:
      • ( devient %28 car $28 (qui est égal à 40 en décimal) est le code ASCII de (
      • ) devient %29
      • etc

4 - Le traitement au niveau du serveur

4.1 - Le programme de traitement

Notre utilisateur a reçu son formulaire, il a rempli les cases, et il a expédié sa requête en cliquant "envoi".

Le serveur va donc recevoir la requête HTTP.

Il est évident que le programme générique du serveur ne sait pas effectuer des traitements sur-mesure qui dépendent des contrôles que nous avons placés dans le formulaire, des identifiants de ces contrôles et des réponses du client.

Il faillait donc trouver un moyen de fournir les paramètres spécifiques à la requête à un programme qui les traite, et tout cela dans le cadre du protocole HTTP.

La solution fut le protocole CGI: Common Gateway Interface.

Par cette mécanique:

  • le Serveur communique les paramètres à un programme CGI
  • ce programme effectue les traitements désirés et retourne une réponse au Serveur
  • le Serveur renvoie la réponse au client
Le programme CGI peut être codé de plusieurs façons:
  • en utilisant des langages de script, comme Perl, Python, Javascript ou autre VB script
  • avec des langages de programmation traditionnels: Delphi, C, assembleur
  • ou encore au moyen de librairies objet pyramidées sur les langages traditionnels. Pour Delphi, WebBroker, WebSnap
Apparemment, les programmes les plus nombreux sont en Perl, qui est un langage interprété qui a été construit spécialement pour écrire des programmes CGI. Comme il s'agit d'un langage interprété, on nomme ces programmes des "scripts". Et par extension, les autres programmes CGI sont appelés des "scripts CGI", bien qu'ils puissent être du binaire usuel.

En ce qui nous concernent, nous allons présenter un script CGI écrit en Delphi, qui est un binaire Delphi tout à fait usuel. Aucune extension de Pascal ni de Delphi n'ont été nécessaires. En vérité, vous saviez déjà écrire des scripts CGI, mais personne ne vous l'avait encore dit !



4.2 - Les types de programme CGI en Delphi

Ayant opté pour Delphi pour écrire notre CGI, nous avons plusieurs possibilités:
  • soit écrire du CGI "de base" pour lequel les communication Serveur - Cgi se font par READ et WRITE
  • soit utiliser une variante, appelée WinCGI, où les communications se font par l'intermédiaire d'un fichier .INI
  • nous pouvons aussi utiliser des DLL, appelées Isapi (IIS), NsApi (Netscape) ou DSO (Apache)
Les deux premiers types de CGI sont des .EXE traditionnels:
  • le Serveur charge le CGI en mémoire
  • le contrôle est donné au CGI qui réalise son traitement
  • arrivé au END. le CGI est retiré de la mémoire
Les autres types de CGI sont des DLL, ce qui évite les chargements / déchargements continuels, mais cause aussi quelques problèmes du fait que certains serveurs tels que NT refusaient de les supprimer de la mémoire sans rebooter le Serveur.

Pour notre part nous présenterons les CGI "de base", qui sont les plus simples.



4.3 - Le fonctionnement du CGI

Le Serveur va donc lancer le CGI et lui communiquer les paramètres de la requête du Client.

Pour les CGI de base, le transfert des paramètres se fait par les fichiers l'entrée et sortie standard des programmes DOS ou Pascal: c'est READ ou READLN qui lisent les données du serveur, et WRITE ou WRITELN qui retourne la réponse au Serveur.

En plus des paramètres, le CGI peut être intéressé par d'autres informations concernant la requête: qui l'a émise, par quel intermédiaire etc.

Pour fournir ces informations au CGI, le Serveur utilise des variables d'environnement DOS. Ce sont des "variables systèmes" que nous pouvons lire ou écrire en utilisant des API telles que GetEnvironmentValue.

Le fonctionnement est donc le suivant:

  • le Serveur reçoit la requête du Client:

  • le Serveur crée et place dans des variables d'environnements les attributs de la requête

  • le Serveur lance le CGI

Voilà. C'est à partir de là que nous récupérons la main en Delphi. Enfin !

Notre programme CGI devra alors:

  • récupérer les attributs de la requête dans les variables d'environnement (l'expéditeur, la méthode etc)
  • récupérer les paramètres de la requête (quels livres, en quelle quantité...)
  • effectuer les traitements prévus par la spécification (vérifier le stock, émettre la facture...)
  • construire la réponse et la transmettre au Serveur


4.4 - Un CGI élémentaire

Commençons par écrire le CGI le plus simple possible: il se contentera simplement de remercier le Client. Voici le texte de ce CGI:

// 001 p_cgi_tutorial
// 03 jan 2001

(*$r+*)
(*$APPTYPE CONSOLE *)

program jcommande;
  var l_outputText;
  begin
    Assign(l_output'');
    Rewrite(l_output);

    writeln(l_output'Content-type: text/html');
    writeln(l_output);

    writeln(l_output'<HTML>');
    writeln(l_output'  <BODY>');
    writeln(l_output'    Merci et bonne journée');
    writeln(l_output'  </BODY>');
    writeln(l_output'</HTML>');

    Close(l_output);
  end.



4.5 - Test du CGI

Si tout se passe bien, nous pouvons directement coder le CGI et le télécharger sur le PC du Serveur, chez notre hébergeur.

Les choses étant ce qu'elles sont et le monde ce que nous savons, il vaut mieux prévoir quelques vérifications.

Deux difficultés se présentent:

  • le CGI est un exécutable DOS: nous n'avons aucune fenêtre Windows pour afficher des messages de test. De plus le Serveur est censé fonctionner en aveugle, rangé quelque part dans un placard sous l'escalier.
  • le Serveur est sur le PC de notre hébergeur, pas sur notre PC de développement. Nous n'avons donc pas toujours accès aux logs, à PerfMon ou autre système de monitoring.
Pour résoudre le premier problème, la seule solution est l'utilisation de fichiers ASCII contenant les messages de mise au point, de suivi ou d'erreur. Bref, un log.

Pour résoudre le second problème, plusieurs solutions:

  • construire un Serveur dédié, qui aura la bonne grâce d'afficher ce qui se passe. Nous avions adopté cette version il y a plusieurs années, passant par plusieurs générations de serveurs plus ou moins musclés. Le problème est que nous arriverons toujours à faire cohabiter harmonieusement notre serveur avec nos CGI, mais cela ne signifie pas que IIS, Netscape ou Apache feront de même.
  • d'autres ont tenté la même approche. Une première version, jamais fournie en source, fut BOB42 de Bob Swart. Pas de source, donc sans espoir de notre point de vue. Du même tonneau fut Omni qui eut son heure de gloire, avec la même absence de source, donc le même sort pour nous
  • la version 6 de Delphi offre un serveur intégré permettant le test. Ici aussi pas de source. De plus nous sommes en Delphi 5 pour le moment.
  • finalement la seule solution est d'essayer un serveur réel: nous pouvons monter IIS, Netscape ou Apache sur une machine disponible localement et effectuer les test ainsi. L'inconvénient est la puissance nécessaire. Ainsi IIS nécessite l'utilisation de NT. Sans aller jusque là, depuis Windows 98 Microsoft fournit PWS: Personal Web Server. Ce logiciel est fourni justement pour permettre de tester le fonctionnement en local de pages Web. Eh bien c'est exactement ce qu'il nous faut, et que nous allons utiliser.


4.6 - Utilisation de PWS

Pour utiliser PWS, il faut
  • installer PWS
  • créer les répertoires nécessaires
  • adapter le formulaire pour qu'il envoie la requête vers PWS et non pas vers le serveur distant
Or donc:
  • PWS se trouve sur le CD Windows 98, et il suffit de cliquer sur SETUP pour l'installer.
  • Les répertoires qui sont utilisé dans cet exemple sont les suivants:

    C:
      Inetpub
        scripts
          p_cgi_tutorial.exe
          orders
            cgi_order_log.txt

    Notre machine a pour nom "pol-500" et se trouve à l'adresse IP 192.0.0.3 (ou 127.0.0.1)

  • pour que le formulaire provoque l'appel du CGI local, nous devons simplement modifier la balise <FORM>:
    • en remplaçant l'URL distante "www.jcolibri.com" par "pol-500"
    • en remplaçant le nom du CGI jcommande.exe par le CGI placé en local: p_cgi_tutorial.exe
    La balise devient ainsi:

    <P>
    <FORM METHOD="POST" ACTION="http://pol-500/scripts/p_cgi_tutorial.exe"><BR>

Nous aurons donc deux jeux de programmes:
  • le CGI pour tester en local
  • le CGI qui sera installé chez notre hébergeur
Pour éviter des dupplications sans fin, nous avons choisi de séparer le texte du projet en deux:
  • une partie commune aux deux versions, placée dans une UNIT et qui effectue le travail réel
  • deux .DPR qui appellent la procédure de traitement de l'unité, et qui sont paramétrés (au niveau des chemins de sortie: Project | Options) pour placer le fichier .EXE soit dans "c:\inetpub\scripts" soit dans un répertoire qui sera utilisé pour copier le CGI vers l'hébergeur distant.
Voyons à présent la programmation du CGI local.



4.7 - Le log

Pour arriver à tester notre CGI, nous utiliserons donc un fichier ASCII que nous pourrons lire en local, ou télécharger du serveur distant.

Voici l'interface de ce log:

    type c_simple_logclass
                         public
                           m_fileFile;
                           m_whoString;
                           m_countInteger;
                           m_convert_non_printableBoolean;

                           constructor create_simple_log(p_whop_file_nameString); Virtual;
                           procedure write_line(p_textString);
                           destructor DestroyOverride;
                       end;

Et:

  • m_file: le fichier
  • m_who: un identifiant du CGI (par exemple "commande") qui permet d'utiliser le même log pour plusieurs CGI
  • m_count: un numéro pour mieux visualiser les messages d'une même session
  • m_convert_non_printable: demande la conversion des caractères en dehors de 32..127
Quant aux méthodes:
  • create_simple_log: crée le fichier ASCII et fournit l'identifiant
  • write_line: ouvre le fichier, ajoute le texte à la fin, et ferme le fichier


4.8 - Le CGI avec son Log

Voici le projet local:

(*$r+*)
(*$APPTYPE CONSOLE *)

program p_cgi_tutorial;
  uses u_cgi_tutorial;

  const k_write_path'orders\';

        k_log_name'cgi_order_log.txt';
        // k_log_name= '';

  begin
    handle_order(k_write_pathk_log_name);
  end.

L'unité u_cgi_tutorial, que nous allons développer, contient simplement le code pour ouvrir le log et envoyer notre réponse élémentaire:

unit u_cgi_tutorial;
  Interface

    procedure handle_order(p_write_pathp_log_nameString);

  Implementation
    uses WindowsSysUtils
        , u_c_simple_log;

    var g_c_simple_logc_simple_log;

    procedure open_log(p_log_nameString);
      begin
        if p_log_name<> ''
          then g_c_simple_log:= c_simple_log.create_simple_log('tutorial'p_log_name);
      end// open_log

    procedure write_to_log(p_textString);
      begin
        if Assigned(g_c_simple_log)
          then g_c_simple_log.write_line(p_text);
      end// write_to_log

    procedure handle_order(p_write_pathp_log_nameString);

      procedure send_simple_ok_answer;
          // -- build a simple answer
        var l_outputText;
        begin
          write_to_log('send_ok_answer');
          Assign(l_outputk_output);
          Rewrite(l_output);

          writeln(l_outputk_content_type);
          writeln(l_output);

          writeln(l_output'<HTML>');
          writeln(l_output'  <BODY>');
          writeln(l_output'    Merci et bonne journée');
          writeln(l_output'  </BODY>');
          writeln(l_output'</HTML>');
          Close(l_output);

          write_to_log(' sent_ok_answer');
        end// send_simple_ok_answer

      begin // handle_order
        open_log('c:\inetpub\'p_write_pathp_log_name);
        
        send_simple_ok_answer;
      end// handle_order

    end

Et voici le log provoqué par la compilation et l'exécution du projet:


03/01/02 08:49:16 commandes 0
03/01/02 08:49:16 commandes 1
03/01/02 08:49:16 commandes 2
03/01/02 08:49:16 commandes 3 =========
03/01/02 08:49:16 commandes 4 send_ok_answer
03/01/02 08:49:16 commandes 5     sent_ok_answer



4.9 - Les variables d'environnement

Nous avons vu que le Serveur va placer dans des variables d'environnement DOS les attributs de la requête.

La spécification CGI présente les variables que tous les serveurs sont censés créer. Par exemple:

  • REQUEST_METHOD la méthode (GET ou POST) utilisée par la requête
  • CONTENT_LENGTH: le nombre de caractères utilisés dans le cas d'une requête POST
Mais d'autres variables ont été ajoutées à travers les ages, plus ou moins acceptées et reconnues par les différents serveurs.

Le mieux est alors d'examiner les variables existant en dehors de toute mécanique CGI, puis regarder celles existant après réceptions par IIS d'une requête CGI: par différence nous aurons les variables propres aux CGI.

Pour lire TOUTES les variables d'environnement, nous utilisons GetEnvironmentStrings, qui renvoie un tableau de pointeurs de pChar terminé par suffisamment de 0.

Nous avons écrit une procédure f_get_all_environment qui retourne la liste de toutes ces variables. Cette fonction est placée dans l'unité u_cgi_helpers dont nous présenterons l'interface ci-dessous.

Si nous appelons cette fonction, nous obtenons, dans notre cas, la liste suivante (triée et épurée):

TMP=C:\WINDOWS\TEMP
TEMP=C:\WINDOWS\TEMP
PROMPT=$p$g
winbootdir=C:\WINDOWS
COMSPEC=C:\WINDOWS\COMMAND.COM
PATH=C:\PROGRAMS Files\Borland\Delphi5\Bin;C:\WINDOWS;C:\WINDOWS\COMMAND;
CMDLINE=WIN
windir=C:\WINDOWS
BLASTER=A220 I5 D1 T4

Soit. Pour obtenir les variables générés par le Serveur:

  • nous ajoutons à notre unité u_cgi_tutorial la ligne:

            write_to_log('ENVIRONMENT: 'f_get_all_environment);

  • nous chargeons dans le navigateur IE la page de commande (celle présentée ci-dessus)
  • nous cliquons sur "submit" ("envoi")
Voici les valeurs dans ce nouveau log ne figurant pas dans le précédent:

CONTENT_LENGTH=0
CONTENT_TYPE=application/x-www-form-urlencoded
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, */*
HTTP_ACCEPT_LANGUAGE=fr
HTTP_CONNECTION=Keep-Alive
HTTP_HOST=pol-500
HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
HTTP_CONTENT_LENGTH=0
HTTP_CONTENT_TYPE=application/x-www-form-urlencoded
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTPS=off
INSTANCE_ID=1
LOCAL_ADDR=192.0.0.3
PATH_TRANSLATED=C:\Inetpub\wwwroot
REMOTE_ADDR=192.0.0.3
REMOTE_HOST=192.0.0.3
REQUEST_METHOD=POST
SCRIPT_NAME=/scripts/p_cgi_tutorial.exe
SERVER_NAME=pol-500
SERVER_PORT=80
SERVER_PORT_SECURE=0
SERVER_PROTOCOL=HTTP/1.1
SERVER_SOFTWARE=Microsoft-IIS/4.0

Dans ce joyeux fouillis nous pouvons distinguer plusieurs catégories de données:

  • celles provenant directement de l'en-tête HTTP:
    • REQUEST_METHOD=POST
    • SCRIPT_NAME=/scripts/p_cgi_tutorial.exe
    • HTTP_ACCEPT=image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, */*
    • HTTP_ACCEPT_LANGUAGE=fr
    • CONTENT_TYPE=application/x-www-form-urlencoded
    • HTTP_CONTENT_TYPE=application/x-www-form-urlencoded
    • HTTP_ACCEPT_ENCODING=gzip, deflate
    • HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
    • HTTP_HOST=pol-500
    • HTTP_CONTENT_LENGTH=37
    • HTTP_CONNECTION=Keep-Alive
    • CONTENT_LENGTH=37
    • HTTPS=off
  • celles concernant le Serveur
    • INSTANCE_ID=1
    • LOCAL_ADDR=192.0.0.3
    • PATH_TRANSLATED=C:\Inetpub\wwwroot
    • SERVER_SOFTWARE=Microsoft-IIS/4.0
    • SERVER_PORT=80
    • SERVER_PORT_SECURE=0
    • SERVER_PROTOCOL=HTTP/1.1
    • SERVER_NAME=pol-500
    • GATEWAY_INTERFACE=CGI/1.1
  • ainsi que le Client
    • REMOTE_ADDR=192.0.0.3
    • REMOTE_HOST=192.0.0.3
Pour notre application, seul CONTENT_LENGTH est utile. Il nous faudra en effet lire le message (le nombre de livres commandés), et pour cela savoir le nombre d'octets à récupérer par READ.

Pour extraire des variables d'environnement celles qui nous intéressent, nous utilisons la fonction GetEnvironmentVariable en fournissant la clé (par exemple CONTENT_LENGTH). Cette fonction est encapsulée dans f_get_environment_value:

    function f_get_environment_value(p_keypChar): String;
      const k_max= 255;
      var l_environment_valueString[k_max];
          l_sizeInteger;
      begin
        l_size:= GetEnvironmentVariable(p_key, @ l_environment_value[1], k_max);
        l_environment_value[0]:= chr(l_size);
        Result:= l_environment_value;
      end// f_get_environment_value

Voici la fonction qui lit les octets du message:

    function f_raw_request_stringString;
      var l_request_methodString;
          l_content_lengthInteger;
          l_indexInteger;
      begin
        l_request_method:= f_request_method;
        l_content_length:= f_content_length;

        // -- now read the question
        if (l_request_method'POST'and (l_content_length> 0)
          then begin
              SetLength(Resultl_content_length);

              for l_index:= 1 to l_content_length do
                Read(Result[l_index]);
            end
          else Result:= '';
      end// f_raw_request_string

Nous convertissons les caractères spéciaux contenus dans cette chaîne ("+" en espace, les %28 en "(" etc):

    function f_convert_special_characters(p_stringString): String;
      var l_hex_stringShortString;
          l_indexInteger;
      begin
        l_index:= 1;
        Result:= '';

        while l_index<= Length(p_stringdo
        begin
          if p_string[l_index]= '+'
            then // -- change the + back to spaces
                 Result:= Result' '
            else
              if p_string[l_index]= '%'
                then
                  begin
                    // -- change specials %26 into &
                    l_hex_string:= '$00';
                    l_hex_string[2]:= p_string[l_index+ 1];
                    l_hex_string[3]:= p_string[l_index+ 2];
                    Result:= ResultChr(StrToInt(l_hex_string));
                    Inc(l_index, 2);
                  end
                else Result:= Resultp_string[l_index];

          Inc(l_index);
        end// while
      end// f_convert_special_characters

Et, pour le confort du programme principal, nous plaçons les paramètres dans une tStringList où la propriété Values nous permettra de facilement récupérer la valeur d'une clé:

    function f_c_parameter_listtStringList;
      var l_raw_request_stringl_request_stringString;
          l_indexInteger;
          l_parameterString;
      begin
        Result:= tStringList.Create;

        l_raw_request_string:= f_raw_request_string;
        l_request_string:= f_convert_special_characters(l_raw_request_string);

        // -- format: xxx=yyy&xxx=yyy
        l_parameter:= '';
        for l_index:= 1 to Length(l_request_stringdo
        begin
          if l_request_string[l_index]= '&'
            then begin
                Result.Add(l_parameter);
                l_parameter:= '';
              end
            else l_parameter:= l_parameterl_request_string[l_index];
        end// for

        // -- add the last one
        if l_parameter<> ''
          then Result.Add(l_parameter);
      end// f_c_parameter_list



4.10 - L'unité u_cgi_helpers

Nous avons écrit une petite unité qui regroupe les procédures et fonctions employées par tous nos CGI. Voici son interface:

    procedure get_cgi_exe_path;
    procedure open_log(p_log_nameString);
    procedure write_to_log(p_textString);

    function f_get_all_environmentString;
    function f_get_environment_value(p_keypChar): String;
    function f_request_methodString;
    function f_content_lengthInteger;

    function f_raw_request_stringString;
    function f_convert_special_characters(p_stringString): String;
    function f_c_parameter_listtStringList;

    var g_cgi_exe_pathString'';
        g_c_simple_logc_simple_logNil;

Et:

  • get_cgi_exe_path: récupère le chemin de l'exe et le stocke dans g_cgi_exe_path
  • open_log et write_to_log: procédure qui permettent de stocker les messages dans c_simple_log
  • f_get_all_environment et f_get_environment_value: la récupération de variables d'environnement
  • f_request_method et f_content_length: récupération de REQUEST_METHOD et CONTENT_LENGTH

  • f_raw_request_string, f_convert_special_characters, f_c_parameter_list: récupération du texte de la requête et extraction des paramètres


4.11 - Le traitement du CGI

Pour terminer nos schémas, une fois que le Serveur a créé les variables d'environnement et a lancé le CGI:
  • le CGI récupère les variables d'environnement et lit le texte du message:

  • le CGI traite les paramètres de la requête, construit une réponse qui est renvoyée au Serveur, qui la retourne au Client:

Voici alors la procédure de traitement de la requête:

// 001 u_cgi_tutorial
// 03 jan 2002

(*$r+*)

unit u_cgi_tutorial;
  Interface

    procedure handle_order(p_write_pathp_log_nameString);

  Implementation
    uses WindowsClassesSysUtils
        , u_c_simple_log
        , u_cgi_helpers
        ;

    const k_content_type'Content-type: text/html';

          // k_output='resu.txt';
          k_output'';

    procedure handle_order(p_write_pathp_log_nameString);

      procedure analyze_order;
        var l_raw_request_stringString;
        begin
          write_to_log('REQUEST_METHOD: 'f_request_method);
          write_to_log('CONTENT_LENGTH: 'IntToStr(f_content_length));

          l_raw_request_string:= f_raw_request_string;
          write_to_log('raw_request   : 'l_raw_request_string);
          write_to_log('request       : 'f_convert_special_characters(l_raw_request_string));
        end// analyze_order

      procedure send_simple_ok_answer;
          // -- build a simple answer
        var l_outputText;
            l_c_parameter_listtStringList;
            l_parameterInteger;
            l_acknowledge_orderString;
        begin
          write_to_log('send_ok_answer');

          l_c_parameter_list:= f_c_parameter_list;
          for l_parameter:= 0 to l_c_parameter_list.Count- 1 do
            write_to_log(l_c_parameter_list[l_parameter]);
          with l_c_parameter_list do
            l_acknowledge_order:= ' de <B><FONT COLOR=#FF0000>'Values['quantite']+ '</FONT>'
                + ' <FONT COLOR=#0000FF>'Values['livre']+ '</FONT></B>';
          l_c_parameter_list.Free;

          Assign(l_outputk_output);
          Rewrite(l_output);

          writeln(l_outputk_content_type);
          writeln(l_output);

          writeln(l_output'<HTML>');
          writeln(l_output'  <BODY>');
          writeln(l_output'    Merci pour la commande 'l_acknowledge_order
              + ' et bonne journée');
          writeln(l_output'  </BODY>');
          writeln(l_output'</HTML>');
          Close(l_output);

          write_to_log(' sent_ok_answer');
        end// send_simple_ok_answer

      begin // handle_order
        get_cgi_exe_path;

        open_log(g_cgi_exe_pathp_write_pathp_log_name);

        // write_to_log('ENVIRONMENT: '+ f_get_all_environment);
        analyze_order;

        send_simple_ok_answer;
      end// handle_order

    end

En tapant "Delphi dBase" et "5", le log est le suivant:



04/01/02 10:05:09 tutorial 0
04/01/02 10:05:09 tutorial 1
04/01/02 10:05:09 tutorial 2
04/01/02 10:05:09 tutorial 3 =========
04/01/02 10:05:09 tutorial 4 REQUEST_METHOD: POST
04/01/02 10:05:09 tutorial 5 CONTENT_LENGTH: 29
04/01/02 10:05:09 tutorial 6 raw_request : livre=Delphi+dBase&quantite=5
04/01/02 10:05:09 tutorial 7 request : livre=Delphi dBase&quantite=5
04/01/02 10:05:09 tutorial 8 send_ok_answer
04/01/02 10:05:09 tutorial 9     livre=Delphi dBase
04/01/02 10:05:09 tutorial 10      quantite=5
04/01/02 10:05:09 tutorial 11      sent_ok_answer

et la réponse glorieuse est:

 
 
Merci pour la commande de 5 Delphi dBase et bonne journée  
 
 
 
 
 


5 - Améliorations

Parmi les améliorations, citions:
  • l'utilisation des composants Delphi (HtmlBroker, WebSnap)

6 - Télécharger le source

Vous pouvez télécharger:
  • u_c_simple_log.zip: l'unité de log seule (1 K)
  • u_cgi_helpers.zip: l'unité des utilitaires CGI (2 K)
  • cgi_tutorial.zip: qui contient:
    • le .DPR de test p_cgi_tutorial son unité principale, u_cgi_tutorlial
    • les deux unités u_c_simple_log et u_cgi_helpers
    • la fichier .HTML de test (à placer dans "InetPub')
    (6 K)
  • jcommande.zip: le .DPR jcommande à placer (dans notre cas) sur le site www.jcolibri.com/scripts/ (2 K)


Comme d'habitude:
  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.

7 - Bibliographie

Les CGI (et leurs dérivés ISAPI etc) étant à la base de toutes les pages Web dynamiques, les articles d'initiation dans le domaine sont légion. Citons, dans le cas de Delphi (les liens sont ceux de mes documents papier. Les plus anciens sont vraissemblablement obsolètes, mais en recherchant par Alta Vista vous les retrouverez peut-être archivés ici ou là):
  • Delphi Internet Solutions - Bob Swart - 14 jan 1998
    Instructions pas à pas pour faire fonctionner des CGI
    DrBob a d'ailleurs publié récemment une version Kylix dans le même domaine (cf Borland Community)
  • Writing Standard CGI using Delphi 2 - Mike Lovell - 5-22-1996
    Utilisation des CGI de base. Malheureusement son exemple graphique voulait faire de l'animation, et utilisait donc un format MIME mutli-part. Je ne suis pas arrivé à le faire fonctionner de ce fait (il marche sans doute, mais ce format MIME m'a compliqué l'essai).
  • The Common Gateway Interface and Delphi - Greg DUNLAP Callista Inc - 1996
    Explique la raison pour laquelle les WinCGI ont été mis en place par WinSite
  • What happens when a CGI script is called - 1996
    Tutorial CGI
  • Writing a fancy hit counter in Delphi 5 Anders Ohlsson
    Un compteur de visiteurs utilisant les composants Delphi 5 (WebBroker). Utilise l'affichage d'un tMemo contenant les chiffres du compteur. Mentionnons que Ohlsson travaille actuellement chez Borland.
  • Réaliser des pages Web Interactives Paul TOTH
    Série d'articles en Français

8 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Créé: avr-03. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
      – http_client
      – moteur_de_recherche
      – javascript_debugger
      + asp_net
      – client_serveur_tcp
      – site_editor
      – pascal_to_html
      – cgi_form
      + les_logs_http_bruts
      + les_blogs
      – intraweb_tutorial
      + services_web
      – serveur_web_iis
      – client_serveur_pop3
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Expert Delphi Résolution de problèmes ponctuels, optimisation, TMA Delphi, audit, migration, réalisation de projets, transfert de technologie - Tél 01.42.83.69.36
Formation Rave Report Constructions d'états, avec prévisualisation, génération .HTML ou .PDF - 2 jours
Formation Ecriture de Composants Delphi Creation de composants : étapes, packages, composants, distribution - 3 jours