DIDACTITIEL ISAPI AVEC LES COMPOSANTS INTERNET DE DELPHI 5/6/7 enterprise

Jean-Pierre Lamon

Si vous voulez faire un download de mes trois tutoriels (ISAPI + WEBSNAP + INTRAWEB), c'est ici [ftp://ftp2.developpez.biz/developpo/delphi/tutoriels/isapi.zip]

- Titre : DIDACTITIEL ISAPI AVEC LES COMPOSANTS INTERNET DE DELPHI 5/6/7 enterprise

- Auteur : Jean-Pierre Lamon

- Parution : 1 avril 2002

- Mise à jour : 2 janvier 2020

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- Préambule

- Introduction

- I. Hello World

- I-A. DÉFINIR UNE PAGE D'ENTRÉE

- I-B. DÉFINIR UNE ACTION

- I-C. CODER L'ACTION

- I-D. CODER LA RÉPONSE

- I-E. DÉPLOYER CETTE PAGE

- II. Gestion statique des données : TPageproducer

- III. Gestion statique des données : utilisation de QueryTableProducer ou DatasetTableProducer

- IV. Gestion dynamique des données : répondre à une entrée de l'utilisateur

- V. Passer des paramètres par code d'une action à l'autre

- VI. Blob : afficher des images

- VII. Cookies

- VII-A. Lire un cookie

- VII-B. Poster un cookie

- VII-C. Effacer un cookie

- VIII. Déchargement et débogage des ISAPI

- IX. Envoyer un mail par ISAPI avec Winshoes

- X. Quelques astuces

- X-A. utiliser des tables maîtres/détails avec DatasetTableProducer

- X-B. Pour rediriger une page

- X-C. Qui envoie la requête ?

- X-D. champ Memo

- X-E. Récupérer la valeur d'un Checkbox

- X-F. Renvoyer un fichier en téléchargement

- X-G. Créer un splashscreen

- X-H. S'assurer de poster dans une table avec clé primaire sans concurrence grâce aux criticalsections

- X-I. Afficher un MessageBox dans une ISAPI

- X-J. Désactiver le cache du browser

- Conclusion / Liens

Préambule

Je ne vais pas m'étendre sur le développement d'Internet, mais un petit préambule en guise de prise de conscience de l'importance que va prendre la programmation d'applis sous Internet et de votre chance dans ce processus irréversible. Vous pouvez évidemment programmer des ASP en VB pour être complètement compatible Krosoft ou Frontpage. Vous pouvez utiliser ce dernier pour accéder en ASP à un SGBD. ASP a ses qualités et ses défauts, mais il est bien entendu que vous devriez apprendre un nouveau langage. Et quand bien même : Delphi intègre très bien les composants ASP.

Or, si vous programmez déjà en Delphi, vous avez un avantage monstrueux : tout ce que vous connaissez de Delphi, vous pourrez le mettre en pratique, avec quelques nouveautés évidemment et une connaissance de base de l'HTML, dans la programmation ISAPI. Et ce que vous apprendrez maintenant, vous pourrez ensuite l'appliquer pour des développements sous Kylix. Et ces avantages indéniables vous permettront de programmer des applis Web extrêmement rapides, compactes, au source protégé, très fiables et… compatibles Linux. Alors, si ce cheminement vous intéresse, ne vous découragez pas. Au début, tout à l'air tellement tordu. Mais c'est comme partout en programmation, une fois qu'on s'y est mis, ce n'est vraiment pas si compliqué. J'espère simplement que ce modeste tutoriel vous permettra d'en être convaincu ;-) Enjoy…

Introduction

Définition

Les applications ISAPI sont des DLL qui sont stockées sur un serveur Web HTTP et peuvent être exécutées par des requêtes client. Le client peut adresser une requête au serveur Web et l'application serveur répond à cette requête en donnant un résultat en format MIME.

L'adressage

Un exemple : vous voulez afficher l'image JPEG ornella1.jpeg à travers une application ISAPI « myisapi.dll » située sur le serveur Web « www.monsite.com » sous le répertoire « /scripts ». L'adresse que vous aurez à envoyer aura cette formulation :

http://www.monsite.com/scripts/myisapi.dll/Getimage?ImgName=ornella1.jpg&amp;ImgId=231
---------------------- ------  ----------- --------- ----------------------------
1                 2          3         4                   5

Les différentes sections de cette adresse sont :

1. L'adresse Web de votre site ;

2. Le répertoire virtuel où se trouve votre ISAPI (en général \\MON_SERVEUR\inetpub\scripts) ;

3. Le nom de votre DLL ;

4. L'action définie dans votre DLL qui traitera la requête ;

5. Paramètres de l'application séparés de l'adresse principale par le signe ? (les paramètres sont séparés entre eux par des &).

CGI vs ISAPI, cache et variables

(merci à JJM pour le débat chaud, « fructueux » et clarifiant sur ce sujet :-)

Différence CGI / ISAPI

Un CGI est une application console standard. Chaque requête est traitée par une nouvelle instance de l'application. Une application ISAPI est par contre une DLL qui est chargée lors du premier appel et reste en mémoire tant que le serveur n'est pas arrêté.

Pour la programmation des CGI, voir l'excellent tutoriel du non moins excellent Paul Toth ;-)

