|
Tutorial: Formulaire CGI - John COLIBRI.
|
- mots clé:tutoriel - programmation Internet - Cgi - Form
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal, Delphi et programmation Internet
- plan:
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" :
|
|
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" :
|
|
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_output: Text;
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:
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_log= class
public
m_file: File;
m_who: String;
m_count: Integer;
m_convert_non_printable: Boolean;
constructor create_simple_log(p_who, p_file_name: String); Virtual;
procedure write_line(p_text: String);
destructor Destroy; Override;
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_path, k_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_path, p_log_name: String);
Implementation
uses Windows, SysUtils
, u_c_simple_log;
var g_c_simple_log: c_simple_log;
procedure open_log(p_log_name: String);
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_text: String);
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_path, p_log_name: String);
procedure send_simple_ok_answer;
// -- build a simple answer
var l_output: Text;
begin
write_to_log('send_ok_answer');
Assign(l_output, k_output);
Rewrite(l_output);
writeln(l_output, k_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_path+ p_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_key: pChar): String;
const k_max= 255;
var l_environment_value: String[k_max];
l_size: Integer;
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_string: String;
var l_request_method: String;
l_content_length: Integer;
l_index: Integer;
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(Result, l_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_string: String): String;
var l_hex_string: ShortString;
l_index: Integer;
begin
l_index:= 1;
Result:= '';
while l_index<= Length(p_string) do
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:= Result+ Chr(StrToInt(l_hex_string));
Inc(l_index, 2);
end
else Result:= Result+ p_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_list: tStringList;
var l_raw_request_string, l_request_string: String;
l_index: Integer;
l_parameter: String;
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_string) do
begin
if l_request_string[l_index]= '&'
then begin
Result.Add(l_parameter);
l_parameter:= '';
end
else l_parameter:= l_parameter+ l_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_name: String);
procedure write_to_log(p_text: String);
function f_get_all_environment: String;
function f_get_environment_value(p_key: pChar): String;
function f_request_method: String;
function f_content_length: Integer;
function f_raw_request_string: String;
function f_convert_special_characters(p_string: String): String;
function f_c_parameter_list: tStringList;
var g_cgi_exe_path: String= '';
g_c_simple_log: c_simple_log= Nil;
|
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_path, p_log_name: String);
Implementation
uses Windows, Classes, SysUtils
, 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_path, p_log_name: String);
procedure analyze_order;
var l_raw_request_string: String;
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_output: Text;
l_c_parameter_list: tStringList;
l_parameter: Integer;
l_acknowledge_order: String;
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_output, k_output);
Rewrite(l_output);
writeln(l_output, k_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_path+ p_write_path+ p_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: (6 K) 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')
- jcommande.zip: le .DPR jcommande à placer (dans notre cas) sur le site
www.jcolibri.com/scripts/ (2K)
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" :
- 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 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 la
développement de projets pour
ses clients, le conseil 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, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|