Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
Novembre 2009
INTRODUCTION
La dernire dcennie a vu lexplosion du Web et plus rcemment lapparition du Web 2.0.
Les interactions automatiques entre sites Web, sites et applications clientes ou entre sites
Web et bases de donnes mtier sont en revanche plus rcentes et exigent gnralement
des interconnexions globales parfois difficiles comprendre.
La vitesse de dplacement et de rafrachissement des donnes sur le Web est devenue bien
suprieure la capacit de consultation de chacun ; do une forte demande pour les
programmes permettant de localiser, suivre et superviser des informations multisources
(donnes commerciales, informations financires, communauts en ligne, campagnes
marketing, etc.).
POURQUOI DES SERVICES WEB ?
Lmergence des services Web permet denvisager de nouveaux modes dutilisation
dInternet par les entreprises... Naviguer sur diffrentes 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 sophistiqus. Par exemple, il est
gnralement trs 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 mme approche serait totalement inefficace surtout si dautres
applications dterminent les rapprovisionnements en fonction des ventes Ressaisir ces
donnes dans une autre application serait en effet compltement ridicule...
Les services Web ont (ou du moins avaient lorigine) prcisment pour vocation de
rsoudre ce problme. Par exemple, lorsque le programme cre automatiquement une
demande de rapprovisionnement de livres ; il ladresse ensuite un service Web qui
retourne immdiatement les informations sur la commande. Ltape suivante consistant par
exemple demander un numro 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 dattente. la livraison, le programme peut galement adresser une information
(par SMS/Pager/Twitter) aux personnes ayant des commandes en attente, dclencher un
paiement avec un service Web bancaire, etc. Cet exemple donne une bonne ide de la
cinmatique gnrale : les services Web sont linteroprabilit des systmes ce que le
Web et le-mail sont aux interactions entre individus...
Le primtre des services Web est extrmement tendu et implique de multiples
technologies et standards. Je me concentrerai donc sur limplmentation Delphi sous-jacente
et sur les aspects techniques des services Web sans aborder leurs implications mtier au
sens large. Delphi for Win32 propose des solutions trs sophistiques pour prendre en
charge les services Web drives initialement de SOAP et qui peuvent aujourdhui tre
facilement tendues travers des composants HTTP et REST.
TECHNOLOGIES CLS DES SERVICES WEB : SOAP ET REST
Le concept de service Web est en soi plutt abstrait. Technologiquement, deux solutions
attirent principalement les dveloppeurs : 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 gnralement HTTP comme protocole de transmission (bien quil
existe des alternatives) et XML (ou JSON) pour les dplacements bidirectionnels de
donnes. Le protocole HTTP standard permet au serveur Web de grer les requtes et de
faire transiter les paquets de donnes travers les pare-feux.
Ce livre blanc naborde pas les dtails de SOAP et porte exclusivement sur REST. Je
commencerai par dfinir les fondations thoriques, je prsenterai ensuite un exemple simple
de serveur et de client, je reviendrai en dtail sur le dveloppement de clients REST pour
des services Web REST particulirement populaires et je conclurai sur le support serveur
REST offert par Delphi 2010 en tant quextension de larchitecture DataSnap.
Les donnes extraites doivent ressembler ce qui suit (lgrement simplifi pour plus de
lisibilit) lorsquelles sont affiches dans un diteur XML :
TRAITEMENT DES DONNES 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 premire contribution du
blog (item) de la liste :
/rss/channel/item[1]/title
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;
Lcran ci-dessous prsente les effets de lexcution de ce programme :
DES CARTES ET EMPLACEMENTS
Laccs des lments de cartographie et de positionnement est trs souvent ncessaire
pour toutes les applications incluant une ou plusieurs adresses ; de multiples informations
cartographiques sont accessibles sur diffrents sites majeurs (Google, Yahoo, Microsoft,
etc.).
SERVICE DE CODAGE GOGRAPHIQUE DE
GOOGLE
Nous commencerons par le service de gocodification de Google permettant de soumettre
une adresse et de rcuprer ses coordonnes de latitude/longitude partir dune requte
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-aprs les coordonnes de New York dans un navigateur en format XML) :
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 rsultat doit ressembler la copie dcran qui suit :
YAHOO MAPS
Ltape suivante peut consister accder la vritable carte correspondant une adresse.
Google Maps propose dinnombrables fonctionnalits conues pour tre hberges sur les
sites Web plutt que dans les applications clientes. (Je propose nanmoins un exemple
dhbergement Google Map dans un programme client ; larchitecture et le code en sont
nanmoins complexes et ne sont pas prsents dans cette tude).
Ce nouvel exemple dnomm YahooMaps utilise lAPI Yahoo Map pour obtenir une carte et
lafficher dans un contrle 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 excuter les programmes et enregistrer un
fichier INI spcifique dans le rpertoire Documents utilisateur dnomm YahooMaps.ini.
La carte est rcupre en deux tapes : le premier appel HTTP passe ladresse et reoit
lURL de limage de la carte ; le deuxime appel HTTP la rcupre. Une fois encore, ces
deux tapes peuvent tre simules dans un navigateur Web trs utile pour le dbogage.
Le programme utilise la base de donnes et llment StringList de lexemple prcdent. Il
intgre galement un bouton permettant dafficher la carte ou une ville dfinie (par exemple,
San Jos, Californie) avec la mthode 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;
// Chargement de limage
Image1.Picture.LoadFromFile('temp.png');
end;
La premire requte GET HTTP fournit la requte relle et retourne un document XML
avec lURL de limage de la carte comme suit (identifiant long omis) :
<Result>
http://gws.maps.yahoo.com/mapimage?MAPDATA=[...]&mvt=m
&cltype=onnetwork&.intl=us&appid=[...]
&oper=&_proxy=ydn,xml
</Result>
Cest la raison pour laquelle le programme peut extraire la valeur du seul nud avec le
code :
XMLDocument1.DocumentElement.NodeValue
Finalement, limage est enregistre dans un fichier temporaire et charg dans un contrle
Image. Au-del de la carte de cette ville spcifique, le programme peut galement retrouver
celles de la base de donnes Customer.cds de lexemple prcdent. Un bouton permet
dobtenir la carte de lemplacement spcifiquement indiqu (San Jos, exemple de la
dmonstration Delphi Live).
En voici un exemple :
Dans ce cas, il est inutile dobtenir une cl de signature (ni de fichier INI) ; il suffit de fournir
un site rfrent (bien que tout semble fonctionner mme 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 rsultat de cet appel est prsent ci-dessous (avec rsultat JSON pour plus de lisibilit) :
{
"responseData":
{
"translatedText":"Was fr ein schner Tag"
},
"responseDetails": null,
"responseStatus": 200
}
Cet exemple accomplit une tape de plus par rapport aux prcdentes dmonstrations. Au
lieu dune requte HTTP, il utilise un composant VCL spcifique, appel par une mthode de
classe (pour viter de placer le composant dans un formulaire -- mme si cela est possible).
Ce composant de support rend lAPI extrmement simple demploi et encapsule
intgralement lappel HTTP.
COMPOSANT DE TRADUCTION
Ci-aprs la dclaration 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;
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 naime pas du
tout %7 !!!
strResult := Http1.Get(strUrl);
La seule condition est de raliser une requte GET HTTP passant len-tte HTTP standard
avec le nom utilisateur et le mot de passe une opration extrmement simple ajouter
dans Delphi aux proprits correspondantes dun composant IdHttp. Une opration plus
complique, si lon peut dire, consiste poster une mise jour dtat sur votre compte. Cette
opration est ralise avec une requte POST HTTP passant un paramtre dtat comme
dans le snippet qui suit, permettant dadresser Twitter le texte de lun des champs de
lenregistrement actuel dun 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. Aprs avoir
post la valeur du champ TEXT de la table, en ralit, le programme indique Vrai dans le
champ POSTED.
Il y a dautre code dans le programme mais rien qui soit rellement li Twitter ni son API
REST. Llment 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 dautres termes, le codage Twitter est extrmement simple et permet de bnficier dune
mise en ligne automatique intervalles fixes ou en cas de survenance dun vnement
pertinent pour vos activits (et dans votre base de donnes).
sList := TStringList.Create;
try
sList.Text := res;
FAuthString := sList.Values['Auth'];
FReqTime := Now;
finally
sList.Free;
end;
end;
Jai cr un service global de type singleton pour cette classe ; chaque fois quune
requte est ncessaire, japplique une mthode helper (fonction globale), qui ajoute le
jeton supplmentaire. 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
// Ajot autorisation de la cl enregistre
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
Bien quun peu lourd, ce code structurel est prcieux pour simplifier les appels SSL habilits.
Ainsi, pour requrir une liste des feuilles de calcul prives, on utilisera le code suivant :
DoAppRequest (
'get',
'http://spreadsheets.google.com/feeds/spreadsheets/private
/full',
'');
Ce code fait partie de la dmonstration, qui utilise les informations disponibles dans le fichier
dbtosheet.ini structur comme suit et charg dexcuter le programme de dmo :
[google]
email=
password=
accounttype=GOOGLE
Le fichier est charg au dmarrage du programme et les trois valeurs de larborescence sont
utilises pour renseigner l'objet TGoogleAuth, dsign googleAuth. Le quatrime paramtre
le service est dfini 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 dun premier bouton permettant de requrir
la liste de feuilles de calcul disponibles et d'ajouter leurs identifiants (ou URL) dans une bote
liste. Lobjet XML en rsultant est analys avec XPath aprs limination de lespace de
nommage, qui ne doit pas tre pris en compte par les requtes 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 sexcute sur chaque nud (en utilisant la fonction de dcompte XPath pour
dterminer le nombre de nuds) et ajoute le titre de la feuille de calcul et son ID/URL la
liste, comme le montre lillustration suivante :
Grce ces donnes didentification ID/URL, il est possible dadresser une seconde requte
pour demander les feuilles donglet disponibles dans le document spcifique. Mme sil nen
existe quun seul, il est ncessaire de se rfrer un onglet spcifique dans chaque
opration affectant le contenu du document. Ceci permet au programme de rcuprer
lidentifiant dans la liste et dtablir la seconde requte REST :
strSheetId := ListBox1.Items.ValueFromIndex
[ListBox1.ItemIndex];
strXml := DoAppRequest ('get', strSheetId, '');
ce stade, une autre boucle similaire permet dextraire les noms de feuilles et de les
dplacer dans une seconde bote 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 vritable force de ce programme est sa capacit ajouter des donnes au document
lui-mme. Le moyen le plus simple dy parvenir est de crer un document se comportant
comme une table de base de donnes (cest--dire disposant dune premire ligne avec les
noms de champs). Ce type de document, apparaissant dans le navigateur, est illustr ci-
dessous :
Les cellules de la premire ligne sont de simples entres texte. Lintrt est qu'il permet de
se rfrer aux colonnes correspondantes l'aide d'un espace de nommage dynamique
comme :
Ainsi, il est possible dajouter de nouvelles lignes au document en rcuprant les champs
dune table avec les noms correspondants. Ceci ncessite une requte POST HTTP
reposant sur lidentifiant 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 rcupre les valeurs des champs qui nous intressent dans
lenregistrement en cours du jeu de donnes client (ClientDataSet) utilis par le programme
et produit les donnes dentre appropries :
function recordtoxml: string;
begin
Result :=
FieldToXml ('custno') +
FieldToXml ('company') +
FieldToXml ('city') +
FieldToXml ('state') +
FieldToXml ('country');
end;
Comme prcdemment mentionn, lespace de nommage gsx: pseudo se rfre aux noms
des colonnes de la feuille de calcul, qui sont dtermins par les chanes de la premire ligne.
Ce code permet dajouter la feuille de calcul de nouvelles lignes correspondant des
informations enregistres dans la base de donnes.
Cependant, rien ne vous empche (ni toute autre personne disposant de droits ddition sur
le document) dditer les donnes aprs leur publication ; le document recalculera alors
automatiquement les totaux en fonction des donnes adresses par lapplication cliente
permettant n'importe quel utilisateur correctement habilit de consulter le document
rsultant.
Nous avons donc cr un mcanisme sophistiqu de consultation et d'dition de donnes
manant de tables de base de donnes, avec des mises jour automatiques et continues et
une solide infrastructure dautorisation et tout cela partir dun compte Gmail gratuit !
Pour un hbergement effectif sur serveur Web, l'option ISAPI est recommande bien que
loption CGI (gnralement plus lente) soit plus simple paramtrer et dboguer.
Toutefois, pour de relles fonctionnalits de dbogage et de test, il est fortement conseill
d'utiliser l'infrastructure Dbogueur dapplication Web fournie par Delphi, qui permet dajouter
directement un point de rupture au code et de contrler les flux de donnes HTTP (y compris
les en-ttes). Si vous choisissez cette option, il vous faudra insrer un nom de classe
interne, exclusivement utilis comme rfrence programme (dans lURL Dbogueur
dapplication Web). Notez galement que mme si cela nest pas explicitement mentionn
dans la bote de dialogue de lassistant, Delphi continue supporter les modules
dintgration de serveur Apache - tant prcis que le paramtrage projet sera manuel.
La bote de dialogue de lassistant DataSnap WebBroker permet de demander une classe de
mthode serveur prte lemploi avec des chantillons ; cependant, vous pouvez, une fois
encore, trs simplement dployer la vtre dans le code. Avec les paramtrages dcrits ci-
dessus, Delphi gnrera un projet avec diffrentes units organises en arborescence :
Un formulaire principal sans usage spcifique 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 proccupez donc
pas trop.
Un module de donnes Web (hritier de TWebModule) hbergeant les composants
serveur DataSnap
Un module de donnes agissant comme une classe serveur, permettant d'ajouter le
code excuter dans le serveur REST.
MODULE DE DONNES WEB GNR PAR LASSISTANT
DATASNAP WEBBROKER
Nous examinerons tout dabord en dtail ces deux modules de donnes, avant d'ajouter de
nouvelles lignes de code programme et de les tester. Le module Web est un composant cl
de larchitecture WebBroker. Il est capable de dfinir de multiples actions et dispose
d'vnements pr- et post-traitement pour n'importe quelle requte HTTP. Sur ce module
Web, il est possible dajouter des composants qui interceptent des actions URL donnes,
comme DSHTTPWebDispatcher dans lexemple fourni ci-dessous :
Ce composant intercepte nimporte quelle requte avec une URL commenant par
datasnap , transmise au support HTTP de DataSnap. Pour les requtes commenant par
datasnap et indiquant un chemin daccs rest , le traitement sera redirig sur le
moteur REST intgr. En dautres termes, les requtes ayant un chemin d'accs
datasnap/rest sont considres comme des requtes REST.
Sagissant de deux chanes, il est possible de les modifier pour prendre en compte
diffrentes URL, comme il est expliqu plus loin dans ce document. Mais pour commencer,
nous examinerons les paramtrages standards.
Les deux autres composants du module de donnes Web constituent la base globale de
DataSnap et indiquent quelle classe rpondra aux requtes (ou quelles classes, en cas
dajout de composants DSServerClass multiples). Les paramtres par dfaut 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 dmarr (manuellement ou
automatiquement) ; en revanche, la configuration DSServerClass a essentiellement lieu dans
le gestionnaire dvnement retournant la classe cible. Le code par dfaut (gnr par
lassistant), qui retourne la classe du module de donnes secondaire (TServerMethods1)
hberg par lunit 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 rponse HTTP par dfaut 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;
Actions = <
item
Default = True
Name = 'DefaultHandler'
PathInfo = '/'
OnAction = WebModule2DefaultHandlerAction
end>
CLASSE SERVEUR EXEMPLE GNRE PAR LASSISTANT
DATASNAP WEBBROKER
La troisime unit gnre par lassistant DataSnap WebBroker est la classe serveur
exemple celle qui prsente les mthodes invoques distance via REST. Il sagit de la
classe Delphi connecte au composant DSServerClass via le gestionnaire dvnements
DSServerClass1GetClass mentionn prcdemment, qui rcupre lessentiel du code rel.
La classe squelette ainsi gnre est trs simple et dtermine par le fait que des
exemples de mthodes sont demands dans lassistant. En voici le code :
type
TServerMethods1 = class(TDSServerModule)
private
{ Private declarations }
public
http://localhost:8081/FirstSimpleRestServer.FirstSimpleRestSe
rver
Pour rappel, il sagit l du HTML retourn par le programme pour laction standard ce qui,
bien sr, nest pas dun grand intrt...
Ltape suivante consiste utiliser lURL spcifique pour la seule requte excutable par le
serveur REST linvocation de la mthode EchoString du TServerMethods1 du support
rest du serveur datasnap . LURL est automatiquement combine avec ajout du
prfixe du serveur REST (/datasnap/rest, par dfaut), du nom de classe, du nom de mthode
et des paramtres de mthode :
/datasnap/rest/TServerMethods1/EchoString/hello%20world
Dans lURL, %20 remplace simplement un espace (il est galement possible de saisir un
espace dans le navigateur). La rponse JSON du serveur REST apparatra alors sous la
forme suivante :
Pendant l'excution de ces tests, il est possible d'utiliser le Dbogueur dapplication Web
pour dterminer quelles requtes et rponses HTTP doivent effectivement tre transfres.
La page illustre ci-dessus est gnre par une requte 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
Comme mentionn prcdemment, la simplicit daccs ces informations peut savrer trs
utile pour dboguer les applications HTTP.
Une autre option, prsentant plus d'intrt, consiste extraire les informations relles de la
structure de donnes JSON retourne par le serveur. Une approche manuelle pourrait tre
adopte (comme il est montr prcdemment), mais dans ce cas, il peut tre prfrable de
profiter du support JSON disponible dans Delphi 2010.
Ce nouveau support JSON de Delphi est propos via une srie de classes dfinies dans
DBXJSON, qui malgr son nom peut aussi tre utilis dans les applications non lies au
framework dbExpress. Lunit DBXJSON dfinit des classes utilisables pour manipuler
diffrents types de donnes JSON (valeurs individuelles de diffrents types, rseaux, objets,
etc.). Ceci est trs utile pour personnaliser le rsultat des applications serveur (comme nous
le verrons dans le projet suivant) et pour lire les donnes reues par un client, comme dans
le cas qui nous intresse.
Les donnes JSON retournes par le serveur sont prsentes sous forme de chane, mais le
support serveur REST cre un objet associ une valeur nomme (ou paire ) et place la
valeur relle dans une gamme. Cest la raison pour laquelle, aprs analyse du rsultat HTTP
dans une structure de donnes JSON, il est ncessaire de naviguer de lobjet la paire qu'il
contient - et de la paire jusqu llment unique quelle 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
lment dArray
ShowMessage ('The response is: ' + strResult);
finally
jObj.Free;
end;
end;
L encore, la complexit est due la structure de donnes retourne par le serveur, puisque
dans dautres circonstances, il serait bien plus simple danalyser les donnes JSON
rsultantes et dy accder.
APPEL DU SERVEUR REST A PARTIR DUNE APPLICATION
WEB AJAX
Pour un simple transfert dobjet dune application Delphi serveur une autre, il existe de
nombreuses alternatives lutilisation de JSON approche particulirement adapte pour
appeler le serveur Delphi compil partir dune application JavaScript excute dans le
navigateur. Ce cas dutilisation est trs pertinent dans la mesure o la technologie AJAX
(appels JavaScript asynchrones effectus dans le navigateur Web) a t et demeure lun
des lments moteur de ladoption REST. Lappel dun serveur SOAP correspondant laide
dun programme bas sur navigateur est largement plus complexe.
Comment donc crer une application reproduisant le client prcdemment dvelopp tout en
tant excute dans le navigateur Web ? De multiples mthodologies et bibliothques
pourraient tre utilises, mais loption privilgie ce stade est celle de la bibliothque open-
source JavaScript jQuery, disponible sur :
http://jquery.com
Une prsentation approfondie de jQuery et de ses modes dutilisation serait trop longue pour
ce livre blanc, mais nous expliquerons nanmoins le code jQuery sous-jacent de cet
exemple prcis. Tout dabord, 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 trs simple, avec du texte, un champ d'entre
et un bouton (sans fichiers CSS sophistiqus et graphiques ajouts lobjectif tant de se
concentrer sur le point qui nous intresse ici) :
<body>
<h1>jQuery and Delphi 2010 REST</h1>
<br/>
<input type="text" id="inputText" size="50"
value="This is a message from jQuery">
<br/>
<input type="button" value="Echo" id="buttonEcho">
$("#inputText")
Cette opration retourne un objet jQuery encapsulant llment DOM de texte dentre. Pour
dfinir un gestionnaire dvnements, il est possible de transmettre un paramtre de
mthode anonyme la fonction click() du bouton. Deux nouvelles invocations sont
effectues : linvocation REST proprement parler ( partir des donnes getJSON globales)
et linvocation htmlI() pour ajouter le rsultat la page HTML de llment de sortie.
Voici la ligne complte du code sous-jacent de cette dmonstration un snippet de code
JavaScript trs 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 dappeler le
serveur personnalis mais uniquement si les paramtres d'autorisation navigateur
permettent un appel AJAX partir dun fichier local et destination dun serveur REST local.
La plupart des navigateurs nautorisent les invocations de serveurs REST que sur le mme
site que celui dont mane la page HTML.
Dans tous les cas, Internet Explorer semble bien fonctionner sur ce fichier local, aprs
activation des scripts locaux et demande de scurit limite (option disponible puisque le
fichier se trouve sur la machine locale ; voir les icnes de la barre d'tat) :
Sur les autres navigateurs, le serveur Web doit ncessairement retourner la page HTML et
les donnes REST ce qui ne prsente pas de difficult particulire, puisque le serveur
REST est, en fait, un serveur Web. Ainsi, pour une solution serveur (sous langle du
navigateur Web) il suffit dajouter une action au module de serveur Web (quon accrochera
lURL /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 rfrer une page serveur donne avec lURL /file, dobtenir
le fichier avec le code JavaScript et de lui faire appeler le serveur REST :
La diffrence avec l'image prcdente ne tient pas uniquement l'utilisation d'un navigateur
diffrent lURL dsigne est galement diffrente. Ce second cas ne repose pas sur le
chargement dun fichier, mais utilise lapplication REST serveur comme serveur Web
complet, en retournant le code HTML utilis pour invoquer ce mme serveur via AJAX.
type
TMyData = class (TPersistent)
public
Name: String;
Value: Integer;
public
constructor Create (const aName: string);
end;
Les objets sont conservs dans un dictionnaire et implments laide de la classe de
conteneur gnrique TObjectDictionary<TKey,TValue> dfinie dans lunit
Generics.Collections depuis Delphi 2009. Cet objet global est initialis lors du dmarrage du
programme avec lajout dune paire d'objets prdfinis. Nous remarquerons que lajout des
objets repose sur une procdure AddToDictionary spcifique pour garantir que le nom
dobjet est conforme la cl de dictionnaire et reoit une valeur alatoire, si aucune nest
fournie :
var
DataDict: TObjectDictionary <string,TMyData>;
md.Value := nVal;
DataDict.Add(aName, md);
end;
initialization
DataDict := TObjectDictionary <string,TMyData>.Create;
AddToDictionary('Sample');
Une fois cette structure de donnes implmente, il est temps daborder les deux premires
mthodes exemple utilises pour retourner les valeurs JSON. La premire retourne la valeur
de lobjet donn (en choisissant une valeur par dfaut si aucun paramtre nest transmis la
fonction) :
Si nous utilisons une URL avec ou sans le paramtre (comme dans les deux lignes
suivantes) :
/datasnap/rest/TObjectsRest/PlainData/Test
/datasnap/rest/TObjectsRest/PlainData
nous obtiendrons tout de mme une rponse JSON, pour lobjet spcifique ou pour un objet
par dfaut :
{"result":[8978]}
Comment retourner un objet complet et non une valeur spcifique ? Le serveur REST ne
peut pas retourner une valeur TObject car le systme nest pas capable de le convertir
automatiquement ; nanmoins, il peut utiliser le nouveau support de conversion JSON pour
convertir un objet existant en format JSON :
Cette approche est surtout utile pour recrer lobjet dans lapplication cliente Delphi et ne
prsente pas grand intrt dans les cas o le client est dvelopp 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 crer un au niveau serveur laide de classes de support comme nous
lavons 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 proprits pour le
nom et la valeur. Nous aurions pu utiliser un nom dynamique (cest--dire la dsignation du
nom faisant partie de la paire), mais cela aurait complexifi la rcupration des donnes sur
le client. Le rsultat de ce code retournera un code JSON plus net, semblable lexemple
suivant :
{"result":[{
"Name":"Test",
"Value":8068
}]}
<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);
} );
Linvocation AJAX de MyData transmet le nom dobjet comme nouveau paramtre URL et
extrait du groupe de rsultat la valeur nomme proprit/paire en laffichant dans un lment
HTML avec espace vide. Un processus similaire (bien qu'un peu plus complexe) a lieu pour
la liste. Il sagit l encore dun appel AJAX, mais cette fois, le code HTML rsultant doit tre
dvelopp. L'opration est excute dans une fonction refreshList spare, appele
automatiquement au dmarrage et manuellement par lutilisateur :
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 vrifier le groupe gnr. Nous aurions pu utiliser le
mcanisme dnumration $.each de jQuery, mais cela aurait complexifi la lecture du code.
La boucle cre le code HTML, qui saffiche ensuite dans lemplacement espace avec lID
donne. Il sagit dune sortie individuelle incluant la valeur de lobjet Sample (le code
dcrit prcdemment) et la liste des valeurs retournes dans le groupe JSON :
$(document).ready(function() {
refreshList();
$("#refresh").click(function(e) {
refreshList();
} );
Quelques oprations restent encore pour gnrer le code Ds que le code HTML est prt
pour la liste (il sagit dune liste de liens), il doit tre accroch ces liens pour que
chaque entre de la liste dans lapplication cliente, lorsquelle est slectionne, entrane le
chargement de lobjet correspondant au niveau serveur. Linterface utilisateur des donnes
objet est constitue de deux botes dentre, qui seront ultrieurement utilises pour
manipuler les donnes objet. Le comportement est ajout chaque point dancrage 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 lutilisation de lappel $(this), qui constitue plus ou moins le paramtre metteur pour
un vnement Delphi. Le contenu html de llment slectionn est son texte, qui est le nom
de llment transmettre au serveur dans lURL avec lexpression :
Aprs avoir dvelopp ce code, on pourra observer laction produite lorsque lon clique sur
lun des lments de la liste : une nouvelle invocation AJAX est effectue destination du
serveur pour demander une valeur donne et la valeur retourne saffiche dans deux botes
dentre texte :
Comme le montre cette illustration, le programme permet de rcuprer une valeur, mais
comporte galement trois boutons pour excuter les oprations les plus communes
(Cration/Lecture/Mise jour/Suppression). Ces actions sont supportes en HTML laide
des 4 mthodes HTML de code (respectivement, PUT, GET, POST et DELETE). Dans le
chapitre suivant, nous verrons comment ces mthodes sont supportes par un serveur
REST Delphi 2010.
POST, PUT ET DELETE
Jusqu prsent, nous avons vu comment obtenir des donnes du serveur REST. Mais quen
est-il de leur mise jour ? Lide gnralement admise pour lenvironnement REST est quil
faut viter dutiliser des URL spcifiques pour identifier les oprations et quil est prfrable
de nen utiliser quune seule pour identifier les objets serveur (comme MyData/Sample dans
le cas dvelopp ici) et dutiliser les mthodes HTTP pour indiquer les actions excuter.
Il serait regrettable que le support REST de Delphi se contente d'associer les URL aux
mthodes ; mais au contraire, il relie non seulement les URL, mais galement la mthode
HTTP aux mthodes selon un schma relativement simple : le nom de lopration est
initialement associ au nom de mthode laide du mappage suivant :
GET obtention (par dfaut, peut tre omis)
POST mise jour
PUT acceptation
DELETE annulation
Ces mappages peuvent tre personnaliss en manipulant les quatre gestionnaires
dvnements correspondants du composant DSHTTPWebDispatcher. En cas de choix des
rgles de nommage standards, le support des diffrentes oprations exigera de dfinir 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 lment, seul le nom est ncessaire ; en revanche, la cration
ou la mise jour dun lment exigera un second paramtre, en plus des donnes.
Limplmentation des trois nouvelles mthodes est assez simple et directe, notamment parce
quelles nont pas retourner une valeur (il va sans dire quon aura vrifi au pralable que
les paramtres ne sont pas vides et que l'objet serveur existe rellement) :
procedure TObjectsRest.updateMyData (name, value: string);
begin
DataDict[name].Value := StrToIntDef (Value, 0);
end;
$("#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 crer lURL avec la rfrence
lobjet supprimer :
$("#buttonDelete").click(function(e) {
$.ajax({
type: "DELETE",
url: baseUrl + "MyData/" +
$("#inputName").val(),
success: function(msg){
$("#log").html(msg);
}
});
});
Cette mthode est invoque par lapplication cliente aprs le chargement de la page,
travers la construction dynamique d'une table HTML avec le code jQuery suivant (semblable
au code amplement dvelopp dans les chapitres prcdents) :
$(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++) {
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 mtadonnes :
$.getJSON(
"/DataRestServer.RestDataServer/datasnap/rest/TServerDa
ta/Meta",
function(data) {
theMetaArray = data.result[0];
Ces informations sont utilises pour crer lentte de table et accder dynamiquement aux
proprits dobjet, avec l'objet de notation [propertyname]. Dans le cas qui nous intresse,
le code existant utilis pour accder un objet avec le symbole de proprit :
thearray[i].Company
devient le code ci-dessous, qui lit la proprit par nom, en utilisant le nom du champ
enregistr dans les mtadonnes :
thearray[i][theMetaArray[j]].
// 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>";
L encore, cet exemple noffre quune base de dpart et n'utilise aucune des extensions
jQuery, qui ajouteraient des fonctionnalits significatives aux tables HTML en les
transformant en puissantes grilles dinterface utilisateur avec des outils de tri, de filtrage et
ddition.
CONCLUSION
Ce livre blanc ne fait queffleurer larchitecture REST et le dveloppement dapplications
REST client et serveur avec Delphi 2010. Il fournit un aperu trs rapide des technologies
associes (XML, JSON, XPath, JavaScript, jQuery) qui peuvent tre ncessaires pour
devenir expert des architectures REST.
tant donn le nombre croissant de serveurs REST publics, lmergence du cloud-
computing et lintrt grandissant pour lhbergement dapplications Web, Delphi pourrait
jouer un rle significatif dans le dveloppement de riches clients dinterface appelant des
serveurs distants et dans le dveloppement de serveurs rels de manipulation de structures
de donnes dans une application cliente (dveloppe dans nimporte quel langage) ou
directement dans le navigateur.
Comme lont montr les dmonstrations finales, la combinaison de JavaScript et dun
serveur REST Delphi permet dutiliser lEDI Embarcadero pour dvelopper des applications
Web professionnelles, modernes et de qualit optimale.
A PROPOS DE LAUTEUR
Marco est lauteur du best-seller Mastering Delphi et a publi, au cours des dernires
annes, des ouvrages sur les dernires versions de Delphi, notamment Delphi 2007
Handbook , Delphi 2009 Handbook et le Delphi 2010 Handbook (actuellement en
cours de rdaction).
Paralllement ses prestations de formation et de consulting sur Delphi, Marco propose des
services de consulting sur les architectures Web et lintgration Web des projets Delphi
sous langle de linvocation dautres 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 llaboration de ce livre blanc,
tout particulirement sur le support JSON de Delphi 2010.
Embarcadero Technologies, Inc. est un diteur leader de solutions primes pour les
dveloppeurs dapplications et les professionnels des bases de donnes pour les aider
concevoir et excuter plus efficacement et rapidement leurs solutions quels que soient les
langages de programmation ou les plates-formes. 90 des 100 premires socits du
classement Fortune et une communaut active de plus de 3 millions dutilisateurs dans le
monde font confiance aux outils Embarcadero pour maximiser leur productivit, rduire les
cots, simplifier la gestion du changement et de la conformit et acclrer linnovation. Ses
produits les plus emblmatiques sont notamment : Embarcadero Change Manager,
Embarcadero RAD Studio, DBArtisan, Delphi, ER/Studio, JBuilder et Rapid SQL.
Cr en 1993, le sige dEmbarcadero se situe San Francisco ; lentreprise est prsente
dans le monde entier et son site Web peut tre consult ladresse www.embarcadero.com