Avantages et inconvénients: ISAPI est forcément plus rapide et quasi obligatoire lors d'un grand volume de requêtes. La programmation d'une ISAPI doit par contre être bien plus pointue, car vu que la DLL reste en mémoire, une action mal programmée peut complètement bloquer le serveur Web (bien qu'il existe sous IIS la possibilité de le charger dans un espace mémoire isolé).

Variables dans une ISAPI

Un autre problème réside dans les variables globales externes à l'objet TWebmodule. Celles-ci peuvent entraîner des conflits entre les différentes requêtes de l'ISAPI. Elles sont de ce fait fortement déconseillées (ou alors, les protéger par threading).

Pour les autres variables, internes à l'objet TWebmodule (private ou public), un problème peut également se poser si le cache est utilisé. Les variables de ce style sont « safe-thread » et ne sont pas affectées par d'autres requêtes simultanées, mais peuvent par contre l'être par une requête précédente. Et pour expliquer cela, voici le principe de fonctionnement d'un TWebmodule en mode cache :

- un TWebmodule est créé tout d'abord lors d'une première requête (après déchargement p.ex.) ;

- à la fin de la requête, l'objet n'est pas détruit, il est mis en file d'attente dans un pool ;

- lors d'une prochaine requête, il y a vérification de la présence d'une instance libre dans ce pool et s'il y en a une, elle est utilisée, sinon, une autre instance est créée (max. 32 instances). Mais si une instance est libre, elle est récupérée telle quelle, à savoir en l'état final ou l'a laissée la requête précédente. Donc si cette requête a changé certaines valeurs d'une variable, ce sont ces valeurs qui seront également récupérées.

Pour éviter ce genre de conflits, plusieurs solutions peuvent s'offrir :

- ne pas utiliser de variables globales à l'intérieur de l'objet, mais utiliser plutôt des paramètres à passer d'une requête à l'autre ;

- désactiver le cache connexion (vous perdez en rapidité) ;

- bien veiller à initialiser, dans le beforedispatch de chaque action, les variables qui pourraient être affectées par une action antérieure ;

- bien veiller à coder principalement à l'intérieur d'une action. Je préconiserai l'ouverture et la fermeture des tables ou queries, la mise en place de filtres ou toute autre action susceptible de modifier certaines valeurs, dans l'action elle-même. Une table, p.ex. peut très bien être créée, définie et détruite dans l'action plutôt que placée dans le WebModule. Le safe-threading est à ce prix.

Concernant le nombre d'instances Webmodule limité à 32, cela ne signifie aucunement que seules 32 connexions simultanées sont permises. Il s'agit d'instances Webmodule créées. Prenons comme hypothèse un temps de réponse de 50 ms. IIS peut dans ce cas renvoyer 38 400 pages par minute. Si vous partez du principe qu'en moyenne vos utilisateurs affichent une page par minute (sauf cas extrêmes évidemment), ceci implique que vous pourriez assurer la connexion à 38 400 utilisateurs simultanés. On a donc une marge, disons… assez large :-)

Création d'une DLL ISAPI avec Delphi

