Kylix 2 Web Services |
Web Services
Een Web Service klinkt heel mooi, maar is niets anders dan een "service" die beschikbaar wordt gesteld via het web.
Niets anders dan een web server toepassing dus, die echter niet vanuit een browser wordt gebruikt, maar vanuit een willekeurige andere toepassing, waarbij de beide toepassingen communiceren via het HTTP protocol en de berichten die ze naar elkaar sturen "inpakken" in zgn.
SOAP enveloppen.
SOAP staat hierbij voor het Simple Object Access Protocol, dat een eenvoudige manier biedt om een remote object te laten benaderen door een client.
Eenvoudig is natuurlijk relatief, maar het werkt in ieder geval een stuk makkelijker dan bijvoorbeeld DCOM of CORBA (om maar eens twee protocollen te noemen die behoorlijk wat tijd vergen om goed te laten werken).
Zie mijn website SOAP Bubbles voor meer algemene informatie over SOAP (voor zowel Delphi 6 als Kylix 2).
Kylix 2 Enterprise (trial edition)
De installatie van Kylix 2 Enterprise vereist een serie nummer en code die je bij de CD krijgt (of vanaf de Borland website kan ophalen) en daarna moet Kylix 2 Enterprise nog geaktiveerd worden, wat met een speciale code kan die je ook op de website (of per telefoon) kan opvragen.
Trek er al met al maar een klein uurtje voor uit, maar daarna kun je 60 dagen genieten van alle mogelijkheden van Kylix 2 Enterprise, en dat zijn er heel wat.
Euros en Guldens
De vorige keer heb in een kleine "normale" web server CGI toepassing geschreven die bedragen in Gulden kon converteren naar Euro (en andersom).
Dit is een handige functionaliteit, echter het interface - de webbrowser - is niet altijd de meest voor de hand liggende vorm.
Soms wil je deze functionaliteit ingebed hebben in een "normale" toepassing, en is het onhandig dat je een web server toepassing moet gebruiken om de Guldens naar Euros om te zetten.
En dan vinden we het wiel nog maar een keer uit, en schrijven de functionaliteit opnieuw.
Dat hoeft niet als we gebruik gemaakt hadden van Web Services, en de converteerfunctionaliteit als een web service "engine" beschikbaar hadden gemaakt.
Hoe dat dan precies in zijn werk gaat zal ik deze keer laten zien.
SOAP Server
Genoeg gepraat, laten we aan de slag gaan met Kylix 2 Enterprise (de echte of de 60-dagen trial versie).
Om een Web Service te bouwen moeten we beginnen met een "SOAP Server Application", en die vinden we op de WebServices tab van de Object Repository nadat we File | New hebben gedaan:
Klik op de SOAP Server Application icoon (merk op dat je alleen maar "SOAP Ser..." ziet, maar de eerste is voor een nieuw projekt, en de tweede voor een "SOAP Server Data Module" die we niet nodig hebben). Zoals ik al eerder zei, is een SOAP server toepassing in feite een speciaal soort web server toepassing, en daarom krijgen we als eerste de vraag van Kylix wat voor soort web server toepassing we willen maken. Omdat je voor een Apache DSO module eerst de Apache web server moet hercompileren (de DSO ondersteuning staat normaal gesproken niet aan), kies ik hier altijd voor een "normale" CGI toepassing:
Druk nu op OK om Kylix een nieuwe (lege) SOAP server toepassing te laten genereren.
SOAP Web Module
Het resultaat is een WebBroker toepassing, waarbij de web module vast drie componenten kado heeft gekregen: een HTTPSOAPDispatcher, een HTTPSOAPPascalInvoker en een WSDLHTMLPublish component:
Deze drie componenten zijn verantwoordelijk voor het "web service" gedrag van de web server toepassing, en verzorgen ook de SOAP afhandeling. Ze zijn er al helemaal klaar voor. Toch is het misschien fijn te weten wąt ze dan precies doen...
unit uEuro;
interface
uses
InvokeRegistry;
type
IEuro = interface(IInvokable)
function GuldenToEuro(const Amount: double): double; stdcall;
function EuroToGulden(const Amount: double): double; stdcall;
end;
TEuro = class(TInvokableClass, IEuro)
public
function EuroToGulden(const Amount: Double): Double; stdcall;
function GuldenToEuro(const Amount: Double): Double; stdcall;
end;
implementation
{ TEuro }
const
GuldenPerEuro = 2.20371;
function TEuro.EuroToGulden(const Amount: Double): Double;
begin
Result := Amount * GuldenPerEuro
end;
function TEuro.GuldenToEuro(const Amount: Double): Double;
begin
Result := Amount / GuldenPerEuro
end;
initialization
InvRegistry.RegisterInterface(TypeInfo(IEuro));
InvRegistry.RegisterInvokableClass(TEuro);
end.
Merk op dat we in de initialization sectie van de unit zowel het interface IEuro als de InvokableClass TEuro zelf moeten registreren in de zogenaamde Invokable Registry.
Hierdoor zal o.a.
de WSDLHTMLPublish component in staat zijn om dynamisch WSDL te genereren voor het gedrag (het interface) van de web service.
Klaarzetten en Testen
Nu we een SOAP interface en implementatie aan onze Web Service hebben toegevoegd zijn we bijna klaar.
We moeten de resulterende Euro toepassing nog in de cgi-bin directory van onze Apache web server neerzetten (die wordt standaard met vrijwel iedere Linux distributie bijgeleverd, en anders is de Apache webserver te downloaden).
Als Euro in de cgi-bin directory staat (bij mij is dat /home/httpd/cgi-bin) kan ik de SOAP Server toepassing aanroepen met de /wsdl toegevoegd aan de URL, zodat ik de lijst van aanwezige web services kan zien.
Het werkt ook met Web Services die op het "echte" internet te vinden zijn, zoals mijn uitgebreide versie van de Euro converter (die ook andere values naar de Euro kan omzetten) die te vinden is op mijn www.drbob42.co.uk Linux web server en te bekijken als http://www.drbob42.co.uk/cgi-bin/Euro42/wsdl.
Behalve de IEuro die we zelf hebben gebouwd, krijgt iedere Delphi of Kylix SOAP Server toepassing ook de IWSDLPublish web service mee.
IEuro Gebruiken
We zijn klaar met de "motor" van onze Web Service.
Het is nu tijd om een client te maken, ook wel "consumer" genaamd.
Dit betekent een geheel nieuw project (het vorige kunnen we opslaan en afsluiten).
Vanwege het platform- en taalonafhankelijke karakter van SOAP hadden we de client ook in een heel andere taal kunnen schrijven - zoals bijvoorbeeld Delphi 6 of C# of zelfs Java.
Maar omdat dit als Kylix 2 artikel begon, wil ik het ook zo afmaken, en een Kylix 2 toepassing schrijven die de zojuist ontwikkelde Web Service gebruikt.
Start een nieuwe toepassing met File | New Application.
Bewaar de unit in MainForm.pas en het project in EuroClient.dpr (of iets dergelijks).
Om de Euro Web Service te kunnen gebruiken moeten we twee belangrijke dingen doen: een WSDL import unit maken en de HTTPRIO component gebruiken.
WSDL Import Unit
Allereerst moeten we uit de WSDL (Web Service Description Language) voor de Euro Web Service een Object Pascal import unit genereren, die precies die methoden specificeert die in het interface IEuro zitten.
Omdat we dit net zelf hebben geschreven lijkt dit een triviale taak (en wellicht onnodig op dit moment), maar bedenk dat de Web Service in een andere omgeving geschreven had kunnen zijn (of dat je de wat uitgebreidere Euro42 Web Service gebruikt die ik op het internet heb gezet - zonder source code deze keer).
Dit doen we met de Web Services Importer wizard uit de WebServices tab van de Object Repository (het meest rechtse icoon in Figuur 1), die je krijgt na File | New.
De dialoog van de Web Services Import wizard kan gebruikt worden om de URL van de Web Service op te geven, in dit geval http://localhost/cgi-bin/Euro/wsdl/IEuro - maar dit kan ook een echte URL van het internet zijn, zoals http://www.drbob42.co.uk/cgi-bin/Euro42/wsdl/IEuro bijvoorbeeld (deze laatste URL wijst naar een Euro42 Web Service die wat uitgebreider is dan het voorbeeld in dit artikel).
We kunnen echter ook met de Browse knop een lokaal WSDL bestand inladen. Bijvoorbeeld eentje die je krijgt als je http://localhost/cgi-bin/Euro/wsdl/IEuro in Netscape wilt laten zien (wat Netscape niet kan, en vervolgens met een Save dialoog komt).
Unit EuroImport; interface uses Types, XSBuiltIns, SysUtils; type IEuro = interface(IInvokable) ['{12345678-1234-1234-1234-1234567890AB}'] function GuldenToEuro(const Amount: Double): Double; stdcall; function EuroToGulden(const Amount: Double): Double; stdcall; end; implementation uses InvokeRegistry; initialization InvRegistry.RegisterInterface(TypeInfo(IEuro), 'urn:uEuro-IEuro', ''); end.Merk op dat het IEuro interface hier plotseling een GUID heeft (de regel met de vele cijfers). Dit is een Global Unique IDentifier die we ook kennen uit de COM wereld van Windows. Maar wat moet een GUID nou in Kylix op Linux? Gelukkig heeft het niks met COM te maken, maar wel met interfaces, die in KYlix zijn geļmplementeerd zonder COM maar met een GUID als we gebruik willen maken van interface RTTI (run-time type information). En waarom is dat nou weer nodig? Dat zullen we zo zien, als we de HTTPRIO component gaan gebruiken.
HTTPRIO Component
Als we de Euro import unit gegenereerd hebben kunnen we verder met de tweede stap: een speciaal component gebruiken dat een koppeling kan maken met de daadwerkelijke SOAP server toepassing, en zich (voor onze client toepassing) kan voordoen alsof hij het remote object zelf is.
Dat doen we met het HTTPRIO component - dat staat voor HTTP-Remote-Invokable-Object (dus een remote aan te roepen object via HTTP) - te vinden op de WebServices tab van het component palette.
In onderstaand plaatje heb ik daarnaast vast twee labels, twee editboxen en twee buttons geplaatst.
Het begint er al een beetje leuk uit te zien, alleen doet het nog niks.
We moeten de HTTPRIO component nog helemaal instellen - dat is wel iets wat we met de hand moeten doen. Er zijn drie properties die een waarde moeten krijgen. Om te beginnen de WSDLLocation property, die moet wijzen naar de URL waar de WSDL voor de Euro Web Service te vinden is. Dat hebben we eerder gehoord, en de waarde in ons voorbeeld is inderdaad nog steeds http://localhost/cgi-bin/Euro/wsdl/IEuro dus vul dat in (als je de Web Service vanaf een andere machine wilt gebruiken zul je in plaats van localhost het IP-adres of de naam van die andere machine moeten opgeven).
HTTPRIO Gebruiken
We zijn er bijna, echt waar.
We moeten nu alleen nog het HTTPRIO component op het juiste moment gebruiken.
Dat doen we uiteraard in de OnClick event handlers van de beide buttons.
De Buttons heb ik overigens btnNaarEuro en btnNaarGulden genoemd, en de twee editboxen heten edtEuro en edtGulden (dat maakt de code wat beter leesbaar).
Zoals ik al eerder zei, is het HTTPRIO een representatie van een remote invokable object, maar op zichzelf staand weet het nog niet welke methoden erin zitten.
Daarom moeten we het HTTPRIO component expliciet casten naar het IEuro interface.
Letterlijk moeten we het IEuro interface "eruit halen" alvorens het te kunnen gebruiken.
Hier hebben we het AS keyword voor nodig, en dat werkt alleen als er RTTI in het spel is.
En dat werkt bij interfaces alleen als er een GUID is gespecificeerd.
Vandaar de noodzaak van het eerdergenoemde GUID (probeer het maar te verwijderen, dan zul je zien dat de code niet langer compileert).
De OnClick event handlers van de beide buttons zijn als volgt ingevuld:
procedure TForm1.btnNaarEuro(Sender: TObject); begin edtEuro.Text := FloatToStr( (HTTPRIO1 AS IEuro).GuldenToEuro( StrToFloatDef(edtGulden.Text,0))); end; procedure TForm1.btnNaarGulden(Sender: TObject); begin edtGulden.Text := FloatToStr( (HTTPRIO1 AS IEuro).EuroToGulden( StrToFloatDef(edtEuro.Text,0))) end;Merk op dat ik de FloatToStr nodig heb om het resultaat van GuldenToEuro en EuroToGulden weer in de Text property van de EditBoxen te zetten, en dat ik bovendien de StrToFloatDef moet gebruiken om de inhoud van een editbox om te zetten naar een Floating Point waarde. Ik gebruik daar overigens StrToFloatDef voor, omdat die anders dan StrToFloat ook een default waarde meekrijgt die het resultaat is als de string geen geldige waarde bevat (of leeg is). Erg handig, maar dat terzijde.
Euro in Aktie!
Het is nu nog een kwestie van compileren en draaien maar.
De EuroClient ziet er weinig bijzonder uit, maar bij iedere klik op een button zal er een SOAP request uitgaan naar de Euro Web Service, waarbij wordt aangegeven welke methode er moet worden aangeroepen (EuroToGulden of GuldenToEuro) en wat de waarde van de parameter is.
Het resultaat komt ook weer via SOAP terug, en uiteindelijk in de editbox terecht.
Web Services
Tot slot een kleine aanzet tot een discussie.
Wat is het nut van Web Services?
Welnu, het biedt de mogelijkheid om toepassingen weer te spliten in twee delen: engines en user interfaces.
Dit is een onderscheid dat ik zelf een jaar of tien geleden al tegenkwam toen Windows 3.x er net was, en de DLLs (Dynamic Link Libraries) net om de hoek kwamen kijken.
In die tijd was het ook gebruikelijk om aan te geven dat DLLs een goede plek waren om engines en niet-visuele functionaliteit in op te nemen, die vervolgens vanuit een toepassing met een user interface gebruikt werden.
Los van de modularisatie zou ook hergebruik van functionaliteit plaats kunnen vinden (en potentieel minder bugs en toegenomen onderhoudbaarheid door meer gemeenschappelijke code).
En ach, ik wil niet veel zeggen, maar ik heb meer DLLs dan EXEs op mijn machine staan, alhoewel het met het hergebruik wel meevalt.
Met uitzondering van de Windows systeem DLLs (de APIs) merk ik niet zoveel van hergebruik van DLLs door andere toepassingen.
Misschien dat Web Services dit kunnen oplossen, omdat daar een voordeel is dat het interface van de Web Service door de Web Service zelf "gepubliceerd" wordt.
Iets wat bij DLLs altijd een probleem was (en nog is) - als er al een interface definitie was bestond die meestal uit een C DLL header file.
Niet het meeste fijne om mee aan de slag te gaan.
Gelukkig is de vertaling van WSDL naar ObjectPascal een stuk eenvoudiger, zodanig dat Delphi en Kylix het automatisch voor ons kunnen doen.
En daarmee Web Services toegankelijk maken voor ons.
En ons beter in staat stelt Web Services te bouwen die weer door anderen gebruikt kunnen worden - kijk als afsluiting maar eens op http://www.xmethods.com.
Meer Informatie
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .
Voor meer informatie verwijs ik daarnaast graag nog naar mijn Delphi Clinic over Web Services.