Vous êtes sur la page 1sur 48

REST et Delphi 2010

Un livre blanc de Marco Cantù (http://blog.marcocantu.com)

Novembre 2009

Corporate Headquarters EMEA Headquarters Asia-Pacific Headquarters


100 California Street, 12th Floor York House L7. 313 La Trobe Street
San Francisco, California 94111 18 York Road Melbourne VIC 3000
Maidenhead, Berkshire Australia
SL6 1SF, United Kingdom

Copyright © 2009 Marco Cantu. All Rights Reserved.


SYNTHÈSE EXÉCUTIVE
La nouvelle architecture REST (« Representational State Transfer ») a un impact significatif
sur l'ensemble de l‟industrie et la plupart des nouveaux services Web « grand-public »
conçus par les acteurs majeurs du secteur (Google, Yahoo, Amazon et aussi Microsoft)
utilisent cette technologie pour partager et fusionner des informations multisources.
L‟implémentation de l‟architecture REST ne s‟appuyant que sur des technologies simples
(HTTP et XML), Delphi lui a toujours offert un support de choix ; sa dernière version
(Delphi 2010) le complète d‟une prise en charge spécifique pour le développement de
serveurs REST – dans l‟infrastructure DataSnap.
Cette étude revient sur les technologies REST impliquées dans une perspective Delphi et
démontre comment créer des applications clientes pour des sites Web populaire et comment
construire des serveurs REST avec le support spécifique offert par Delphi 2010.

INTRODUCTION
La dernière décennie a vu l‟explosion du Web et plus récemment l‟apparition du Web 2.0.
Les interactions automatiques entre sites Web, sites et applications clientes ou entre sites
Web et bases de données métier sont en revanche plus récentes et exigent généralement
des interconnexions globales parfois difficiles à comprendre.
La vitesse de déplacement et de rafraîchissement des données sur le Web est devenue bien
supérieure à la capacité de consultation de chacun ; d‟où une forte demande pour les
programmes permettant de localiser, suivre et superviser des informations multisources
(donnes commerciales, informations financières, communautés en ligne, campagnes
marketing, etc.).
POURQUOI DES SERVICES WEB ?
L‟émergence des services Web permet d‟envisager de nouveaux modes d‟utilisation
d‟Internet par les entreprises... Naviguer sur différentes pages pour passer une commande
est suffisant pour les environnements B2C (business-to-consumer) mais certainement pas
pour les contextes B2B (business-to-business) plus sophistiqués. Par exemple, il est
généralement très ergonomique pour un utilisateur de rechercher et acheter des livres sur un
site marchand ; en revanche, si vous dirigez une librairie en ligne passant des centaines de
commandes par jour, la même approche serait totalement inefficace – surtout si d‟autres
applications déterminent les réapprovisionnements en fonction des ventes… Ressaisir ces
données dans une autre application serait en effet complètement ridicule...
Les services Web ont (ou du moins avaient à l‟origine) précisément pour vocation de
résoudre ce problème. Par exemple, lorsque le programme crée automatiquement une
demande de réapprovisionnement de livres ; il l‟adresse ensuite à un service Web qui
retourne immédiatement les informations sur la commande. L‟étape suivante consistant par
exemple à demander un numéro de suivi de livraison. À ce stade, le programme peut utiliser
un autre service Web pour suivre la livraison jusqu‟à sa destination – et informer le client sur
le temps d‟attente. À la livraison, le programme peut également adresser une information
(par SMS/Pager/Twitter) aux personnes ayant des commandes en attente, déclencher un
paiement avec un service Web bancaire, etc. Cet exemple donne une bonne idée de la
cinématique générale : les services Web sont à l‟interopérabilité des systèmes ce que le
Web et l‟e-mail sont aux interactions entre individus...
Le périmètre des services Web est extrêmement étendu et implique de multiples
technologies et standards. Je me concentrerai donc sur l‟implémentation Delphi sous-jacente
et sur les aspects techniques des services Web – sans aborder leurs implications métier au
sens large. Delphi for Win32 propose des solutions très sophistiquées pour prendre en
charge les services Web – dérivées initialement de SOAP – et qui peuvent aujourd‟hui être
facilement étendues à travers des composants HTTP et REST.
TECHNOLOGIES CLÉS DES SERVICES WEB : SOAP ET REST
Le concept de service Web est en soi plutôt abstrait. Technologiquement, deux solutions
attirent principalement les développeurs : le standard SOAP (Simple Object Access Protocol,
voir http://www.w3.org/TR/soap/) et REST (Representational State Transfer) avec sa
variante XML-RPC (XML-Remote Procedure Call).
Ces deux solutions utilisent généralement HTTP comme protocole de transmission (bien qu‟il
existe des alternatives) et XML (ou JSON) pour les déplacements bidirectionnels de
données. Le protocole HTTP standard permet au serveur Web de gérer les requêtes et de
faire transiter les paquets de données à travers les pare-feux.
Ce livre blanc n‟aborde pas les détails de SOAP et porte exclusivement sur REST. Je
commencerai par définir les fondations théoriques, je présenterai ensuite un exemple simple
de serveur et de client, je reviendrai en détail sur le développement de clients REST pour
des services Web REST particulièrement populaires et je conclurai sur le support serveur
REST offert par Delphi 2010 – en tant qu‟extension de l‟architecture DataSnap.

LES CONCEPTS SOUS-JACENTS DE


REST (REPRESENTATIONAL STATE
TRANSFER)
Même si l‟idée générale de REST est relativement ancienne, sa formalisation et la théorie
sous-jacente sont plus récentes. Il convient avant tout de souligner qu‟il n‟existe pas de
standard REST formel.
REST est l‟acronyme de « Representational State Transfer », initialement employé par Roy
Fielding dans son mémoire de Ph.D. de 2000, ce terme a été rapidement adopté pour
désigner les accès aux données par le Web à travers HTTP et des URL – plutôt qu‟en
s‟appuyant sur le standard SOAP.
Initialement, le terme REST se référait à un style architectural décrivant la relation entre un
navigateur et un serveur Web. L‟idée étant que lorsqu‟on accède à une ressource Web (avec
un navigateur ou une application cliente spécifique) le serveur retourne une représentation
de ladite ressource (page, image, données brutes, etc.). Le client recevant cette
représentation est défini dans un état donné ; lorsqu‟il accède à d‟autres informations ou
pages (par exemple via un lien) son état change lors du transfert.
Dans les propres mots de Roy Fielding : « REST retranscrit la rationalité de comportement
d’une application Web bien conçue : un réseau de pages Web (ou machine d’état virtuelle)
où l’utilisateur progresse à travers l’application en sélectionnant des liens (transitions d’état)
pour afficher la page suivante (état applicatif suivant) transféré et restitué à l’utilisateur pour
manipulation. »
POINTS CLÉS DE L‟ARCHITECTURE REST
Si REST est bien une architecture (ou mieux : un « style architectural »), ce n‟est clairement
pas un standard (bien qu‟il s‟appuie sur des standards existants : HTTP, URL, différents
types de formats pour les données, etc.). Alors que SOAP se superpose à HTTP et XML ;
les architectures REST utilisent HTTP et XML (et d‟autres formats) exactement comme ils
sont :
 REST utilise des URL pour identifier une ressource serveur (SOAP utilise une seule
URL pour plusieurs requêtes, détaillées dans l‟enveloppe SOAP). On retiendra que
l‟URL identifie une ressource et non une opération sur la ressource.
 REST recourt à des méthodes HTTP pour indiquer les opérations à effectuer
(récupérer ou HTTP GET, créer ou HTTP PUT, mettre à jour ou HTTP POST et
effacer ou HTTP DELETE)
 REST utilise des paramètres HTTP (comme paramètres de requête et POST) pour
fournir des informations supplémentaires au serveur.
 REST s‟appuie sur HTTP pour l‟authentification, le cryptage et la sécurité (avec
HTTPS)
 REST retourne les données sous forme de documents bruts en différents formats
Mime (XML, JSON, images, etc.)
Dans ce type de scénario de nombreux éléments architecturaux doivent être pris en
considération. REST exige que les systèmes soient :
 Client/serveur par nature (pas de SGBDR à ce niveau)
 Sans état
 Adaptés à la gestion en cache (la même URL doit retourner les mêmes données si
elle est appelée deux fois en séquence – sauf si les données ont changé côté
serveur), afin de permettre l‟insertion de serveurs de proxy et de cache entre le client
et le serveur. Le corollaire est que les opérations GET ne doivent pas avoir d‟effets
induits.
La théorie de REST est naturellement plus complète que la présentation précédente qui
constitue cependant un bon point de départ. Les exemples pratiques qui suivent sont
accompagnés du code Delphi et devraient clarifier ces concepts élémentaires.

DELPHI ET LES TECHNOLOGIES REST


Comme nous l‟avons vu, il n‟existe pas de standard REST et le développement REST
n‟exige aucun outil spécifique ; cependant, quelques standards connexes méritent une brève
description (sachant que chacun d‟entre eux pourrait faire l‟objet une étude à part entière…).
Une fois encore, nous envisagerons ces technologies sous l‟angle de la prise en charge par
Delphi.
HTTP (CLIENT ET SERVEUR)
Le protocole HTTP (HyperText Transfer Protocol) est au cœur du Web et ne nécessite pas
une plus ample présentation ; notons simplement qu‟il peut être utilisé avec des navigateurs
mais aussi avec n‟importe quelle autre application.
Avec Delphi, le moyen le plus simple d‟écrire une application cliente utilisant HTTP consiste
à répondre sur le composant client IdHttp (ou Indy HTTP). En appelant la méthode GET de
ce composant en fournissant une URL comme paramètre, il est possible de récupérer le
contenu de n‟importe quelle page Web et de nombreux serveurs REST. Il est parfois
nécessaire de définir d‟autres propriétés pour fournir des informations d‟authentification ou
attacher un deuxième composant pour le support SSL (nous le verrons par des exemples).
Ce composant prend alors en charge les différentes méthodes HTTP – au-delà de GET.
Notons que pour des raisons de sécurité, les requêtes IdHttp doivent être réalisées dans un
thread dans la mesure où la suite Indy utilise des threads bloquants pouvant geler l‟interface
jusqu‟au retour des requêtes (ce qui peut être long si le serveur Web est lent ou en cas de
transferts volumineux). Dans les démonstrations de cette étude, nous n‟utiliserons pas de
threads pour des raisons de simplicité – mais c‟est bien l‟approche recommandée.
Du côté du serveur, il est possible d‟utiliser de multiples architectures pour créer un serveur
Web ou une extension de serveur Web dans Delphi. Pour un serveur Web autonome, il est
possible d‟utiliser le composant IdHttpServer et pour créer des extensions de serveur Web
(applications CGI, ISAPI ou modules Apache) le framework WebBroker. Une autre option est
possible grâce au support HTTP de DataSnap dans Delphi 2010.
XML
Le langage XML (Extended Markup Language) est un format commun de données ; de
nombreux serveurs REST utilisent pourtant d‟autres structures comme JSON (JavaScript
Object Notation) et parfois même des fichiers en texte brut, délimités par des virgules. Une
fois encore, la technologie XML est bien connue et je n‟y reviendrai pas en détail.
Delphi permet de traiter les documents XML avec le composant XmlDocument. Il s‟agit d‟un
encapsuleur destiné à l‟un des moteurs XML DOM disponibles (par défaut Microsoft XML
DOM). Lorsqu‟un document est chargé, il est possible de naviguer à travers les nœuds de sa
structure ou d‟effectuer des requêtes avec XPath (ma méthode préférée).
XPATH
XPath est un langage de requête pour identifier et traiter les nœuds d‟un document XML. La
notation utilisée par XPath ressemble à un chemin d‟accès simple (/racine/nœud1/nœud2)
avec des crochets pour exprimer des conditions sur les attributs des nœuds ou les sous-
nœuds (racine/nœud[@val=5]) ou même des expressions complexes. Le résultat d‟une
déclaration XPath peut être en soi une expression (comme le nombre de nœuds répondant à
la règle ou la valeur totale d‟un ensemble de nœuds).
Delphi permet d‟exécuter une requête XPath en l‟appliquant au DOM hébergeant le
document (si le DOM spécifique le prend en charge). Nous verrons un exemple d‟utilisation
d‟XPath dans la première démonstration ci-dessous.

CLIENTS REST ÉCRITS EN DELPHI


Le Web propose une quantité impressionnante d‟exemples de serveurs REST (dont les
fameux Amazon Web Service, devenus « Amazon E-Commerce Service » depuis
qu‟Amazon Web Service est utilisé pour l‟offre de Cloud Computing) permettant d‟accéder
aux informations en utilisant une structure de données XML plutôt que le format HTML.
Même si beaucoup de services Web utilisent REST sur Internet, la plupart des services Web
exigent du développement (comme indiqué dans les démos suivantes) et très peu offrent un
véritable accès gratuit et ouvert. Pour une liste légèrement différente des clients REST
Delphi et pour obtenir le code source de ces démonstrations, vous pouvez vous reporter aux
sections spécifiques de l‟un de mes sites Web :
http://ajax.marcocantu.com/delphirest/default.htm

CLIENT REST POUR LES FLUX RSS


Les formats les plus diffusés pour distribuer des informations XML sont RSS et ATOM ; ces
technologies sont plutôt liées aux blogs et aux sites d‟information mais peuvent être utilisées
avec n‟importe quelle source de données. L‟intérêt de ces technologies de diffusion réside
dans leur capacité à fournir aux applications clientes les mêmes informations que celles
auxquelles l'utilisateur accéderait généralement avec un navigateur Web. Les informations
de ces flux sont traitées par les applications clientes et peuvent même être combinées dans
une synthèse de flux similaires (comme sur le site Delphi Feeds).
Pour le premier exemple d‟application cliente utilisant REST, j‟ai écrit un client RSS
extrêmement simple recherchant dans les blogs Delphi sur http://www.delphifeeds.com.
Chaque fois que l‟on accède à des données XML dynamiques avec une URL et que l‟on peut
modifier l‟URL pour accéder à des données différentes on utilise bien l‟approche REST.
APPEL REST ET DOCUMENT XML
Le programme RssClient utilise un composant IdHttp et un composant XMLDocument.
Le premier est utilisé pour récupérer les données sur le Web (c'est-à-dire par appel client
REST) et les charger dans le second :
var
strXml: string;
begin
strXml := IdHTTP1.Get
('http://feeds.delphifeeds.com/delphifeeds');
XMLDocument1.LoadFromXML(strXml);

Les données extraites doivent ressembler à ce qui suit (légèrement simplifié pour plus de
lisibilité) lorsqu‟elles sont affichées dans un éditeur XML :
TRAITEMENT DES DONNÉES RSS AVEC XPATH
Pour extraire les informations requises dans ce document XML, le programme RssClient
utilise des expressions XPath. Par exemple, pour lire le titre de la première contribution du
blog (item) de la liste :

/rss/channel/item[1]/title

Cette opération est réalisée en cycle, parallèlement à l‟extraction d‟autres informations


formatées et affichées dans une liste déroulante. L‟utilisation de XPath nécessite une
interface spécifique pour le moteur Microsoft (lien avec l„interface étendue
IDOMNodeSelect).
Dès qu‟il dispose des nœuds, le programme recherche tous les nœuds texte enfants avec
une fonction d‟aide getChildNodes créée à cet effet et ajoute les données à une zone de
liste. Ci-après le code intégral de la méthode exécutée en cliquant sur le bouton de mise à
jour du programme (« Update ») :
procedure TRssForm.btnUpdateClick(Sender: TObject);
var
strXml, title, author, pubDate: string;
I: Integer;
IDomSel: IDOMNodeSelect;
Node: IDOMNode;
begin
strXml := IdHTTP1.Get
('http://feeds.delphifeeds.com/delphifeeds');

XMLDocument1.LoadFromXML(strXml);
XMLDocument1.Active := True;
IDomSel := (XMLDocument1.DocumentElement.DOMNode
as IDOMNodeSelect);
for I := 1 to 15 do
begin
Node := IDomSel.selectNode(
'/rss/channel/item[' + IntToStr (i) + ']/title');
title := getChildNodes (Node);
Node := IDomSel.selectNode(
'/rss/channel/item[' + IntToStr (i) + ']/author');
author := getChildNodes (Node);
Node := IDomSel.selectNode(
'/rss/channel/item[' + IntToStr (i) + ']/pubDate');
pubDate := getChildNodes (Node);
ListBox1.Items.Add(author + ': ' + title + ' [' +
pubDate + ']');
end;
end;
L‟écran ci-dessous présente les effets de l‟exécution de ce programme :
DES CARTES ET EMPLACEMENTS
L‟accès à des éléments de cartographie et de positionnement est très souvent nécessaire –
pour toutes les applications incluant une ou plusieurs adresses ; de multiples informations
cartographiques sont accessibles sur différents sites majeurs (Google, Yahoo, Microsoft,
etc.).
SERVICE DE CODAGE GÉOGRAPHIQUE DE
GOOGLE
Nous commencerons par le service de géocodification de Google permettant de soumettre
une adresse et de récupérer ses coordonnées de latitude/longitude à partir d‟une requête
telle que :

http://maps.google.com/maps/geo?q=[address]&output=[format]
&key=[key]

Il est également possible d'entrer une URL similaire dans son navigateur à des fins de test
(voir ci-après les coordonnées de New York dans un navigateur en format XML) :

Mon exemple de géolocalisation s‟appuie sur les adresses d‟entreprise de la base de


données exemple Customer.cds fournie avec Delphi (utilisant une copie locale, dans le
fichier ZIP avec le code source du projet).
Comme la plupart de ces services, l‟utilisation est gratuite pour un usage limité (le
programme intègre quelques appels sleep() supplémentaires pour ne pas atteindre le taux
maximal par minute) mais exige un enregistrement pour le service considéré sur
http://code.google.com.
Le programme de démonstration exige que vous ajoutiez votre devkey à un fichier
GeoLocation.ini devant résider dans le répertoire des documents de l‟utilisateur ; sa structure
est simple :
[googlemap]
devkey=

RÉSOLUTION ADRESSE CLIENT


Le programme fonctionne en deux étapes :
 Tout d‟abord, il recherche des noms uniques de villes/états/pays en scannant le
composant ClientDataSet et en complétant une liste de chaîne. Ce code n‟étant pas
lié à REST ne figure pas dans ce document.
 La deuxième étape consiste à rechercher chaque ville avec le service Google
Geocoding et à compléter un composant mémoire ClientDataSet avec les
informations résultantes.
Cette fois, plutôt que de demander la version XML des données, nous recourons à un format
CSV plus simple – que le programme analyse avec un objet StringList.
Ci-dessous, le code Geocoding :
procedure TFormMap.btnGeocodingClick(Sender: TObject);
var
I: Integer;
strResponse, str1, str2: string;
sList:TStringList;
begin
cdsTown.Active := False;
cdsTown.CreateDataSet;
cdsTown.Active := True;
sList := TStringList.Create;

for I := 0 to sListCity.Count - 1 do
begin
ListBox1.ItemIndex := I;
if Length (sListCity.Names[I]) > 2 then
begin
strResponse := IdHTTP1.Get( TIDUri.UrlEncode(
'http://maps.google.com/maps/geo?q=' +
(sListCity.Names[I]) + '&output=csv&key=' +
googleMapKey));
sList.LineBreak := ',';
sList.Text := strResponse;
str1 := sList[2];
str2 := sList[3];
cdsTown.AppendRecord([sListCity.Names[I],
StrToFloat (str1), StrToFloat (str2),
Length (sListCity.ValueFromIndex[I])]);
Sleep (150);
Application.ProcessMessages;
end;
end;
sList.Free;
end;
Le résultat doit ressembler à la copie d‟écran qui suit :
YAHOO MAPS
L‟étape suivante peut consister à accéder à la véritable carte correspondant à une adresse.
Google Maps propose d‟innombrables fonctionnalités conçues pour être hébergées sur les
sites Web plutôt que dans les applications clientes. (Je propose néanmoins un exemple
d‟hébergement Google Map dans un programme client ; l‟architecture et le code en sont
néanmoins complexes et ne sont pas présentés dans cette étude).
Ce nouvel exemple dénommé YahooMaps utilise l‟API Yahoo Map pour obtenir une carte et
l‟afficher dans un contrôle Image. Les informations sur cette API REST et le lien pour obtenir
un identifiant gratuit pour Yahoo Application sont accessibles sur :
http://developer.yahoo.com/maps/
Une fois encore, cet identifiant est requis pour exécuter les programmes et enregistrer un
fichier INI spécifique dans le répertoire « Documents utilisateur » dénommé YahooMaps.ini.
La carte est récupérée en deux étapes : le premier appel HTTP passe l‟adresse et reçoit
l‟URL de l‟image de la carte ; le deuxième appel HTTP la récupère. Une fois encore, ces
deux étapes peuvent être simulées dans un navigateur Web – très utile pour le débogage.
Le programme utilise la base de données et l‟élément StringList de l‟exemple précédent. Il
intègre également un bouton permettant d‟afficher la carte ou une ville définie (par exemple,
San José, Californie) avec la méthode suivante :
const
BaseUrl = 'http://api.local.yahoo.com/MapsService/V1/';
procedure TFormMap.Button1Click(Sender: TObject);
var
strResult: string;
memStr: tFileStream;
begin
strResult := IdHTTP1.Get(BaseUrl +
'mapImage?' +
'appid=' + yahooAppid +
'&city=SanJose,California');
XMLDocument1.Active := False;
XMLDocument1.XML.Text := strResult;
XMLDocument1.Active := True;
strResult := XMLDocument1.DocumentElement.NodeValue;
XMLDocument1.Active := False;

// maintenant, nous récupérons l’image référencée


memStr:= TFileStream.Create ('temp.png', fmCreate);
IdHttp1.Get(strResult, memStr);
memStr.Free;

// Chargement de l‘image
Image1.Picture.LoadFromFile('temp.png');
end;

La première requête GET HTTP fournit la requête réelle et retourne un document XML
avec l‟URL de l‟image de la carte comme suit (identifiant long omis) :

<Result>
http://gws.maps.yahoo.com/mapimage?MAPDATA=[...]&amp;mvt=m
&amp;cltype=onnetwork&amp;.intl=us&amp;appid=[...]
&amp;oper=&amp;_proxy=ydn,xml
</Result>
C‟est la raison pour laquelle le programme peut extraire la valeur du seul nœud avec le
code :
XMLDocument1.DocumentElement.NodeValue
Finalement, l‟image est enregistrée dans un fichier temporaire et chargé dans un contrôle
Image. Au-delà de la carte de cette ville spécifique, le programme peut également retrouver
celles de la base de données Customer.cds de l‟exemple précédent. Un bouton permet
d‟obtenir la carte de l‟emplacement spécifiquement indiqué (San José, exemple de la
démonstration Delphi Live).
En voici un exemple :

API GOOGLE TRANSLATE


Google propose un autre exemple d‟API REST avec son service de traduction (« Google
Translate REST API » dont la documentation est disponible sur
http//code.google.com/apis/ajaxlanguage/documentation/

Dans ce cas, il est inutile d‟obtenir une clé de signature (ni de fichier INI) ; il suffit de fournir
un site référent (bien que tout semble fonctionner même sans cette information). Il est
possible de demander une traduction dans son navigateur Web en entrant une URL du type :
http://ajax.googleapis.com/ajax/services/language/translate?
v=1.0&q=What%20a%20nice%20day&langpair=en|de
Le résultat de cet appel est présenté ci-dessous (avec résultat JSON pour plus de lisibilité) :

{
"responseData":
{
"translatedText":"Was für ein schöner Tag"
},
"responseDetails": null,
"responseStatus": 200
}

Cet exemple accomplit une étape de plus par rapport aux précédentes démonstrations. Au
lieu d‟une requête HTTP, il utilise un composant VCL spécifique, appelé par une méthode de
classe (pour éviter de placer le composant dans un formulaire -- même si cela est possible).
Ce composant de support rend l‟API extrêmement simple d‟emploi et encapsule
intégralement l‟appel HTTP.
COMPOSANT DE TRADUCTION
Ci-après la déclaration de classe du composant :
type
TBabelGoogleRest = class (TComponent)
protected
Http1: TIdHttp;
FFromLang: string;
FToLang: string;
FActiveInForm: Boolean;
procedure SetFromLang(const Value: string);
procedure SetToLang(const Value: string);
public
function DoTranslate (strIn: string): string; virtual;
constructor Create(AOwner: TComponent); override;

class function Translate (strIn, langIn, langOut:


string): string;
published
property FromLang: string read FFromLang write
SetFromLang;
property ToLang: string read FToLang write SetToLang;
end;

Le traitement effectif se situe dans la fonction DoTranslate :

function TBabelGoogleRest.DoTranslate(strIn: string): string;


var
strUrl, strResult: string;
nPosA, nPosB: Integer;
begin

strUrl := Format (
'http://ajax.googleapis.com/ajax/services/language/tran
slate?' +
'v=1.0&q=%s&langpair=%s',
[TIdUri.ParamsEncode (strIn),
FFromLang + '%7C' + FToLang]); // Format n’aime pas du
tout %7 !!!
strResult := Http1.Get(strUrl);

nPosA := Pos ('"translatedText":', strResult); // Début


des données JSON
if nPosA = 0 then
begin
nPosA := Pos ('"responseDetails":', strResult);
nPosA := nPosA + Length ('"responseDetails":');
end
else
nPosA := nPosA + Length ('"translatedText":');

nPosA := PosEx ('"', strResult, nPosA) + 1; // Guillemets


ouvrants
nPosB := PosEx ('"', strResult, nPosA) - 1; // Guillemets
fermants
Result := Copy (strResult, nPosA, nPosB - nPosA + 1);
end;
Le résultat de la requête à l‟URL indiquée est en format JSON (considéré comme une API
Javascript par Google). J‟ai choisi de traiter manuellement l‟information JSON mais j‟aurais
pu utiliser le nouveau support de mappage JSON vers objet de Delphi 2010 que nous
aborderons plus tard. La méthode effective peut être appelée par une méthode de classe
créant un objet temporaire, définissant ses propriétés et appelant la fonction DoTranslate.
D‟autres scénarii sont possibles mais le code restant doit rester simple à comprendre.
Le formulaire principal du programme de démonstration dispose d‟une boîte de liste
proposant tous les langages supportés.
L‟exemple effectue une traduction à partir de l‟anglais mais peut être naturellement inversé.
Théoriquement, toute combinaison de deux langues devrait fonctionner ; en pratique ce n‟est
pas toujours le cas… Lors de la demande d‟une traduction, les résultats sont ajoutés à un
journal. Ici, l‟appel est appliqué au premier groupe de langues (par ordre alphabétique) :
TWITTER – AUSSI SIMPLE QUE POSSIBLE...
L‟interface de Twitter a largement contribué à l‟essor de ce site et a donné naissance à un
écosystème complet d‟applications interfacées avec Twitter. L‟interface de service Web
REST de Twitter est aussi simple que le service l‟est pour ses utilisateurs. Twitter a été
essentiellement développé en Ruby – et Ruby on Rails n‟est certainement pas étranger à
l‟idée de mise en avant de REST en raison de son mappage clair des URL avec les
ressources applicatives internes -- une des fondations de l‟approche REST.
Si vous disposez d‟un compte Twitter vous pouvez accéder à vos 20 dernières entrées et
aux 20 dernières entrées générales avec les URL suivantes :
http://twitter.com/statuses/user_timeline.xml
http://twitter.com/statuses/public_timeline.xml

La seule condition est de réaliser une requête GET HTTP passant l‟en-tête HTTP standard –
avec le nom utilisateur et le mot de passe – une opération extrêmement simple à ajouter
dans Delphi aux propriétés correspondantes d‟un composant IdHttp. Une opération plus
compliquée, si l‟on peut dire, consiste à poster une mise à jour d‟état sur votre compte. Cette
opération est réalisée avec une requête POST HTTP passant un paramètre d‟état comme
dans le snippet qui suit, permettant d‟adresser à Twitter le texte de l‟un des champs de
l‟enregistrement actuel d‟un composant ClientDataSet (ClientDataSet1TEXT) et de le
marquer comme « posté » :
procedure TForm34.btnPostCurrentClick(Sender: TObject);
var
strResult: string;
listParams: TStringList;
begin
listParams := TStringList.Create;
listParams.Add('status=' + ClientDataSet1TEXT.AsString);
try
strResult := idhttp1.Post(
'http://twitter.com/statuses/update.xml',
listParams);
ShowMessage (strResult);
ClientDataSet1.Edit;
ClientDataSet1POSTED.AsString := 'T';
ClientDataSet1.Post;
finally
listParams.Free;
end;
end;
Ce code est extrait de mon application « Delphi Tweet of the Day » qui utilise un composant
ClientDataSet (local ou raccroché à un serveur distant) pour saisir les Tweets. Après avoir
posté la valeur du champ TEXT de la table, en réalité, le programme indique « Vrai » dans le
champ POSTED.
Il y a d‟autre code dans le programme mais rien qui soit réellement lié à Twitter ni à son API
REST. L‟élément suivant pertinent est la configuration du composant IdHttp :
object IdHTTP1: TIdHTTP
Request.ContentLength = -1
Request.Accept = 'text/html, */*'
Request.BasicAuthentication = True
Request.Password = '***' // Omis mais requis
Request.UserAgent = 'tweetoftheday'
Request.Username = 'delphitweetday'
HTTPOptions = [hoForceEncodeParams]
end
En d‟autres termes, le codage Twitter est extrêmement simple et permet de bénéficier d‟une
mise en ligne automatique à intervalles fixes ou en cas de survenance d‟un événement
pertinent pour vos activités (et dans votre base de données).

INTERFAÇAGE AVEC LES SERVICES GOOGLE


SPREADSHEET
Si l‟intégration de cartes, de traductions et de communications Twitter peut devenir partie
intégrante de votre marketing d‟entreprise, il deviendra de plus en plus important au fil du
temps de pouvoir interagir avec des composants « métier » tels que Google Docs
(http://docs.google.com/) dont l‟interface de Service Web permet de télécharger et récupérer
(voire même convertir) des documents Web et de contrôler leurs habilitations d‟accès. Des
fonctionnalités utiles qui peuvent être complétées grâce à l‟interface permettant de travailler
avec le contenu d‟un document Web : l‟API Google Spreadsheet Services (encore en phase
bêta lors de la rédaction de ce document).
Cette API permet d‟accéder à chaque cellule de n‟importe quelle feuille de calcul du système
(privée ou publique). Toute personne recherchant ces documents avec une interface Web
peut ainsi consulter les changements en temps réel ! Dans la mesure où cette API (comme
beaucoup d‟autres API Google) permet d‟accéder aux documents personnels et réservés,
elle exige une couche de sécurité plus robuste que les API REST utilisées jusqu‟à présent.
Comparativement à des solutions plus simples, il existe deux différences notables :
L‟API utilise HTTPS plutôt que HTTP. Il est donc nécessaire de référencer OpenSSL ou une
autre bibliothèque SSL à l‟application cliente et d‟appeler le code de support Indy adapté.
L‟API exige une requête d‟authentification séparée, qui retournera un jeton d‟habilitation à
durée limitée. Ce jeton devra ensuite être inclus dans toutes les requêtes ultérieures
(émanant de la même adresse IP).
Avant d‟envisager le problème spécifique qui nous préoccupe, il est nécessaire d‟examiner
cette infrastructure (en gardant à l‟esprit que ces classes de gestion des habilitations ont été
établies pour une autre catégorie de Services Web Google, l‟interface Provisioning API,
utilisée pour créer des comptes dans des domaines Google privés et payants de messagerie
et d‟échange documentaire).
Pour supporter l‟authentification et émettre une requête à partir de ce support, j‟ai créé une
classe Delphi spécifique, TGoogleAuth, qui dispose de l‟interface publique suivante (les
multiples méthodes privées d‟accès aux champs et propriétés ont été supprimées) :
type
TGoogleAuth = class
public
property GoogleEmail: string
read FGoogleEmail write SetGoogleEmail;
property GooglePwd: string
read FGooglePwd write SetGooglePwd;

property AuthString: string


read GetAuthString write SetAuthString;
property ReqTime: TDateTime
read FReqTime write SetReqTime;
property AccountType: string
read FAccountType write SetAccountType;
property ServiceName: string
read FServiceName write SetServiceName;
public
function Expired: Boolean;
procedure Renew;
end;
Quatre de ces propriétés (e-mail, mot de passe, type de compte et nom de service) sont des
valeurs d‟entrée – les deux autres correspondant à la chaîne d‟habilitation et sa date de
création, utilisées pour permettre son renouvellement automatique un certain nombre de fois.
Lorsque ce jeton est demandé, la classe vérifie s‟il est nécessaire d‟en obtenir un nouveau. Il
s‟agit de la méthode « getter » de la propriété AuthString :
function TGoogleAuth.GetAuthString: string;
begin
if FAuthString = '' then
Renew;
if Expired then
Renew;
Result := FAuthString;
end;
Le code de requête d‟habilitation est intégré à la méthode « Renew », qui utilise une
connexion SSL HTTP pour solliciter le jeton d'habilitation transmettant le nom d'utilisateur et
mot de passe en format crypté :
procedure TGoogleAuth.Renew;
var
res, req: String;
sList: TStringList;
IdHttp: TIdHttp;
begin
FAuthString := '';

IdHttp := TIdHttp.Create (nil);


try
IdHttp.IOHandler :=
TIdSSLIOHandlerSocketOpenSSL.Create(IdHttp);
req :=
'https://www.google.com/accounts/ClientLogin?Email=' +
FGoogleEmail + '&Passwd=' + FGooglePwd +
'&accountType=' + FAccountType + '&service=' +
FServiceName;

res := IdHttp.Get (req);


finally
idHttp.Free;
end;

sList := TStringList.Create;
try
sList.Text := res;
FAuthString := sList.Values['Auth'];
FReqTime := Now;
finally
sList.Free;
end;
end;
J‟ai créé un service global de type « singleton » pour cette classe ; chaque fois qu‟une
requête est nécessaire, j‟applique une méthode « helper » (fonction globale), qui ajoute le
jeton supplémentaire. Voici le code de cette (assez longue) fonction :
function DoRequest (
const methodAttr, fromAttr, strData: string): string;
var
res: String;
postStream: TStream;
IdHttp: TIdHttp;
resStream: TStringStream;
begin
IdHttp := TIdHttp.Create (nil);
try
// Ajoût autorisation de la clé enregistrée
IdHttp.Request.CustomHeaders.Values ['Authorization'] :=
'GoogleLogin auth=' + googleAuth.AuthString;
IdHttp.Request.CustomHeaders.Values ['Content-type'] :=
'application/atom+xml';
IdHttp.Request.CustomHeaders.Values ['GData-Version'] :=
'2';
// Utilisation SSL
IdHttp.IOHandler :=
TIdSSLIOHandlerSocketOpenSSL.Create(IdHttp);
try
if (methodAttr = 'post') or (methodAttr = 'put') then
begin
postStream := TStringStream.Create (strData);
try
postStream.Position := 0;
if (methodAttr = 'post') then

res := IdHttp.Post (fromAttr, postStream)


else // (methodAttr = 'put')
res := IdHttp.Put (fromAttr, postStream);
finally
PostStream.Free;
end;
end
else if (methodAttr = 'delete') then
begin
resStream := TStringStream.Create ('');
try
IdHttp.DoRequest (hmDelete, fromAttr, nil,
resStream);
finally
resStream.Position := 0;
res := resStream.DataString;
resStream.Free;
end;
end
else // 'get' ou non affecté
res := IdHttp.Get (fromAttr);
except
on E: Exception do // Interception et utilisation comme
résultat
begin
res := E.Message;
end;
end;
finally
IdHttp.Free;
end;
Result := res;

Bien qu‟un peu lourd, ce code structurel est précieux pour simplifier les appels SSL habilités.
Ainsi, pour requérir une liste des feuilles de calcul privées, on utilisera le code suivant :
DoAppRequest (
'get',
'http://spreadsheets.google.com/feeds/spreadsheets/private
/full',
'');
Ce code fait partie de la démonstration, qui utilise les informations disponibles dans le fichier
dbtosheet.ini – structuré comme suit et chargé d‟exécuter le programme de démo :
[google]
email=
password=
accounttype=GOOGLE
Le fichier est chargé au démarrage du programme et les trois valeurs de l‟arborescence sont
utilisées pour renseigner l'objet TGoogleAuth, désigné googleAuth. Le quatrième paramètre
– le service – est défini par le programme :
googleAuth.GoogleEmail :=
inifile.ReadString('google', 'email', '');
googleAuth.GooglePwd :=
inifile.ReadString('google', 'password', '');
googleAuth.AccountType :=
inifile.ReadString('google', 'accounttype', 'GOOGLE');
googleAuth.ServiceName := 'wise';
Dans cette configuration, le programme dispose d‟un premier bouton permettant de requérir
la liste de feuilles de calcul disponibles et d'ajouter leurs identifiants (ou URL) dans une boîte
liste. L‟objet XML en résultant est analysé avec XPath – après élimination de l‟espace de
nommage, qui ne doit pas être pris en compte par les requêtes XPath, au risque de devenir
excessivement complexes :
procedure TForm34.btnListSheetsClick(Sender: TObject);
var
strXml: string;
IDomSel: IDOMNodeSelect;
Node: IDOMNode;
I, nCount: Integer;
title, id: string;
begin
strXml := DoAppRequest ('get',
'http://spreadsheets.google.com/feeds/spreadsheets/priv
ate/full',
'');
strXml := StringReplace (strXml,
'<feed xmlns=''http://www.w3.org/2005/Atom''', '<feed
', []);
XMLDocument1.LoadFromXML(strXml);
XMLDocument1.Active := True;

IDomSel := (XMLDocument1.DocumentElement.DOMNode as
IDOMNodeSelect);
nCount := IDomSel.selectNodes ('/feed/entry').length;
for I := 1 to nCount do
begin
Node := IDomSel.selectNode(
'/feed/entry[' + IntToStr (i) + ']/title');
title := getChildNodes (Node);

Node := IDomSel.selectNode(
'/feed/entry[' + IntToStr (i) + ']/content/@src');
id := getChildNodes (Node);
ListBox1.Items.Add(title + '=' + id);
end;
end;
La boucle s‟exécute sur chaque nœud (en utilisant la fonction de décompte XPath pour
déterminer le nombre de nœuds) et ajoute le titre de la feuille de calcul et son ID/URL à la
liste, comme le montre l‟illustration suivante :

Grâce à ces données d‟identification ID/URL, il est possible d‟adresser une seconde requête
pour demander les feuilles d‟onglet disponibles dans le document spécifique. Même s‟il n‟en
existe qu‟un seul, il est nécessaire de se référer à un onglet spécifique dans chaque
opération affectant le contenu du document. Ceci permet au programme de récupérer
l‟identifiant dans la liste et d‟établir la seconde requête REST :
strSheetId := ListBox1.Items.ValueFromIndex
[ListBox1.ItemIndex];
strXml := DoAppRequest ('get', strSheetId, '');

À ce stade, une autre boucle similaire permet d‟extraire les noms de feuilles et de les
déplacer dans une seconde boîte liste :
IDomSel := (XMLDocument1.DocumentElement.DOMNode as
IDOMNodeSelect);
nCount := IDomSel.selectNodes ('/feed/entry').length;
for I := 1 to nCount do
begin
Node := IDomSel.selectNode(
'/feed/entry[' + IntToStr (i) + ']/title');
title := getChildNodes (Node);
Node := IDomSel.selectNode(
'/feed/entry[' + IntToStr (i) + ']/content/@src');
id := getChildNodes (Node);
ListBox2.Items.Add(title + '=' + id);
end;

Mais la véritable force de ce programme est sa capacité à ajouter des données au document
lui-même. Le moyen le plus simple d‟y parvenir est de créer un document se comportant
comme une table de base de données (c‟est-à-dire disposant d‟une première ligne avec les
noms de champs). Ce type de document, apparaissant dans le navigateur, est illustré ci-
dessous :
Les cellules de la première ligne sont de simples entrées texte. L‟intérêt est qu'il permet de
se référer aux colonnes correspondantes à l'aide d'un espace de nommage dynamique
comme :

<gsx:Company>Tom Sawyer Diving Centre<(gsx:Company>

Ainsi, il est possible d‟ajouter de nouvelles lignes au document en récupérant les champs
d‟une table avec les noms correspondants. Ceci nécessite une requête POST HTTP
reposant sur l‟identifiant ID :

const
gsxNameSpace =
'xmlns:gsx="http://schemas.google.com/' +
'spreadsheets/2006/extended"';

begin
strSheetId := ListBox2.Items.ValueFromIndex
[ListBox2.ItemIndex];

Memo1.Lines.Add(
DoAppRequest ('post',
strSheetId,
'<entry xmlns="http://www.w3.org/2005/Atom" ' +
gsxNameSpace + '>' +
recordtoxml +
'</entry>'));

La fonction recordtoxml récupère les valeurs des champs qui nous intéressent dans
l‟enregistrement en cours du jeu de données client (ClientDataSet) utilisé par le programme
et produit les données d‟entrée appropriées :
function recordtoxml: string;
begin

Result :=
FieldToXml ('custno') +
FieldToXml ('company') +
FieldToXml ('city') +
FieldToXml ('state') +
FieldToXml ('country');
end;

function FieldToXml (fieldname: string): string;


begin
Result := '<gsx:' + fieldname + '>' +
ClientDataSet1.FieldByName(fieldname).AsString +
'</gsx:' + fieldname + '>';
end;

Comme précédemment mentionné, l‟espace de nommage gsx: pseudo se réfère aux noms
des colonnes de la feuille de calcul, qui sont déterminés par les chaînes de la première ligne.
Ce code permet d‟ajouter à la feuille de calcul de nouvelles lignes correspondant à des
informations enregistrées dans la base de données.
Cependant, rien ne vous empêche (ni toute autre personne disposant de droits d‟édition sur
le document) d‟éditer les données après leur publication ; le document recalculera alors
automatiquement les totaux en fonction des données adressées par l‟application cliente –
permettant à n'importe quel utilisateur correctement habilité de consulter le document
résultant.
Nous avons donc créé un mécanisme sophistiqué de consultation et d'édition de données
émanant de tables de base de données, avec des mises à jour automatiques et continues et
une solide infrastructure d‟autorisation… et tout cela à partir d‟un compte Gmail gratuit !

SERVEURS REST DANS DELPHI 2010


Après ces longs développements dans lesquels nous avons analysé une large gamme
d‟applications REST clientes écrites dans Delphi et appris à répondre à différentes requêtes
d‟autorisation et à utiliser différents formats de type de données, il est temps d‟aborder la
seconde partie de ce livre blanc, consacrée au développement de serveurs REST dans
Delphi 2010.
Il convient tout d‟abord de préciser que ceci est possible dans n‟importe quelle version de
Delphi capable de créer un serveur Web – c‟est-à-dire probablement à partir de Delphi 3.
Aujourd‟hui, vous pouvez utiliser le composant IdHTTPServer ou l‟architecture WebBroker
ou d‟autres solutions tierces pour déployer votre propre serveur REST personnalisé. C‟est ce
que j‟ai moi-même fait pendant des années, comme le montrent les exemples cités dans
mon ouvrage Mastering Delphi 2005. Ce livre blanc se limitera donc délibérément à une
option spécifique, car celle-ci est étroitement intégrée dans Delphi 2010 et constituera
probablement la base des futures extensions produit fournies par Embarcadero
Technologies.
CONCEPTION D‟UN PREMIER SERVEUR REST SIMPLIFIE
Pour développer un premier serveur REST simplifié dans Delphi 2010, il est possible
d‟utiliser l‟assistant DataSnap:
Comme le montre l‟illustration ci-dessus, il existe deux assistants DataSnap et il est donc
nécessaire d‟en choisir un. Si vous souhaitez héberger votre serveur REST en tant que
serveur Web, il est probablement recommandé de choisir Application WebBroker DataSnap;
vous pourrez toutefois ajouter ultérieurement des composants au projet généré par les deux
assistants pour les rendre similaires. En règle générale, il est recommandé d‟utiliser un
serveur DataSnap ordinaire si vous envisagez de déployer des clients Delphi ou d'autres
applications et si votre serveur se trouve sur votre réseau interne, tandis qu'un serveur
DataSnap WebBroker intégré au serveur HTTP sera préférable pour les architectures
ouvertes et pour invoquer le serveur REST à partir d‟applications basées sur navigateur.
D‟un point de vue légèrement différent, le serveur DataSnap WebBroker offre plus de
contrôle sur les requêtes HTTP entrantes et une fonctionnalité d‟intégration des données
REST dans le serveur WebBroker.
Si vous avez choisi cette option, une boîte de dialogue s‟affichera avec quelques
paramètres. La principale décision à prendre dans cet assistant concerne le choix du
« squelette » applicatif.
Mais aucune inquiétude… Vous pourrez modifier l‟architecture projet en préservant
ultérieurement l‟intégralité de votre code :

Pour un hébergement effectif sur serveur Web, l'option ISAPI est recommandée – bien que
l‟option CGI (généralement plus lente) soit plus simple à paramétrer et à déboguer.
Toutefois, pour de réelles fonctionnalités de débogage et de test, il est fortement conseillé
d'utiliser l'infrastructure Débogueur d‟application Web fournie par Delphi, qui permet d‟ajouter
directement un point de rupture au code et de contrôler les flux de données HTTP (y compris
les en-têtes). Si vous choisissez cette option, il vous faudra insérer un nom de classe
interne, exclusivement utilisé comme référence programme (dans l‟URL Débogueur
d‟application Web). Notez également que même si cela n‟est pas explicitement mentionné
dans la boîte de dialogue de l‟assistant, Delphi continue à supporter les modules
d‟intégration de serveur Apache - étant précisé que le paramétrage projet sera manuel.
La boîte de dialogue de l‟assistant DataSnap WebBroker permet de demander une classe de
méthode serveur prête à l‟emploi avec des échantillons ; cependant, vous pouvez, une fois
encore, très simplement déployer la vôtre dans le code. Avec les paramétrages décrits ci-
dessus, Delphi générera un projet avec différentes unités organisées en arborescence :
 Un formulaire principal sans usage spécifique mais pouvant servir pour les
informations de connexion. Ce formulaire sera supprimé lorsque vous modifierez la
structure projet pour un module serveur Web ou CGI – ne vous en préoccupez donc
pas trop.
 Un module de données Web (héritier de TWebModule) hébergeant les composants
serveur DataSnap
 Un module de données agissant comme une classe serveur, permettant d'ajouter le
code à exécuter dans le serveur REST.
MODULE DE DONNÉES WEB GÉNÉRÉ PAR L‟ASSISTANT
DATASNAP WEBBROKER
Nous examinerons tout d‟abord en détail ces deux modules de données, avant d'ajouter de
nouvelles lignes de code programme et de les tester. Le module Web est un composant clé
de l‟architecture WebBroker. Il est capable de définir de multiples actions et dispose
d'événements pré- et post-traitement pour n'importe quelle requête HTTP. Sur ce module
Web, il est possible d‟ajouter des composants qui interceptent des actions URL données,
comme DSHTTPWebDispatcher dans l‟exemple fourni ci-dessous :

object DSHTTPWebDispatcher1: TDSHTTPWebDispatcher


RESTContext = 'rest'
Server = DSServer1
DSHostname = 'localhost'
DSPort = 211
WebDispatch.MethodType = mtAny
WebDispatch.PathInfo = 'datasnap*'
end

Ce composant intercepte n‟importe quelle requête avec une URL commençant par
« datasnap », transmise au support HTTP de DataSnap. Pour les requêtes commençant par
« datasnap » et indiquant un chemin d‟accès « rest », le traitement sera redirigé sur le
moteur REST intégré. En d‟autres termes, les requêtes ayant un chemin d'accès
« datasnap/rest » sont considérées comme des requêtes REST.
S‟agissant de deux chaînes, il est possible de les modifier pour prendre en compte
différentes URL, comme il est expliqué plus loin dans ce document. Mais pour commencer,
nous examinerons les paramétrages standards.
Les deux autres composants du module de données Web constituent la base globale de
DataSnap et indiquent quelle classe répondra aux requêtes (ou quelles classes, en cas
d‟ajout de composants DSServerClass multiples). Les paramètres par défaut sont les
suivants :
object DSServer1: TDSServer
AutoStart = True
HideDSAdmin = False
end
object DSServerClass1: TDSServerClass
OnGetClass = DSServerClass1GetClass
Server = DSServer1
LifeCycle = 'Session'
end
Le composant DSServer ne demande qu‟à être démarré (manuellement ou
automatiquement) ; en revanche, la configuration DSServerClass a essentiellement lieu dans
le gestionnaire d‟événement retournant la classe cible. Le code par défaut (généré par
l‟assistant), qui retourne la classe du module de données secondaire (TServerMethods1)
hébergé par l‟unité correspondante (Fsrs_ServerClass) est le suivant :
procedure TWebModule2.DSServerClass1GetClass(
DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
PersistentClass := Fsrs_ServerClass.TServerMethods1;
end;

Finalement, le module Web fournit une réponse HTTP par défaut à toutes les autres actions
en retournant un simple code HTML :

procedure TWebModule2.WebModule2DefaultHandlerAction(Sender:
TObject;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean);
begin
Response.Content := '<html><heading/><body>' +
'DataSnap Server</body></html>';
end;

Cette action est configurée lors de la conception comme suit :

Actions = <
item
Default = True
Name = 'DefaultHandler'
PathInfo = '/'
OnAction = WebModule2DefaultHandlerAction
end>
CLASSE SERVEUR EXEMPLE GÉNÉRÉE PAR L‟ASSISTANT
DATASNAP WEBBROKER
La troisième unité générée par l‟assistant DataSnap WebBroker est la classe serveur
exemple – celle qui présente les méthodes invoquées à distance via REST. Il s‟agit de la
classe Delphi connectée au composant DSServerClass via le gestionnaire d‟événements
DSServerClass1GetClass mentionné précédemment, qui récupère l‟essentiel du code réel.
La classe « squelette » ainsi générée est très simple et déterminée par le fait que des
exemples de méthodes sont demandés dans l‟assistant. En voici le code :
type
TServerMethods1 = class(TDSServerModule)
private
{ Private declarations }
public

function EchoString(Value: string): string;


end;
On notera que cette classe hérite de la classe TDSServerModule, qui s‟apparente à un
module de données standard (avec support des composants DataSetProvide), mais repose
sur une option spécifique de compilation permettant une forme de génération RTTI pour les
méthodes publiques (antérieure à la nouvelle extension RTTI de Delphi 2010) : {$MethodInfo
ON}.
La méthode EchoString par défaut retourne simplement le paramètre transmis, mais a été
légèrement mise à jour pour répéter la queue de la chaîne – comme dans un réel écho :
function TServerMethods1.EchoString(Value: string): string;
begin
Result := Value + '...' +
Copy (Value, 2, maxint) + '...' +
Copy (Value, Length (Value) - 1, 2);
end;

COMPILATION ET TEST DU SERVEUR REST


Il est maintenant temps de compiler le serveur et de vérifier son bon fonctionnement. Une
fois effectuées la compilation et l‟exécution du programme, il sera nécessaire d‟exécuter le
Débogueur d‟application Web (disponible dans le menu Outils de Delphi) et de le démarrer
(à l‟aide du bouton correspondant) :
Le Débogueur d‟application Web fonctionne sur un port spécifique – dans ma configuration
8081, comme le montre l‟illustration ci-dessus. Il est possible d‟ouvrir l‟URL par défaut pour
voir les différentes applications disponibles dans cette architecture ou de saisir l‟URL
spécifique du programme, qui prend le format ApplicationName.ServerClass. Dans le cas qui
nous intéresse, les deux jetons sont identiques, de sorte que l‟URL du serveur devient :

http://localhost:8081/FirstSimpleRestServer.FirstSimpleRestSe
rver

En l‟ouvrant dans un navigateur Web, il est possible de vérifier si le Débogueur d‟application


Web et le serveur spécifique fonctionnent (le serveur spécifique doit être déjà en exécution
car le Débogueur d‟application Web ne le démarrera pas automatiquement). Le navigateur
affichera alors ce qui suit :

Pour rappel, il s‟agit là du HTML retourné par le programme pour l‟action standard – ce qui,
bien sûr, n‟est pas d‟un grand intérêt...
L‟étape suivante consiste à utiliser l‟URL spécifique pour la seule requête exécutable par le
serveur REST – l‟invocation de la méthode EchoString du TServerMethods1 du support
« rest » du serveur « datasnap ». L‟URL est automatiquement combinée avec ajout du
préfixe du serveur REST (/datasnap/rest, par défaut), du nom de classe, du nom de méthode
et des paramètres de méthode :

/datasnap/rest/TServerMethods1/EchoString/hello%20world

Dans l‟URL, « %20 » remplace simplement un espace (il est également possible de saisir un
espace dans le navigateur). La réponse JSON du serveur REST apparaîtra alors sous la
forme suivante :

Pendant l'exécution de ces tests, il est possible d'utiliser le Débogueur d‟application Web
pour déterminer quelles requêtes et réponses HTTP doivent effectivement être transférées.
La page illustrée ci-dessus est générée par une requête de navigateur :
GET /FirstSimpleRestServer.FirstSimpleRestServer/
datasnap/rest/TServerMethods1/EchoString/hello%20world
HTTP/1.1
Host: localhost:8081
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US)
AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.195.27
Safari/532.0
Accept:
application/xml,application/xhtml+xml,text/html;q=0.9,
text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Encoding: gzip,deflate,sdch
Cookie:
LastProgID=FirstSimpleRestServer.FirstSimpleRestServer
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

Cette requête retourne la réponse HTTP complète suivante :

HTTP/1.1 200 200 OK


Connection: close
Content-Type: TEXT/HTML
Content-Length: 44

{"result":["hello world...ello world...ld"]}

Comme mentionné précédemment, la simplicité d‟accès à ces informations peut s‟avérer très
utile pour déboguer les applications HTTP.

INVOCATION DU SERVEUR REST A PARTIR D‟UN SIMPLE


CLIENT DELPHI
Après avoir développé le serveur et vérifié son bon fonctionnement, il est temps de
développer une application client Delphi pour le tester. Deux approches différentes peuvent
être adoptées, la première consistant à développer un client Delphi DataSnap en utilisant la
couche spécifique de transport fournie par REST. Cette option n‟a toutefois que peu
d‟avantage par rapport à celle consistant à utiliser les couches de transport HTTP ou TCP de
DataSnap.
La seconde possibilité – celle choisie dans ce chapitre – est de créer un client REST
spécifique, de la même manière que les autres clients développés dans la première partie de
ce livre blanc. Ainsi, n‟importe quel autre langage pourra être utilisé pour ce développement
– qui ne repose pas sur un support spécifique. Il suffira pour cela de créer une application
Delphi VCL standard et d‟y ajouter un composant IdHTTP pour l‟exécution effective de la
requête REST, une boîte d‟édition pour les entrées et un bouton, avec le code suivant :
const
strServerUrl = 'http://localhost:8081/' +
'FirstSimpleRestServer.FirstSimpleRestServer/';
strMethodUrl = 'datasnap/rest/TServerMethods1/EchoString/';
procedure TFormFirstRestClient.btnPlainCallClick(Sender:
TObject);
var
strParam: string;
begin
strParam := edInput.Text;
ShowMessage (IdHTTP1.Get(strServerUrl + strMethodUrl +
strParam));
end;
Cet appel crée une URL appropriée en concaténant l'adresse serveur, le chemin relatif pour
accéder à la méthode donnée avec le serveur REST et le paramètre unique. Il retourne la
réponse suivante :

Une autre option, présentant plus d'intérêt, consiste à extraire les informations réelles de la
structure de données JSON retournée par le serveur. Une approche manuelle pourrait être
adoptée (comme il est montré précédemment), mais dans ce cas, il peut être préférable de
profiter du support JSON disponible dans Delphi 2010.
Ce nouveau support JSON de Delphi est proposé via une série de classes définies dans
DBXJSON, qui – malgré son nom – peut aussi être utilisé dans les applications non liées au
framework dbExpress. L‟unité DBXJSON définit des classes utilisables pour manipuler
différents types de données JSON (valeurs individuelles de différents types, réseaux, objets,
etc.). Ceci est très utile pour personnaliser le résultat des applications serveur (comme nous
le verrons dans le projet suivant) et pour lire les données reçues par un client, comme dans
le cas qui nous intéresse.
Les données JSON retournées par le serveur sont présentées sous forme de chaîne, mais le
support serveur REST crée un objet associé à une valeur nommée (ou « paire ») et place la
valeur réelle dans une gamme. C‟est la raison pour laquelle, après analyse du résultat HTTP
dans une structure de données JSON, il est nécessaire de naviguer de l‟objet à la paire qu'il
contient - et de la paire jusqu‟à l‟élément unique qu‟elle contient :

procedure TFormFirstRestClient.btnToJSONClick(Sender:
TObject);
var
strParam, strHttpResult, strResult: string;
jValue: TJSONValue;
jObj: TJSONObject;
jPair: TJSONPair;
jArray: TJSOnArray;
begin
strParam := edInput.Text;
strHttpResult := IdHTTP1.Get(strServerUrl +
strMethodUrl + strParam);
jValue := TJSONObject.ParseJSONValue(
TEncoding.ASCII.GetBytes(strHttpResult), 0);
if not Assigned (jValue) then
begin
ShowMessage ('Error in parsing ' + strHttpResult);
Exit;
end;

try
jObj := jValue as TJSONObject;
jPair := jObj.Get(0); // Obtention de la seule et unique
paire JSON
jArray := jPair.JsonValue as TJsonArray; // La valeur de
paire est un array
strResult := jArray.Get(0).Value; // Premier et seul
élément d’Array
ShowMessage ('The response is: ' + strResult);
finally
jObj.Free;
end;
end;

Là encore, la complexité est due à la structure de données retournée par le serveur, puisque
dans d‟autres circonstances, il serait bien plus simple d‟analyser les données JSON
résultantes et d‟y accéder.
APPEL DU SERVEUR REST A PARTIR D‟UNE APPLICATION
WEB AJAX
Pour un simple transfert d‟objet d‟une application Delphi serveur à une autre, il existe de
nombreuses alternatives à l‟utilisation de JSON – approche particulièrement adaptée pour
appeler le serveur Delphi compilé à partir d‟une application JavaScript exécutée dans le
navigateur. Ce cas d‟utilisation est très pertinent dans la mesure où la technologie AJAX
(appels JavaScript asynchrones effectués dans le navigateur Web) a été – et demeure – l‟un
des éléments moteur de l‟adoption REST. L‟appel d‟un serveur SOAP correspondant à l‟aide
d‟un programme basé sur navigateur est largement plus complexe.
Comment donc créer une application reproduisant le client précédemment développé tout en
étant exécutée dans le navigateur Web ? De multiples méthodologies et bibliothèques
pourraient être utilisées, mais l‟option privilégiée à ce stade est celle de la bibliothèque open-
source JavaScript jQuery, disponible sur :
http://jquery.com
Une présentation approfondie de jQuery et de ses modes d‟utilisation serait trop longue pour
ce livre blanc, mais nous expliquerons néanmoins le code jQuery sous-jacent de cet
exemple précis. Tout d‟abord, la page HTML inclut jQuery et son support JSON :
<head>
<title>jQuery and Delphi 2010 REST</title>
<script
src="http://jqueryjs.googlecode.com/files/jquery-
1.3.2.min.js"
type="text/javascript"></script>
<script
src="http://jquery-json.googlecode.com/files/jquery.json-
2.2.min.js"
type="text/javascript"></script>
</head>

Par ailleurs, la page a une interface utilisateur très simple, avec du texte, un champ d'entrée
et un bouton (sans fichiers CSS sophistiqués et graphiques ajoutés – l‟objectif étant de se
concentrer sur le point qui nous intéresse ici) :
<body>
<h1>jQuery and Delphi 2010 REST</h1>

<p>This example demonstrates basic use of jQuery calling a


barebone Delphi 2010 REST server. </p>
<p>Insert the text to "Echo":

<br/>
<input type="text" id="inputText" size="50"
value="This is a message from jQuery">
<br/>
<input type="button" value="Echo" id="buttonEcho">

<div id="result">Result goes here: </div>


</body>
Les lignes ci-dessus constituent le « squelette » et il convient maintenant de passer au code
JavaScript à proprement parler. Pour cela, il suffit d‟ajouter un gestionnaire d‟événements au
bouton, de lire le texte d'entrée, d'effectuer l'appel REST et d‟afficher le résultat. Pour
accéder aux objets de la page, nous utiliserons le plus simple des sélecteurs jQuery, basé
sur l‟ID des objets, comme dans :

$("#inputText")

Cette opération retourne un objet jQuery encapsulant l‟élément DOM de texte d‟entrée. Pour
définir un gestionnaire d‟événements, il est possible de transmettre un paramètre de
méthode anonyme à la fonction click() du bouton. Deux nouvelles invocations sont
effectuées : l‟invocation REST à proprement parler (à partir des données getJSON globales)
et l‟invocation htmlI() pour ajouter le résultat à la page HTML de l‟élément de sortie.
Voici la ligne complète du code sous-jacent de cette démonstration – un snippet de code
JavaScript très compact mais peu lisible :

$(document).ready(function() {
$("#buttonEcho").click(function(e) {
$.getJSON("http://localhost:8081/"
"FirstSimpleRestServer.FirstSimpleRestServer/"
"datasnap/rest/TServerMethods1/EchoString/" +
$("#inputText").val(),
function(data) {
$("#result").html(data.result.join(''));
} );
});
});
En ouvrant simplement un fichier HTML avec le code donné, il est possible d‟appeler le
serveur personnalisé – mais uniquement si les paramètres d'autorisation navigateur
permettent un appel AJAX à partir d‟un fichier local et à destination d‟un serveur REST local.
La plupart des navigateurs n‟autorisent les invocations de serveurs REST que sur le même
site que celui dont émane la page HTML.
Dans tous les cas, Internet Explorer semble bien fonctionner sur ce fichier local, après
activation des scripts locaux et demande de sécurité limitée (option disponible puisque le
fichier se trouve sur la machine locale ; voir les icônes de la barre d'état) :

Sur les autres navigateurs, le serveur Web doit nécessairement retourner la page HTML et
les données REST – ce qui ne présente pas de difficulté particulière, puisque le serveur
REST est, en fait, un serveur Web. Ainsi, pour une « solution serveur » (sous l‟angle du
navigateur Web) il suffit d‟ajouter une action au module de serveur Web (qu‟on accrochera à
l‟URL « /file ») et de retourner un fichier HTML à partir de celui-ci :
procedure TWebModule2.WebModule2DefaultHandlerAction(Sender:
TObject;
Request: TWebRequest; Response: TWebResponse; var Handled:
Boolean);
var
strRead: TStreamReader;
begin
strRead := TStreamReader.Create('jRestClient.html');
try
Response.Content := strRead.ReadToEnd;
finally
strRead.Free;
end;
end;

Il est maintenant possible de se référer à une page serveur donnée avec l‟URL /file, d‟obtenir
le fichier avec le code JavaScript et de lui faire appeler le serveur REST :

La différence avec l'image précédente ne tient pas uniquement à l'utilisation d'un navigateur
différent – l‟URL désignée est également différente. Ce second cas ne repose pas sur le
chargement d‟un fichier, mais utilise l‟application REST serveur comme serveur Web
complet, en retournant le code HTML utilisé pour invoquer ce même serveur via AJAX.

RETOUR ET MISE A JOUR D'OBJETS


Après avoir vu comment développer un serveur REST très simple avec support Delphi 2010
DataSnap, il est temps d‟examiner les lignes de code nécessaires pour le rendre plus
puissant. Comme nous l‟avons vu, le serveur retourne des données JSON et convertit dans
ce format le résultat des fonctions. Il est possible de transmettre un objet en tant que résultat
et de le convertir. Toutefois, en pratique, il sera préférable de garder la maîtrise totale du
processus en créant des objets JSON spécifiques au niveau serveur et en les retournant.
Ceci est l‟un des objectifs du projet suivant.
Ce projet démontrera également comment mettre en œuvre d‟autres méthodes HTTP que la
méthode « get », pour récupérer – et modifier – un objet de niveau serveur à partir d‟un
simple client basé sur navigateur et écrit en JavaScript. Enfin, nous aborderons les
problématiques de gestion des URL et découvrirons notamment comment améliorer leur
flexibilité.
RETOUR D‟OBJETS ET DE VALEURS JSON
Dans ce second projet, nous utiliserons l‟assistant DataSnap WebBroker, sélectionnerons
(de nouveau) l‟architecture Débogueur d‟application Web et opterons pour une classe de
base TPersistent, puisque nous n‟avons pas besoin de module de données comme classe
cible pour les appels REST. Le support RTTI spécifique étant requis, il est nécessaire
d‟hériter (au moins) de TPersistent et de marquer la classe avec la directive
$METHODINFO, comme l‟illustre le code suivant :
{$METHODINFO ON}
type
TObjectsRest = class(TPersistent)
public
function PlainData (name: string): TJSONValue;
function DataMarshal (name: string): TJSONObject;
end;
{$METHODINFO OFF}
Comme le montre cet exemple, deux fonctions ont été ajoutées à la classe pour pouvoir
retourner soit une valeur, soit un objet complet. D‟autres méthodes seront ultérieurement
ajoutées à la classe.
La structure de données sous-jacente de cette application est une liste d‟objets de type
personnalisé (notons que le développement aurait pu être plus « orienté-objet », mais pour
les besoins de cette illustration, une approche simplifiée a été délibérément choisie) :

type
TMyData = class (TPersistent)
public
Name: String;
Value: Integer;
public
constructor Create (const aName: string);
end;
Les objets sont conservés dans un dictionnaire et implémentés à l‟aide de la classe de
conteneur générique TObjectDictionary<TKey,TValue> définie dans l‟unité
Generics.Collections depuis Delphi 2009. Cet objet global est initialisé lors du démarrage du
programme avec l‟ajout d‟une paire d'objets prédéfinis. Nous remarquerons que l‟ajout des
objets repose sur une procédure AddToDictionary spécifique pour garantir que le nom
d‟objet est conforme à la clé de dictionnaire et reçoit une valeur aléatoire, si aucune n‟est
fournie :
var
DataDict: TObjectDictionary <string,TMyData>;

procedure AddToDictionary (const aName: string; nVal: Integer


= -1);
var
md: TMyData;
begin
md := TMyData.Create (aName);
if nVal <> -1 then

md.Value := nVal;
DataDict.Add(aName, md);
end;

initialization
DataDict := TObjectDictionary <string,TMyData>.Create;
AddToDictionary('Sample');
Une fois cette structure de données implémentée, il est temps d‟aborder les deux premières
méthodes exemple utilisées pour retourner les valeurs JSON. La première retourne la valeur
de l‟objet donné (en choisissant une valeur par défaut si aucun paramètre n‟est transmis à la
fonction) :

function TObjectsRest.PlainData(name: string): TJSONValue;


begin
if Name = '' then
name := 'Sample'; // default
Result := TJSONNumber.Create(DataDict[name].Value);
end;

Si nous utilisons une URL avec ou sans le paramètre (comme dans les deux lignes
suivantes) :

/datasnap/rest/TObjectsRest/PlainData/Test
/datasnap/rest/TObjectsRest/PlainData

nous obtiendrons tout de même une réponse JSON, pour l‟objet spécifique ou pour un objet
par défaut :

{"result":[8978]}

Comment retourner un objet complet et non une valeur spécifique ? Le serveur REST ne
peut pas retourner une valeur TObject car le système n‟est pas capable de le convertir
automatiquement ; néanmoins, il peut utiliser le nouveau support de conversion JSON pour
convertir un objet existant en format JSON :

function TObjectsRest.DataMarshal(name: string): TJSONObject;


var
jMarshal: TJSONMarshal;
begin
jMarshal := TJSONMarshal.Create(TJSONConverter.Create);
Result := jMarshal.Marshal(DataDict[name]) as TJSONObject;
end;

Cette approche est surtout utile pour recréer l‟objet dans l‟application cliente Delphi – et ne
présente pas grand intérêt dans les cas où le client est développé dans un autre langage. Le
code JSON correspondant est assez lourd :

{"result":[{
"type":"ObjectsRestServer_Classes.TMyData",
"id":1,
"fields": {
"Name":"Test",
"Value":8068}
}]}
Quelle est donc la meilleure option pour retourner un objet JSON ? Nous recommanderons
probablement d'en créer un au niveau serveur à l‟aide de classes de support – comme nous
l‟avons fait dans la fonction MyData :
function TObjectsRest.MyData(name: string): TJSONObject;
var
md: TMyData;
begin
md := DataDict[name];
Result := TJSONObject.Create;
Result.AddPair(
TJSONPair.Create ('Name', md.Name));
Result.AddPair(
TJSONPair.Create ('Value',
TJSONNumber.Create(md.Value)));
end;

Nous avons donc créé un code TJSONObject et ajouté deux paires de propriétés pour le
nom et la valeur. Nous aurions pu utiliser un nom dynamique (c‟est-à-dire la désignation du
nom faisant partie de la paire), mais cela aurait complexifié la récupération des données sur
le client. Le résultat de ce code retournera un code JSON plus net, semblable à l‟exemple
suivant :

{"result":[{
"Name":"Test",
"Value":8068
}]}

LISTE D‟OBJETS AVEC TJSONARRAY


Maintenant que nous disposons d‟une liste d‟objets, il peut s‟avérer nécessaire de pouvoir y
accéder. Il pourra être utile de disposer d'une liste des noms uniquement (sans données)
pour développer une interface utilisateur au niveau client.
Pour retourner une liste, il est possible d‟utiliser un groupe TJSONArray – en l‟occurrence,
un groupe de chaînes créé avec un énumérateur sur les clés du dictionnaire :
function TObjectsRest.List: TJSONArray;
var
str: string;
begin
Result := TJSONArray.Create;
for str in DataDict.Keys do
begin
Result.Add(str);
end;
end;
Cet appel retourne un groupe sous format JSON, qui est à son tour transmis (selon le
processus habituel) dans un groupe désigné « résultat » (d‟où la double notation de groupe
imbriqué) :
{"result":[
["Test","Sample"]
]}
Après avoir vu comment retourner une liste de valeurs et extraire les données de chaque
élément individuel, nous pouvons maintenant commencer à développer l‟interface utilisateur.
AU NIVEAU CLIENT : LISTE ET VALEURS
Au lieu de développer le code HTML initial avec la liste de valeurs et de laisser l‟utilisateur en
choisir une, il est possible d‟exploiter les fonctionnalités complètes du modèle AJAX.
Au démarrage, la page ne contiendra pas de données, mais uniquement les éléments HTML
et le code JavaScript. Dès que la page est chargée – et même sans intervention de
l‟utilisateur – elle contacte le serveur pour requérir les données réelles et les transférer dans
l‟interface utilisateur.
Ainsi, au démarrage, le programme affiche la valeur de l‟objet Exemple, en utilisant les
éléments HTML et l‟invocation AJAX ci-dessous (l‟exécution se fait comme lorsque le
document est prêt, c'est-à-dire lorsque le DOM a terminé le chargement) :

<div>Sample: <span id="sample"></span></div>

<script>
var baseUrl = "/ObjectsRestServer.RestObjects/dataSnap" +
"/rest/TObjectsRest/";
$(document).ready(function() {
$.getJSON(baseUrl + "MyData/Sample",
function(data) {
strResult = data.result[0].Value;
$("#sample").html(strResult);
} );

L‟invocation AJAX de MyData transmet le nom d‟objet comme nouveau paramètre URL et
extrait du groupe de résultat la valeur nommée propriété/paire en l‟affichant dans un élément
HTML avec espace vide. Un processus similaire (bien qu'un peu plus complexe) a lieu pour
la liste. Il s‟agit là encore d‟un appel AJAX, mais cette fois, le code HTML résultant doit être
développé. L'opération est exécutée dans une fonction refreshList séparée, appelée
automatiquement au démarrage et manuellement par l‟utilisateur :

<div>Current entries list:


<a href="#" id="refresh">Refresh</a>
<span id="list"></span></div>

function refreshList()
{
$.getJSON(baseUrl + "list",
function(data) {
thearray = data.result[0];
var ratingMarkup = ["<br>"];
for (var i=0; i < thearray.length; i++) {
ratingMarkup = ratingMarkup +
"<a href='#'>" + thearray[i] +
"</a><br>";
}
$("#list").html(ratingMarkup);
} );
};

Le code utilise une boucle « pour » pour vérifier le groupe généré. Nous aurions pu utiliser le
mécanisme d‟énumération $.each de jQuery, mais cela aurait complexifié la lecture du code.
La boucle crée le code HTML, qui s‟affiche ensuite dans l‟emplacement espace avec l‟ID
donnée. Il s‟agit d‟une sortie individuelle incluant la valeur de l‟objet « Sample » (le code
décrit précédemment) et la liste des valeurs retournées dans le groupe JSON :

Comme mentionné précédemment, la fonction refreshList est invoquée au démarrage (dans


le gestionnaire d‟événements disponibles) ; elle est également connectée à un lien
correspondant pour permettre à l‟utilisateur de rafraîchir ultérieurement les données de la
liste sans avoir à rafraîchir l‟ensemble de la page HTML :

$(document).ready(function() {
refreshList();

$("#refresh").click(function(e) {
refreshList();
} );

Quelques opérations restent encore pour générer le code… Dès que le code HTML est prêt
pour la liste (il s‟agit d‟une liste de liens), il doit être « accroché » à ces liens pour que
chaque entrée de la liste dans l‟application cliente, lorsqu‟elle est sélectionnée, entraîne le
chargement de l‟objet correspondant au niveau serveur. L‟interface utilisateur des données
objet est constituée de deux boîtes d‟entrée, qui seront ultérieurement utilisées pour
manipuler les données objet. Le comportement est ajouté à chaque point d‟ancrage dans le
conteneur de liste.

$("#list").find("a").click(function(e) {
var wasclicked = $(this);
$.getJSON(baseUrl + "MyData/" + $(this).html(),
function(data) {
strResult = data.result[0].Value;
$("#inputName").val(wasclicked.html());
$("#inputValue").val(strResult);
} );
});

Notons l‟utilisation de l‟appel $(this), qui constitue plus ou moins le paramètre Émetteur pour
un événement Delphi. Le contenu html de l‟élément sélectionné est son texte, qui est le nom
de l‟élément à transmettre au serveur dans l‟URL avec l‟expression :

baseUrl + "MyData/" + $(this).html()

Après avoir développé ce code, on pourra observer l‟action produite lorsque l‟on clique sur
l‟un des éléments de la liste : une nouvelle invocation AJAX est effectuée à destination du
serveur pour demander une valeur donnée et la valeur retournée s‟affiche dans deux boîtes
d‟entrée texte :

Comme le montre cette illustration, le programme permet de récupérer une valeur, mais
comporte également trois boutons pour exécuter les opérations les plus communes
(Création/Lecture/Mise à jour/Suppression). Ces actions sont supportées en HTML à l‟aide
des 4 méthodes HTML de code (respectivement, PUT, GET, POST et DELETE). Dans le
chapitre suivant, nous verrons comment ces méthodes sont supportées par un serveur
REST Delphi 2010.
POST, PUT ET DELETE
Jusqu‟à présent, nous avons vu comment obtenir des données du serveur REST. Mais qu‟en
est-il de leur mise à jour ? L‟idée généralement admise pour l‟environnement REST est qu‟il
faut éviter d‟utiliser des URL spécifiques pour identifier les opérations et qu‟il est préférable
de n‟en utiliser qu‟une seule pour identifier les objets serveur (comme MyData/Sample dans
le cas développé ici) et d‟utiliser les méthodes HTTP pour indiquer les actions à exécuter.
Il serait regrettable que le support REST de Delphi se contente d'associer les URL aux
méthodes ; mais au contraire, il relie non seulement les URL, mais également la méthode
HTTP aux méthodes selon un schéma relativement simple : le nom de l‟opération est
initialement associé au nom de méthode à l‟aide du mappage suivant :
 GET obtention (par défaut, peut être omis)
 POST mise à jour
 PUT acceptation
 DELETE annulation
Ces mappages peuvent être personnalisés en manipulant les quatre gestionnaires
d‟événements correspondants du composant DSHTTPWebDispatcher. En cas de choix des
règles de nommage standards, le support des différentes opérations exigera de définir la
classe serveur comme suit :

type
TObjectsRest = class(TPersistent)
public
function List: TJSONArray;
function MyData (name: string): TJSONObject;
procedure updateMyData (name, value: string);
procedure cancelMyData (name: string);
procedure acceptMyData (name, value: string);
end;

Pour obtenir ou supprimer un élément, seul le nom est nécessaire ; en revanche, la création
ou la mise à jour d‟un élément exigera un second paramètre, en plus des données.
L‟implémentation des trois nouvelles méthodes est assez simple et directe, notamment parce
qu‟elles n‟ont pas à retourner une valeur (il va sans dire qu‟on aura vérifié au préalable que
les paramètres ne sont pas vides et que l'objet serveur existe réellement) :
procedure TObjectsRest.updateMyData (name, value: string);
begin
DataDict[name].Value := StrToIntDef (Value, 0);
end;

procedure TObjectsRest.cancelMyData(name: string);


begin
DataDict.Remove(name);
end;

procedure TObjectsRest.acceptMyData(name, value: string);


begin
AddToDictionary (name, StrToIntDef (Value, 0));
end;

EDITION AU NIVEAU CLIENT


Maintenant que les opérations CRUD sont disponibles sur le serveur REST, il est possible
de compléter l‟application client JavaScript à l‟aide du code des trois boutons d‟édition
(l‟illustration incluant l‟interface utilisateur basée sur navigateur figure plus haut dans ce livre
blanc).
jQuery dispose d‟un support spécifique de l‟opération « get » (avec différentes versions,
notamment la version spécifique JSON utilisée précédemment) et d‟un autre support pour
les opérations « post » ; en revanche, pour les autres méthodes HTTL, il est nécessaire
d‟utiliser l'invocation de niveau inférieur (et légèrement plus complexe) $.ajax. Cette
invocation comporte, parmi ses paramètres, une liste de valeurs associées par paires, sur
une douzaine de possibilités. Les paramètres les plus pertinents sont le type et l‟url, tandis
que les données permettent de transmettre d‟autres paramètres POST.
La mise à jour est relativement simple et il est possible de fournir l‟intégralité des données au
serveur REST à l‟aide de l‟URL :

$("#buttonUpdate").click(function(e) {
$.ajax({
type: "POST",
url: baseUrl + "MyData/" +
$("#inputName").val() + "/" +
$("#inputValue").val(),
success: function(msg){
$("#log").html(msg);
}
});
});

La suppression est tout aussi simple, exigeant uniquement de créer l‟URL avec la référence
à l‟objet à supprimer :
$("#buttonDelete").click(function(e) {
$.ajax({
type: "DELETE",
url: baseUrl + "MyData/" +
$("#inputName").val(),
success: function(msg){
$("#log").html(msg);
}
});
});

L'implémentation PUT s‟annonçait plus problématique, puisqu‟en l‟absence de données


fournies, certains navigateurs (notamment Chrome) affichent les données comme « non-
définies » -- ce qui entraîne une erreur de l‟analyse d‟entrée HTTP du serveur REST et un
plantage de ce dernier. Les informations devant être transmises (et étant donné qu‟il n‟est
pas possible de transmettre plus de paramètres que ceux demandés par le serveur au risque
d‟entraîner une erreur), une option consiste à remplacer l‟un des éléments d‟URL par un
élément de données correspondant :
$("#buttonNew").click(function(e) {
$.ajax({
type: 'PUT',
data: $("#inputValue").val(),
url: baseUrl + "MyData/" +
$("#inputName").val(),
success: function(msg){
$("#log").html(msg);
}
});
});

On notera que la documentation jQuery déconseille expressément d‟utiliser la méthode PUT


dans les navigateurs pour le pas risquer d‟obtenir des résultats mitigés. Ceci explique peut-
être également pourquoi un certain nombre de services REST (notamment ceux de
Microsoft) tendent à utiliser la méthode POST pour la mise à jour et la création d‟objets
serveur. Il est toutefois préférable, chaque fois que possible, d‟aborder les deux notions
séparément pour plus de clarté et de cohérence.
Ainsi, avec les trois méthodes supplémentaires ajoutées à la classe et les invocations
JavaScript adéquates, nous disposons maintenant d‟un exemple avec une interface
utilisateur complète, basée sur navigateur, pour créer et éditer des objets dans le serveur
REST. L‟illustration suivante présente quelques objets créés :

SERVEURS REST ORIENTÉS-DONNÉES


Si l‟objectif initial sous-jacent de DataSnap est de déplacer des tables de données d‟un
serveur de niveau intermédiaire à une application cliente, il peut paraître étonnant de
constater qu‟il est impossible de retourner un jeu de données à partir d‟un serveur REST
développé dans Delphi 2010. Plus précisément, cette opération ne peut pas être exécutée
directement ni aussi simplement que pour sa représentation XML, mais il est possible de
créer un résultat JSON avec toutes les données du jeu de données Delphi. C‟est l‟objet du
dernier exemple développé dans ce livre blanc.
Le programme est assez élémentaire, puisqu‟il se contente de retourner les données d‟un
Jeu complet, sans métadonnées. Il aurait bien besoin de différentes extensions et
d‟affinages de son interface utilisateur, mais fournit toutefois une base suffisante pour
commencer. La classe serveur ne comporte qu‟une seule méthode, qui retourne un jeu
complet de données dans un groupe JSON contenant des objets/enregistrements
individuels :

function TServerData.Data: TJSONArray;


var
jRecord: TJSONObject;
I: Integer;
begin
ClientDataSet1.Open;
Result := TJSonArray.Create;

while not ClientDataSet1.EOF do


begin
jRecord := TJSONObject.Create;
for I := 0 to ClientDataSet1.FieldCount - 1 do
jRecord.AddPair(
ClientDataSet1.Fields[I].FieldName,
TJSONString.Create
(ClientDataSet1.Fields[I].AsString));
Result.AddElement(jRecord);
ClientDataSet1.Next;
end;
end;

Cette méthode est invoquée par l‟application cliente après le chargement de la page, à
travers la construction dynamique d'une table HTML avec le code jQuery suivant (semblable
au code amplement développé dans les chapitres précédents) :
$(document).ready(function() {

$.getJSON(
"/DataRestServer.RestDataServer/datasnap/rest/TServerDa
ta/Data",
function(data) {
thearray = data.result[0];
var ratingMarkup = "<table border='1'>";
for (var i=0; i < thearray.length; i++) {

ratingMarkup = ratingMarkup + "<tr><td>" +


thearray[i].Company +
"</td><td>" +
thearray[i].City +
"</td><td>" +
thearray[i].State +
"</td><td>" +
thearray[i].Country +
"</td></tr>";
}
ratingMarkup = ratingMarkup + "</table>";
$("#result").html(ratingMarkup);
} );
});
Le résultat élémentaire s‟affichant dans Internet Explorer est le suivant :
Peut-il être amélioré ? Est-il possible, par exemple, de ne pas lister l'intégralité des champs à
afficher dans JavaScript, lorsque tous sont requis ? La version finale du programme ajoute
un support des métadonnées pour améliorer la sortie finale.
Du côté serveur, une seconde fonction retourne un groupe de noms de champs à partir des
définitions de champs de jeu de données :

function TServerData.Meta: TJSONArray;


var
jRecord: TJSONObject;
I: Integer;
begin
ClientDataSet1.Open;
Result := TJSonArray.Create;

for I := 0 to ClientDataSet1.FieldDefs.Count - 1 do
Result.Add(ClientDataSet1.FieldDefs[I].Name);
end;
Le JavaScript au niveau client a été étendu par une seconde invocation du serveur REST
pour obtenir les métadonnées :
$.getJSON(
"/DataRestServer.RestDataServer/datasnap/rest/TServerDa
ta/Meta",
function(data) {
theMetaArray = data.result[0];

Ces informations sont utilisées pour créer l‟entête de table et accéder dynamiquement aux
propriétés d‟objet, avec l'objet de notation [“propertyname”]. Dans le cas qui nous intéresse,
le code existant utilisé pour accéder à un objet avec le symbole de propriété :

thearray[i].Company

devient le code ci-dessous, qui lit la propriété par nom, en utilisant le nom du champ
enregistré dans les métadonnées :
thearray[i][theMetaArray[j]].

Voici le code JavaScript complet utilisé pour créer le balisage HTML :


var ratingMarkup = "<table border='1'><tr>";
// header
for (var j=0; j < theMetaArray.length; j++) {

ratingMarkup = ratingMarkup + "<th>" +


theMetaArray[j] + "</th>";
};
ratingMarkup = ratingMarkup + "</tr>";

// content
for (var i=0; i < thearray.length; i++) {
ratingMarkup = ratingMarkup + "<tr>";
for (var j=0; j < theMetaArray.length; j++) {
ratingMarkup = ratingMarkup + "<td>" +
thearray[i][theMetaArray[j]] + "</td>";
};
ratingMarkup = ratingMarkup + "</tr>";
}
ratingMarkup = ratingMarkup + "</table>";

Le résultat de cette version étendue gagne légèrement en simplicité (et en flexibilité) :

Là encore, cet exemple n‟offre qu‟une base de départ et n'utilise aucune des extensions
jQuery, qui ajouteraient des fonctionnalités significatives aux tables HTML en les
transformant en puissantes grilles d‟interface utilisateur avec des outils de tri, de filtrage et
d‟édition.

CONCLUSION
Ce livre blanc ne fait qu‟effleurer l‟architecture REST et le développement d‟applications
REST client et serveur avec Delphi 2010. Il fournit un aperçu très rapide des technologies
associées (XML, JSON, XPath, JavaScript, jQuery) qui peuvent être nécessaires pour
devenir expert des architectures REST.
Étant donné le nombre croissant de serveurs REST publics, l‟émergence du cloud-
computing et l‟intérêt grandissant pour l‟hébergement d‟applications Web, Delphi pourrait
jouer un rôle significatif dans le développement de riches clients d‟interface appelant des
serveurs distants et dans le développement de serveurs réels de manipulation de structures
de données dans une application cliente (développée dans n‟importe quel langage) ou
directement dans le navigateur.
Comme l‟ont montré les démonstrations finales, la combinaison de JavaScript et d‟un
serveur REST Delphi permet d‟utiliser l‟EDI Embarcadero pour développer des applications
Web professionnelles, modernes et de qualité optimale.

A PROPOS DE L‟AUTEUR
Marco est l‟auteur du best-seller « Mastering Delphi » et a publié, au cours des dernières
années, des ouvrages sur les dernières versions de Delphi, notamment « Delphi 2007
Handbook », « Delphi 2009 Handbook » et le « Delphi 2010 Handbook » (actuellement en
cours de rédaction).
Parallèlement à ses prestations de formation et de consulting sur Delphi, Marco propose des
services de consulting sur les architectures Web et l‟intégration Web des projets Delphi –
sous l‟angle de l‟invocation d‟autres serveurs et de leur mise à disposition.
Vous pouvez consulter le blog de Marco sur http://blog.marcocantu.com, échanger avec lui
sur Twitter sur http://twitter.com/marcocantu ou le contacter par email à marco.cantu
@gmail.com.
Marco souhaite remercier Daniele Teti pour sa contribution à l‟élaboration de ce livre blanc,
tout particulièrement sur le support JSON de Delphi 2010.
Embarcadero Technologies, Inc. est un éditeur leader de solutions primées pour les
développeurs d‟applications et les professionnels des bases de données pour les aider à
concevoir et exécuter plus efficacement et rapidement leurs solutions – quels que soient les
langages de programmation ou les plates-formes. 90 des 100 premières sociétés du
classement Fortune et une communauté active de plus de 3 millions d‟utilisateurs dans le
monde font confiance aux outils Embarcadero pour maximiser leur productivité, réduire les
coûts, simplifier la gestion du changement et de la conformité et accélérer l‟innovation. Ses
produits les plus emblématiques sont notamment : Embarcadero® Change Manager™,
Embarcadero™ RAD Studio, DBArtisan®, Delphi®, ER/Studio®, JBuilder® et Rapid SQL®.
Créé en 1993, le siège d‟Embarcadero se situe à San Francisco ; l‟entreprise est présente
dans le monde entier et son site Web peut être consulté à l‟adresse www.embarcadero.com

Vous aimerez peut-être aussi