Vous devrez avoir la version Client/serveur de Delphi pour avoir les composants Internet. Ils peuvent cependant être acquis séparément pour les autres versions. (P.-S. Ces exemples sont tirés d'un projet fait sous D5 enterprise. version anglaise). Vous devez également connaître les principes du langage HTML, car, dans certains cas, vous serez appelé à créer toute la page « à la mimine ».

Création d'un nouveau projet -> Fichier / Nouveau puis…

Vous choisissez ISAPI/NSAPI (NSAPI étant un serveur Netscape)et vous obtenez votre Webmodule qui sera le conteneur de tous vos objets :

Les événements d'un Webmodule :

- AfterDispatch : cet événement est appelé après qu'une requête a été envoyée à une des WebActions ;

- BeforeDispatch : la même chose, mais avant. Vous pouvez faire vos initialisations de variables ici ;

- OnCreate,OnDestroy : l'ouverture et la fermeture d'une base de données peuvent se faire ici (recommandé par Borland, mais peut se faire également dans le code même de l'action). Pour les tables et requêtes par contre, je recommanderai de le faire dans l'action elle-même. !!! Le OnDestroy est appelé uniquement lors du déchargement de la DLL.

I - Hello World

I-A - DÉFINIR UNE PAGE D'ENTRÉE

C'est donc dans ce webmodule que tout va se passer. Vous allez y placer vos différents objets (non visuels évidemment), tels que des Pageproducers (producteurs de page) ou objets base de données. C'est également par ce Webmodule que vous allez définir les « actions », qui sont la base de toute requête Web.

Pour un premier essai, nous allons tout simplement définir une page qui affichera tout simplement « hello world ». Dans HTMLDoc nous allons donc écrire le code HTML suivant :

<html><body>
Hello world
</body></html>

Première chose à faire, définir une page d'entrée. Elle peut soit exister déjà, soit être créée dynamiquement. Vous prenez donc dans la palette des composants Internet un composant Pageproducer, qui a les propriétés suivantes dans l'inspecteur d'objet :

Pour définir votre page d'entrée, vous pouvez donner :

1. Le nom d'un fichier sous HTMLFile ex. : (c:\inetpub\wwwroot\index.htm) ;

2. Coder le source HTM en « dur » dans le TStrings HTMLdoc ;

3. Remplir HTMLDoc dynamiquement à l'appel de la page.

I-B - DÉFINIR UNE ACTION

Votre page étant définie, nous allons maintenant pouvoir définir l'action par défaut.
Pour ce faire, vous reprenez votre WebModule visuel, et cliquez à l'intérieur.Vous obtenez l'inspecteur d'objets suivant :

Vous cliquez sur les trois points TWebActionItems (Propriété Actions), et vous obtenez l'éditeur d'actions ou vous allez cliquer sur l'icône jaune pour ajouter une nouvelle action (WebactionItem1).

Dans l'inspecteur d'objets, vous définissez les propriétés de votre action Première chose à faire, changez Default de false à true, car vous allez définir votre action par défaut.

Vous cliquez alors sur Events et vous obtenez :

I-C - CODER L'ACTION

Vous devez faire un double clic sur WebModule1WebActionItem1Action (dialogue de Onaction) et obtenir la page de code à définir pour votre première action :

I-D - CODER LA RÉPONSE

Nous allons à présent coder l'action principale.

Les paramètres principaux :

- Request (TWebRequest) : la requête reçue par le serveur (avec entre autres les paramètres dont nous nous servirons plus loin) ;

- Response (TWebResponse): ce que nous allons renvoyer comme page.

Regardez l'aide de Delphi pour les propriétés et les méthodes de TWebRequest et TWebResponse. Elles vous permettront d'aller bien plus loin dans l'élaboration d'ISAPI complexes.

Le contenu de la réponse peut être un TStringList (nous verrons cela plus loin également) ou une page telle que celle que nous avons définie au départ.

La formulation de cette première action est simple : comme réponse, nous allons envoyer le contenu de PageProducer1

procedureTWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Begin
Response.Content:=PageProducer1.Content;
end;

I-E - DÉPLOYER CETTE PAGE

Pour déployer maintenant ce premier essai, nous allons tout d'abord sauvegarder notre projet sous le nom MyIsapi.dpr.

Puis dans le menu Projets / options / répertoire / répertoire de sortie (j'ai la version anglaise de D5, donc les intitulés peuvent différer), nous allons mettre le répertoire habituel des scripts ISAPI d'un serveur IIS ou PWS, à savoir « c:\inetpub\scripts ». Nous compilons notre première ISAPI. Une fois cette opération effectuée, vous devriez voir sous c:\inetpub\scripts le fichier MyIsapi.dll.

Pour appeler cette ISAPI, il faut évidemment que votre serveur Web soit lancé. Vous lancez votre browser préféré (Netsacpe évidemment ;-), puis tapez comme adresse : http://localhost/scripts/myisapi.dll.

Vous devriez avoir une page HTML affichant : Hello world. Si ce n'est pas le cas, soit je n’ai pas été clair :o), soit le répertoire /scripts n'a pas les droits requis (exécuter), soit vous avez commis une erreur.

Tips

Pour rediriger une page : Response.SendRedirect(MonNouvelURL);
Si vous voulez avoir des informations sur qui envoie la requête, regardez les propriétés RemoteAddr et UserAgent de TwebRequest.

II - Gestion statique des données : TPageproducer

Nous allons voir maintenant comment afficher des données statiquement (sans entrée de l'utilisateur) à partir d'un SBGD.

Imporant pour les ISAPI : pour un accès à votre SGBD par ODBC, vous devez impérativement créer un DNS Système et non utilisateur.

Autre petit conseil : évitez le BDE si possible. Surtout au vu de l'attribution des droits IUSR_VOTRE_MACHINE (c'est le compte utilisateur Internet de base sous IIS). Si vous en avez la possibilité, les composants ADO sont parfaits, ou alors Interbase 6, car compatible Linux et… gratuit ;-)

Tout se passe à nouveau dans le Webmodule. Nous allons y ajouter des composants base de données :

- un composant TDatabase pour l'accès au SGBD (ici avec les composants ADO) ;

- un composant TTable pour l'accès aux données ;

- un composant TPageProducer

Nous allons insérer dans la page Showname le code HTML. Vous allez ajouter un Tag (ici <#NOM>) où Delphi insérera les données.

Dans l'éditeur d'action, nous allons rajouter… une action ;-)

…que nous nommons Showname, dans le dialogue pathInfo. Pour l'affichage des résultats, nous avons deux solutions. Soit mettre le nom de notre page qui recueillera les données sous Producer et aucun codage n'est nécessaire…

… soit ne rien mettre sous Producer et coder la réponse : voici le code d'appel de l'action ShowName, qui appelle la page du même nom :

procedure TWebM.WebMShownameAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
begin
Response.Content := ShowName.Content;
end;

Lors de l'appel à cette page, la procédure ShowNameHTMLTag est activée et c'est là où nous allons remplacer le tag <#NOM> par les données du SGBD à travers l'événement OnHTMLTag :

procedure TWebM.ShowNameHTMLTag(Sender: TObject; Tag: TTag; const TagString: String;
TagParams: TStrings; var ReplaceText: String);
Var Data: String;
begin
Try
if CompareText(TagString, 'NOM') = 0 then
begin
//Ouverture de la base et du fichier
Try
Adresses.Connected:=True;
Except on E:exception do
Data:=E.Message;
end;
Nom.Open;
Data := '';
Nom.First;
while not Nom.Eof do
begin
Data := Data + Nom.FieldByName('Nom').AsString+' '+Nom.FieldByName('prenom').AsString;
//Nous devons ajouter à Data un retour/ligne HTML
Data:= Data +'<BR>';
Nom.Next;
end;
end;
Finally
Adresses.Connected:=False;
ReplaceText := Data;
end;
end;

Vous devriez alors, en appelant http://localhost/myisapi.dll/showname avoir une liste de toute votre table.

III - Gestion statique des données : utilisation de QueryTableProducer ou DatasetTableProducer

!!!. Les deux composants QueryTableProducer et DatasetTableProducer ne fonctionnent pas avec les composants ADO.

Pour afficher des données à travers une requête.

Les composants du Webmodule :

La requête sera :

Select nom, prenom, lieu from nom where nom>'S'

À noter ici l'ajout d'un composant TSession, auquel nous attribuons la propriété autosessionname à True. Ceci évitera les problèmes d'accès concurrents. À noter que les composants Internet de D4 sont bogués, et qu'il faut impérativement ajouter, dans la première ligne du source de programme, la ligne : ismultithread:=True;

Pour Paradox, il faudra attribuer les propriétés NetfileDir et PrivateDir selon les usages habituels de ce SGBD.

Avec l'inspecteur d'objets du QueryTableProducer, attribuons à la prop. Query, le nom de notre requête : Query1.

Nous pouvons déjà définir ici les en-têtes (Header) et pieds de page (Footer) de la page HTML des résultats, ainsi que les attributs de ligne et de table.

Sous Header nous allons ajouter le code HTML :

<HTML><TITLE>Les noms plus grands que S</TITLE><BODY>

et dans Footer :

</BODY></HTML>

Un des grands avantages du composant QueryTableProducer est qu'il vous permet de définir déjà en design time les colonnes de vos résultats. Ainsi, si vous cliquez deux fois sur le composant, vous aurez le tableau suivant après avoir ajouté tous les champs de votre Query1. Vous aurez ainsi, visuellement, la disposition de votre tableau. (P.-S. Je conseille vivement sous Width, de mettre 95 % afin que votre tableau se mette automatiquement à la bonne largeur lors d'un redimensionnement du browser).

Vous pouvez définir, en cliquant sur n'importe quel champ du QueryTableProducer1.Columns, les propriétés de chacune des cellules :

et le tour est joué, votre page est terminée.

Pour la déployer, nous allons rajouter une action dans notre éditeur d'action, action que nous allons nommer /Showquery. Notre Webmodule donne donc :

Le code du OnAction de notre nouvelle action :

procedure TWebM.WebMWebActionItem3Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Var S:String;
begin
S:='';
Try
Try
Adresses.Connected:=True;
Except
On E:Exception do
Begin
S:='<html><body>Une erreur d''accès s''est produite : '+e.message+'</body></html>;
Response.Content:=S;
Adresses.connected:=False;
Exit;
end;
end;
Finally
Response.Content:=QueryTableProducer1.Content;
Adresses.Connected:=False;
end;
end;

Il suffit alors de recompiler notre DLL Isapi et de tester cette action par

http://localhost/myisapi.dll/ShowQuery

À noter que si vous avez déjà lancé votre DLL, vous devrez la décharger. Reportez-vous au chapitre traitant ce point pour connaître la marche à suivre.

Pour le DatasetTableProducer, le principe est le même, mais évidemment sans requête SQL. Toute votre table est affichée.

IV - Gestion dynamique des données : répondre à une entrée de l'utilisateur

Les entrées d'un utilisateur se font par des formulaires <form>. Il existe deux méthodes pour passer des paramètres par un formulaire : GET ou POST.

- La principale différence réside dans le fait qu'avec la méthode GET, tous les paramètres seront affichés dans l'adresse HTTP envoyée au serveur : « http://mydomain/MyIsapi.dll/ShowName?firstname=Ornella&Lastname=Muti ». Ceci est un exemple, (Ornella Muti donc ;-) que nous allons utiliser, par le plus grand des hasards, par la suite.
D'autre part, la taille de la ligne des paramètres sera limitée à 255 caractères.
Le paramètre renvoyé à la DLL par le serveur HTTP sera du type QueryFields.

- La méthode POST entraînera une adresse HTTP sans paramètre affiché : http://mydomain/MyIsapi.dll/ShowName. Le paramètre sera lui du type ContentFields. Le serveur Web ne renvoie tout d'abord que 48 K. Si vous avez un Contenfield plus long,regardez ici.

Le formulaire comporte deux paramètres principaux : une méthode et une action. La méthode est soit POST soit GET. L'action est l'appel à la procédure qui sera traitée par notre ISAPI. La formulation HTML est la suivante :

<FORM method="GET" action="/scripts/myisapi.dll/searchname">

Notre formulaire contiendra des champs. Ce sont les valeurs de ces champs, entrées par l'utilisateur, qui seront récupérées et traitées par notre DLL. La formulation HTML :

<INPUT type="text" name="NOM" value="">

Ici p.ex., c'est le paramètre name qui sera transmis à la DLL. Nous allons donc essayer de passer ce formulaire à une nouvelle action de notre DLL que l'on nommera searchname avec la méthode POST. Le code de notre nouvelle page HTML :

<html>
<body>
<form method="POST" action="/scripts/myisapi.dll/searchname">
<p>Prénom: <input type="text" name="FIRSTNAME" value=""><br>Nom : <input type="text" name="LASTNAME" value="">
<input type="submit" value="Envoyer">
</p>
</form>
</body>
</html>

Notre DLL devra maintenant traiter cette requête Web. Les paramètres passés par la requête seront récupérés par Request.ContentFields.Values['FIRSTNAME'] (Ornella) et Request.ContentFields.Values['LASTNAME'] (Muti).
Avec la méthode GET, comme vu plus haut, nous aurions récupéré ces paramètres par Request.QueryFields.Values['FIRSTNAME']…

Nous allons donc créer cette nouvelle action SearchName :

Vous remarquerez que cette nouvelle action ne possède pas de « Producer », car nous allons créer notre page HTML dynamiquement. Voici le code de cette nouvelle action

procedure TWebM.WebMSearchNameAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Var S:TStringList;

begin
S:=TStringList.Create;
Try
S.Add('<HTML><BODY>');
Adresses.connected:=True;
Query1.SQL.Clear;
Query1.SQL.Add('Select Nom, Prenom, Lieu from NOM where prenom=');
Query1.SQL.Add(QuotedStr(Request.Contentfields.Values['FIRSTNAME']));
Query1.SQL.Add(' and nom=');
Query.SQL.Add(QuotedStr(Request.ContentFields.Values['LASTNAME']));
Query1.SQL.Add(' order by Nom, Prenom);
Query1.Open;
If Query1.RecordCount=0 then
S.Add('Enregistrement introuvable...')
Else
Begin
While not Query1.Eof do
Begin
S.Add(Query1.FieldByName('Nom').AsString+' ');
S.Add(Query1.FieldByName('Prenom').AsString+' ');
S.Add(Query1.FieldByName('Lieu').AsString);
//Retour à la ligne
S.Add('<BR>');
Query1.Next;
end;
end;
Query1.Close;
S.Add('</HTML></BODY>');
Adresses.Connected:=False;
Handled:=True;
Finally
//On renvoie la page HTML construite (S)
Response.Content:=S.Text;
S.Free;
end;
end;

V - Passer des paramètres par code d'une action à l'autre

Comme étape suivante, nous allons voir comment passer des paramètres d'une action à l'autre.

Ainsi, lorsque vous affichez les résultats de votre action, vous aimeriez qu'elle affiche un lien vers une étape suivante de l'information que vous voulez fournir. Dans notre exemple, nous aimerions que la recherche que nous avons faite sur Ornella Muti (simple listing des noms, prénoms, lieux qui sont dans le SGBD) lance une autre action de notre ISAPI qui elle affiche le détail sur elle (avec sa photo éventuellement:)

Nous allons donc créer une action supplémentaire qui s'appellera « detail ». Dans cette action, nous pourrions coder l'affichage de toutes les données. Il suffit de faire comme dans l'action précédente (Searchname).

Mais c'est dans le code de cette action précisément que nous allons changer quelque chose. Nous allons en effet rajouter un lien qui renverra vers la nouvelle action.

Et pour faire cela, rien de plus simple, il suffit d'ajouter une référence (A HREF) à notre nouvelle action Detail. Ainsi, quand l'utilisateur cliquera sur ce lien, il lancera cette nouvelle action.

Notre code du chapitre précédent devient :

//la partie début du lien de notre affichage
S.Add('<A HREF="/scripts/myisapi.dll/detail?id='+Query1.FieldByName('id_nom').AsString+'">');
//la partie qui sera effectivement affichée
S.Add('Query1.FieldByName('Nom').AsString+' ');
//La partie fin du lien;
S.Add('</A>');

Il nous suffira alors de récupérer dans la nouvelle action Detail le paramètre passé (ici request.contentfields.values['id_nom']) et de faire une nouvelle requête sur la valeur de id_nom pour afficher ensuite selon la méthode décrite dans le chapitre précédent tous les champs de l'enregistrement. On peut ainsi passer des paramètres lors de chaque action. Ceci est très important, car cette méthode permet d'éviter de déclarer des variables globales qui sont les ennemies jurées de la programmation d'ISAPI

Ce paramètre n'est pas forcément un paramètre visible d'une form (dialogue texte, checkbox, liste déroulante, etc.). Vous pouvez très bien rajouter et gérer des champs cachés. La formulation :

<input type="hidden" name="FIRSTNAME" value="+Request.QueryFields.Values['MonParametreRecupere']+'">

Vous pouvez ainsi passer d'une action à l'autre n'importe quel paramètre invisible à l'utilisateur. D'autant plus si vous utilisez la méthode Post qui n'affiche pas ces paramètres.

VI - Blob : afficher des images

Avec l'action Detail que nous avons définie dans le chapitre précédent, nous allons afficher l'image. Pour cela, il suffit de créer une nouvelle action que nous appellerons GetImage. Un stream renverra l'image. Il faut impérativement lancer une nouvelle requête pour afficher cette image (ici QImage). Le configuration est la suivante : une table principale NOM et une table secondaire PICTURES avec deux champs : un champ de référence à la table principale (id_nom) et un champ Blob contenant l'image (Pictures)

L'action principale (à rajouter, les gestions d'erreur, etc.)

procedure TWebModule1.WebModule1WebActionItem3Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Var S:TStringList;

begin
//Le TStringList S est notre page HTML
S:=TStringList.Create;
S.Add('<html><body background="/images/backg.gif">');
S.Add('<center><table border="0">');
Connect_Base;
QFind.SQL.Clear;
//Récupération des paramètres Firstname et Lastname dont on gère
//les '' par QuotedStr ou AnsiQuotedStr;
Qfind.SQL.Add('Select id_nom, Nom, Prenom from Nom where Nom= '+
QuotedStr(Request.Queryfields.Values['LASTNAME'])+' and prenom='+
QuotedStr(Request.QueryFields.Values['FIRSTNAME']));
QFind.Open;
If QFind.RecordCount>0 then
Begin
While not QFind.Eof do
Begin
S.Add(Format('<td valign="top">%s <td valign="top">%s<td valign="top">',
[QFind.FieldByName('Nom').AsString, Qfind.FieldByName('Prenom').AsString]);
// On cherche si une image existe
QPicture.SQL.Clear;
QPicture.SQL.Text:='Select * from pictures where id_nom ='+
QFind.FieldByName('id_nom').AsString;
QPicture.Open;
If QPicture.RecordCount>0 then
Begin
//on va chercher l'image par l'action GetImage et on l'affiche à cet endroit
S.Add(format('<img src="/scripts/adr.dll/GetImage?rec=%d nosave>,
[QFindid_nom.AsInteger])';
end
Else S.Add('Pas d''image');
S.Add('<tr>');
QFind.Next;
end;

S.Add('</table></body></html>');
Response.Content:=S;
S.Free;
Disconnect_Base;
//Handled indique que l'action est terminée
Handled:=True;
end;

L'action GetImage :

procedure TWebModule1.WebModule1WebActionItem7Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Var MS:TMemoryStream;

begin
//Il faut tout de même faire une nouvelle requête même si l'image avait déjà été sélectionnée par QPicture
If QImage.Active then
QImage.Close;
QImage.SQL.Text:='Select * from pictures where id_nom='+Request.QueryFields.Values['rec'];
QImage.Open;
Ms:=TMemoryStream.Create;
TBlobField(QImage.FieldByName('Pictures')).SaveToStream(Ms);
MS.Position:=0;
Response.ContentType:='image/jpeg';
Response.ContentStream:=Ms;
Response.SendResponse;
Handled:=True;
//Ne pas libérer le TMemoryStream, Delphi s'en charge
end;

Pour ceux qui ne l'ont pas encore vue, je ne résiste pas au plaisir de vous montrer le résultat de l'action GetImage ;-)

Vous en voulez d'autres ? ;-) 1 / 2 / 3

Si vous voulez transmettre autre chose que des images (exécutables p.ex.), la procédure est la même, vous pouvez passer par un flux. Il suffit de changer la ligne en bleu (content-type) et de mettre le type MIME adéquat : p.ex. application/zip ou application/exe etc.

Pour que votre page affiche p.ex. un fichier à télécharger, vous recherchez l'extension du nom de votre fichier et dans content-type vous mettez : application/+« extension du nom de votre fichier ». Le browser affichera alors le dialogue de téléchargement. Sans cette manière de faire, il affichera le source du fichier.

VII - Cookies

Le cookie est un petit fichier qui est stocké directement sur l'ordinateur de l'utilisateur, dans un répertoire précis et propre à chaque browser. Pour que vous puissiez l'utiliser, il faut impérativement que l'utilisateur ait permis la pose de cookies. Cette permission passe par le browser respectif. Chaque cookie doit avoir un nom (ici Acookie).

Un cookie est tout simplement un String qui commence par : « NomDuCookie= » puis les valeurs attribuées au cookie (p.ex. n° d'enregistrement, séparés par un caractère, date de la dernière commande, etc.).
Ces valeurs peuvent ensuite être manipulées à votre convenance.

VII-A - Lire un cookie

dans un String (S1)

S1:=request.CookieFields.Values['ACookie'];

VII-B - Poster un cookie

var slstCookie:TStringList;

begin
slstCookie := TStringList.Create;
try
slstCookie.Clear;
slstCookie.Append('CookieExample=ACookie');
slstCookie.Append('CookieField1=Value1');
slstCookie.Append('CookieField2=Value2');
Response.SetCookieField(slstCookie, '', '/scripts',-1,False);
Response.Content := 'Cookie has been set. Thank you!';
finally
slstCookie.Free;
end;
end;

Exemple d'une redirection avec cookies :

var  slstCookie:TStringList;

begin
slstCookie := TStringList.Create;
try
slstCookie.Clear;
slstCookie.Append('CookieExample=ACookie');
slstCookie.Append('CookieField1=Value1');
slstCookie.Append('CookieField2=Value2');
Response.SetCookieField(slstCookie, '', '/scripts',-1,False);
//Sendredirect ne marche par avec les cookies
Response.Content := '<meta http-equiv="refresh" content="0;'+
'URL=http:&#47;/localhost/scripts/myisapi.dll/getimage?nom=ornella.jpg');
finally
slstCookie.Free;
end;
end;

Paramètres du cookie

procedure SetCookieField(Values: TStrings; const ADomain, APath: string;
AExpires: TDateTime; ASecure: Boolean);

Values sont les valeurs que l'on attribue au cookie.
ADomain et APath sont le nom d'URL vers lequel les cookies sont à envoyer.
Un cookie peut être gardé plus ou moins longtemps. AExpires détermine ce délai. P. ex., ici, c'est -1 = jamais ou ça peut être Now pour immédiatement ou Now+1 pour expiration dans un jour ou Date… ou 0 pour effacer le cookie à chaque fermeture du browser.
Ce délai est utilisé pour...

VII-C - Effacer un cookie

Response.SetCookieField(S,'','',Now(),false);

VIII - Déchargement et débogage des ISAPI

Un des grands problèmes du débogage des ISAPI est dû au fait que la DLL, une fois chargée, reste en mémoire.
Si vous voulez recompiler un projet, il vous faudra tout d'abord décharger cette DLL. Il existe plusieurs solutions pour faire face à ce problème.

- Développer un CGI plutôt qu'un ISAPI (qui ne sera donc pas en cache à chaque appel), puis le transformer en ISAPI lors du stade final.

- Sous NT ou W2K, dans le IIS Manager, vous allez sur le répertoire racine « mon site Web », bouton droit de la souris, propriétés et sous paramètres d'application, vous cliquez sous « configuration » et désactivez le Checkbox « mettre en cache les applications ISAPI ». Ne pas oublier de recocher cette case lors de la mise en production.

- Utiliser un outil externe. Il en existe plusieurs, le plus utilisé étant un shareware, omnihttpd : http://www.omnicron.ab.ca/httpd/. L'ennui que j'ai constaté : il ne fonctionne pas avec des frames. Ou alors le débogueur Intrabob de Bob Swart (alias DrBob) : http://www.drbob42.com/tools/index.htm. Ou encore : http://dataweb.de/isapie.htm. Un autre que j'utilise, mais il est payant, Falcon : http://www.blueface.com/.

- Ce qui suit :

Décharger une DLL sous Win98

Procédure

1. Faire un raccourci vers PWS (c:\windows\system\inetsrv\pws.exe).

2. Bouton droit de la souris sur le nouveau raccourci et changer la cible qui devient : « c:\windows\system\inetsrv\pws.exe /stop ». Ceci permettra de décharger la DLL. Mais pour la déboguer (et c'est bien plus pratique) :

3. Dans « projet », aller sous run/parameters (j'ai d5 c/s en anglais) et dans application hôte et mettre « c:\windows\system\inetsrv\inetinfo.exe ».

4. Sous paramètres, mettre « -e w3svc ».

5. Dans le projet, aller sous « /projet/options/répertoires », et dans output directory mettre « c:\inetpub\scripts » (ou le répertoire de script ayant les droits d'exécution) (enfin, ça, c'est sous NT).

6. À partir de là, il y a possibilité de déboguer la DLL mais il ne faut pas que PWS soit lancé. On peut l'arrêter par l'icône du system tray (bouton droit puis Stop service). Là, l'icône doit avoir un X rouge. Pour décharger complètement le PWS, il faut cliquer sur le raccourci créé plus haut.

Décharger une DLL sous Win2000

La méthode que j'utilise est de passer par un paramètre d'application de IIS5. Pour y arriver, il faut déclarer la DLL dans un processus mémoire séparé. Pour ce faire, sous l'administrateur IIS5, sélectionner le répertoire contenant la DLL (en général scripts) :

puis bouton droit de la souris et propriétés. Une fenêtre s'ouvre et sous paramètres d'application, il faut mettre pour nom de l'application, le nom de votre DLL. La protection d'application doit être réglée sur Élevée (Isolé) ou moyenne. De cette façon, si votre DLL « plante », elle ne plantera pas le serveur Web.

À partir de là, il vous suffit de stopper le serveur Web dans l'administrateur IIS et de le relancer. Votre DLL sera déchargée.

Ou alors :

La prochaine fois que vous appellerez votre DLL, vous ouvrirez à nouveau cette fenêtre et le bouton « Décharger » ne sera plus grisé. Il suffira de le cliquer pour pouvoir à nouveau compiler votre DLL.

Sur mon site vous pourrez trouver un petit exe avec sources qui vous permettra de décharger le répertoire automatiquement.

Décharger une DLL sous NT 4

La même procédure que ci-dessus peut être utilisée pour NT4. Cependant, il m'est arrivé fréquemment qu'en cliquant sur le bouton « décharger », le serveur Web plante lamentablement, tout simplement. Le pourquoi du comment dans l'article de Stéphane ci-dessous. Sous Win2000, ce procédé est nettement plus efficace, mais vous ne pourrez par contre plus déboguer vos DLL tant que ce paramètre existera.

Débogage de DLL ISAPI

Pour le débogage lui-même, voici une traduction, avec sa permission, de la page de l'excellent Stéphane Grobéty :o)

Débogage sous W2K et IIS5

1. Lancer le manager IIS WWW ;

2. Ouvrir Internet Services Manager et lancez votre site Web ;

3. Ouvrir l'onglet des propriétés de votre répertoire /scripts (ou celui où vous placez vos DLL) ;

4. Choisir protection d'application « basse » ;

5. Cliquez sur le bouton de configuration et choisissez l'onglet débogage de l'application ;

6. Activez les deux boutons indicateurs de débogage ;

7. Fermez le manager de services Internet et arrêtez le service IIS WWW (n'arrêtez pas votre site web avant d'arrêter IIS)) ;

8. Mettez sous Run¦Paramètres : application hôte : C:\WINNT\system32\inetsrv\inetinfo.exe. Sous paramètres : -e w3svc ;

9. C'est tout !

Une autre méthode de débogage sous W2K avec le service des composants (COM+)

Une méthode apparemment complexe, mais une fois réglée, ça fonctionne pile-poil. Par contre, ne pas oublier de tout remettre en état après la séance de débogage si vous travaillez directement sur le site de production.

Étape 1

Comme dans NT4, la première étape consiste à créer un répertoire virtuel (celui où vous placerez votre DLL, en général /Scripts) sur votre site sous IIS. S'il n'existe pas : clic droit sur la ligne de votre site Web--> Nouveau --> répertoire virtuel. Dans les propriétés de ce répertoire virtuel, le Nom de l'application devrait être à la base le nom du répertoire virtuel (en principe Scripts).

Sous Exécuter les autorisations, il faut mettre « Scripts et exécutables ». Enfin, sous Protection d'application, pour le débogage et non en production, il faut mettre Élevée (Isolé).

Rem. : en fait, ce réglage sur « Isolé » lancera pour chaque DLL une nouvelle instance de Dllhost.exe, qui, comme son nom l'indique, prend en charge votre ISAPI (sous protection moyenne, toutes vos DLL seront placées dans une seule instance de Dllhost.exe). Si votre Dllhost.exe gonfle, ceci est dû au fait que le réglage d'AspTrackTrheadingModel dans la métabase d'IIS5 n'est pas bon. Il faut que cette valeur soit à 1 et non pas à 0 comme elle est initialisée lors d'un « fresh install » de w2k. Changez ces valeurs grâce à metaedit.exe que vous trouvez sous : http://support.microsoft.com/support/kb/articles/Q232/0/68.ASP?LN=EN-US&SD=gn&FR=0.

Votre DLL doit être placée dans ce répertoire. Si le bouton Décharger est activé, cliquez-le pour décharger votre DLL.

Étape 2

Dans le service des composants --> (Démarrer - Programmes - Outils d'administration - Service des composants - Ordinateurs - Poste de travail - Applications COM+) vous devriez maintenant voir votre répertoire virtuel. Rem. Il apparaîtra uniquement si votre protection d'application est réglée sur « Élevée » (p.ex. IIS-{Monsiteweb//Root/scripts})
Clic droit et sélectionnez les Propriétés.
Sous Identité, sélectionnez « Utilisateur interactif ».
Sous Avancé, débogage, activez « Ouvrir dans le débogueur ».
Ceci vous affichera la ligne de commande de l'appel du débogueur.
De cette ligne, copiez la partie ID du processus (partie depuis. /ProcessId:{no ID du processus COM})

Étape 3

Dans D5, sous projets - paramètres mettez c:\winnt\system32\dllhost.exe comme application hôte et sous paramètres, l'ID du processus que vous avez copié : il doit commencer par /Processid:{ et finir avec }).

Étape 4

Avant de lancer le projet, il faut être sûr qu'aucune instance de la DLL ne tourne et qu'elle soit bien déchargée. Si ce n'est pas le cas :

- retournez dans le service des composants, clic droit sur le répertoire virtuel et sélectionnez « arrêter » ;

- retour dans le gestionnaire IIS et cliquez sur le bouton « décharger » dont nous avons parlé avant.

D'autre part, il ne faut pas oublier, dans Delphi, de mettre sous Projets / Options / Répertoire, le chemin complet de votre répertoire virtuel (en général, c:\inetpub\scripts). De cette façon, votre DLL se placera directement dans le bon répertoire.
Lancez la DLL (ctrl-F9). Lancez votre browser avec l'adresse de votre ISAPI (http://localhost/scripts/myisapi.dll).
À cette étape, si vous avez placé vos points d'arrêt, vous devriez voir Delphi se mettre en premier plan avec l'EDI stoppé à vos points d'arrêt.

Quand vous avez terminé votre séance de débogage, utilisez la commande « arrêter » du service des composants. Cela déchargera votre DLL. Vous pourrez alors recommencer, etc.

Étape 5

Une fois votre séance de débogage terminée, remettez sous Identié dans le service des composants l'utilisateur IWAM_…
Si vous avez des problèmes pour obtenir l'utilisateur Web par défaut, faites comme suit :

- retournez dans IIS, et sous les propriétés de votre répertoire virtuel, remettez la protection d'application à « basse » ;

- contrôlez que votre DLL est déchargée. Après avoir effectué cela, remettez ce paramètre à Haute et confirmez. Dans le service des composants, faites un Actualiser de Applications COM+ et vous devriez retrouver l'utilisateur par défaut.

Vous pouvez alors changer à nouveau la protection de l'application sous « moyenne » ou « basse ». En actualisant Application COM+ sous le service des composants, le IIS-{Monsiteweb//Root/scripts}) devrait disparaître.

IX - Envoyer un mail par ISAPI avec Winshoes

Utilise la suite Winshoes (actuellement Indy, qui a subi certains remaniements : http://www.nevrona.com/indy)

Var e_mail:TStringList;
S:String;
Mail1:TWinShoeMessage;
begin
e_mail:=TStringList.Create;
Try
S:='<HTML><body><CENTER>';
with TWinshoeSMTP.Create(nil) do
begin
try
Host := 'Votre serveur Mail';
UserId:='Votre nom';
Mail1 := TWinshoeMessage.Create(nil);
try
with e_mail do
begin
If Request.QueryFields.Values['EMAIL']='' then
Mail1.From:='Inconnu'
Else
Mail1.From:=Request.QueryFields.values['EMAIL'];
Mail1.Too.Add('mirella_diC@solovely.it');
Mail1.Subject:='Ciao bella';
Mail1.ContentType:='text/plain;charset=iso-8859-1';
Mail1.Text:=E_Mail;
// Dans ce cas, une forme avec plusieurs dialogues (on envoye tout)
E_mail.Assign(Request.QueryFields);
Mail1.Text:=E_Mail;
Try
Send(mail1);
Except
on E: Exception do
raise Exception.Create('Erreur à l''envoi.<BR>' + E.Message);
end;
S:=S+'<br>Message de '+Mail1.From+' envoyé à <br><center>'+Mail1.Too.Text
+'<br> si aucun message d''erreur au-dessus<BR></center>';
end;
except
on E: Exception do
raise Exception.Create('Erreur à l''envoi.<BR>' + E.Message);
end;
finally
Mail1.Free;
end;
Free;
end;
Finally
e_mail.Free;
S:=S+'</BODY></HTML>';
Response.Content:=S;
Handled:=True;
end;
end;

X - Quelques astuces

X-A - utiliser des tables maîtres/détails avec DatasetTableProducer

Une petite astuce toute bête que j'ai trouvée en retravaillant mon moteur de recherche NgScan et qui peut rendre de grands services.

Quand on utilise des Pageproducer orientés données, un des problèmes que l'on rencontre, lorsque l'on utilise des tables, est la difficulté d'afficher des champs provenant de tables détails. On peut utiliser des lookupfields, mais dans certaines circonstances ce n'est pas indiqué.

Pour s'affranchir de cette contrainte, il suffit de surcharger la propriété 'custom' de la colonne du DatasetTableProducer. En effet, cette propriété surcharge les paramètres de la balise <TD>. Elle agit ainsi : <TD custom>. Il nous suffit donc de commencer la chaîne custom par le caractère > pour fermer la balise et d'ajouter ensuite n'importe quel champ d'une table détail. Le code exemple :

Columns[0].Custom := '><FONT SIZE=-1><A HREF="/ng/ngscan.dll/images/basket?id='
+ TDetail.FieldByName('p_key').AsString + '"  target="droite">'
+ '<IMG SRC="/ng/images/bask.gif" border=0></A';
// Ne pas mettre le >, c'est le producteur de page qui s'en charge

X-B - Pour rediriger une page

Response.SendRedirect(MonNouvelURL);

X-C - Qui envoie la requête ?

Si vous voulez avoir des informations sur qui envoie la requête, regardez les propriétés RemoteAddret UserAgent de TwebRequest.

X-D - champ Memo

Si le Tag d'un PageProducer est à remplacer par un champ Memo, faites :

ReplaceText := Field.AsString

X-E - Récupérer la valeur d'un Checkbox

Si vous voulez récupérer la valeur d'un Checkbox :

if Request.ContentFields.IndexOfName('CBox') <> -1

X-F - Renvoyer un fichier en téléchargement

Si vous voulez renvoyer un fichier en téléchargement, rajoutez cette info afin que le nom du fichier apparaisse dans la boîte de dialogue :

Response.SetCustomHeader('Content-Disposition', 'filename=' +LeNomDuFichier);

X-G - Créer un splashscreen

Vous créez un fichier HTM d'attente (ex. TPageProducer)

procedure TWebModule1.WebModule1SplashAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
I: Integer;
S: string;
begin

Response.SendResponse;
S := PPageProducer.Content;
Request.WriteString(S);
// Donner le temps de lire quoi :-)
for I := 1 to 3 do
Sleep(3000);
// On envoie la page finale
Request.WriteString('<META HTTP-EQUIV="refresh" CONTENT="0; URL=http:&#47;/www.ornella.com">');
end;

X-H - S'assurer de poster dans une table avec clé primaire sans concurrence grâce aux criticalsections

Un exemple d'intégration d'enregistrements dans un SGBD client/serveur à partir d'une ISAPI.

Il s'agissait en fait d'évaluer les cours par des étudiants d'une école. Plusieurs cours (une vingtaine d'étudiants) ont été utilisés pour remplir un questionnaire (7 questions par cours, 1 champ de remarque libre et 27 cours + 4 champs de remarques libres) et chaque réponse était inscrite dans une table de Mssql7.

Le problème était évidemment d'éviter les erreurs lors de l'attribution de clés primaires. Une première version, basée sur une p_key auto-incrémentée par MsSQL n'arrivait pas à supprimer les concurrences dans l'attribution des clés primaires: deux utilisateurs arrivaient parfois à s'attribuer la même clé.

La solution a été de :

- créer une table des nouveaux enregistrements, et pour la p_key des réponses et pour celle des remarques ;

- protéger l'attribution de cette clé primaire par un « criticalsection » (merci JJM pour la suggestion :-)))

Dans l'unité du Webmodule, il suffit d'intégrer au fond de cette unit, avant le dernier end.

var FLock: TCriticalSection;
initialization
if not Assigned(fLock) then
fLock := TCriticalSection.Create;
finalization
fLock.Free;
end.

Puis dans le OnNewRecord de chaque table, la valeur id_newrec+1 de la table des enregistrements est attribuée au nouvel enregistrement en « l'entourant » d'un criticalsection :

procedure TWebEvalM.ReponsesNewRecord(DataSet: TDataSet);
begin
try
FLock.Enter;
NewRec.Open;
NewRec.Edit;
Reponses.FieldByName('ClefRéponse').AsInteger :=   NewRec.FieldByName('id_newRec').AsInteger + 1;
NewRec.FieldByName('id_NewRec').AsInteger := Reponses.FieldByName('ClefRéponse').AsInteger;
NewRec.Post;
NewRec.Close;
finally
FLock.Release;
end;
end;

14 822 enregistrements insérés en 1h30 sans aucun message d'erreur, avant, c'était « presque » la cata :-)

X-I - Afficher un MessageBox dans une ISAPI

MessageBox(0,PChar(sDialogCaption), PChar(sDialogMessage),
MB_SERVICE_NOTIFICATION or MB_TOPMOST);

À noter que cette boîte de dialogue s'affichera sur le serveur, non le client.

X-J - Désactiver le cache du browser

Rajouter avant d'envoyer la réponse :

Response.Expires := -1;
Response.CustomHeaders.Values['Cache-Control'] := 'no-cache';
Response.CustomHeaders.Values['Pragma'] := 'no-cache';

Conclusion / Liens

Good luck… hope it helps !

Et avant de partir, une dernière petite :

… et le premier qui dit que je fais une fixation sur elle, ben… il a raison ;-)

Liens

ASP avec Delphi:Sur le site de Michel Bardou

Composants ASP + Delphi : sur le site de JJM

CGI avec Delphi: Sur le site de Paul Toth

Midas et XML avec Delphi : Sur le site de DrBob (en anglais)

Vous cherchez un hébergeur pour vos applis IntraWeb ? Tout est là, et ce n’est pas cher :-))))

Mon moteur de recherche Ngscan: http://www.ngscan.com

Newsgroup nzn sur Internet : news://news.vienneinfo.org/nzn.fr.delphi.Internet