Bob Swart (aka Dr.Bob)
Kylix 2 Web Services

De eerste versie van Kylix was nog geen negen maanden uit of de tweede versie werd al aangekondigd en is inmiddels beschikbaar. Niet langer in een Kylix Desktop en Kylix Server editie, maar nu in een Kylix 2 Professional en Kylix 2 Enterprise editie. Daarnaast is er een Kylix 2 Open Edition beschikbaar (maar die is 30,338,464 bytes groot, en dat is zonder kabelmodem of ASDL een beetje te groot om te downloaden). Tot slot is er ook de Kylix 2 Enterprise "Trial Edition", en die is ook beschikbaar op CD (ik heb er al bijna honderd weg mogen geven bij de Internet In Business beurs en tijdens mijn trainingen).
In dit artikel, dat ook verschijnt in het januari 2002 nummer van Blaise, ga ik gebruik maken van de Kylix 2 Enterprise 60-dagen trial editie. Dit is een volledig werkende versie van Kylix 2 Enterprise, alleen is het na 60 dagen afgelopen. Daarnaast is het verboden om toepassingen (gemaakt met deze trial editie) te distribueren - daar moet je een echte licentie van Kylix 2 voor hebben, of de Kylix 2 Open Editie, die echter een hoop leuke zaken mist, zoals... SOAP en Web Services. Dit zijn uitbreiding van de normale web server toepassingen.
Voordat ik daarmee begin overigens eerst nog wat goed nieuws: Borland heeft (weer) het licht gezien, en de WebBroker tools en componenten in de Kylix 2 Professional versie gestopt (in het vorige nummer van Blaise beschreef ik de unit DrBobCGI die nodig was omdat in Kylix 1 Desktop de WebBroker componenten ontbraken), waardoor DrBobCGI voor Kylix 2 eigenlijk alleen nog nut heeft voor de Kylix 2 Open Edition.

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:

kylix 2 object repository

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:

new soap server application dialoog

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:

soap web module

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...
De HTTPSoapDispatcher component is verantwoordelijk voor het afhandelen van (via HTTP) binnenkomende SOAP berichten, en zal deze "uitdelen" aan de verantwoordelijke onderdelen (de interfaces en methodes die we straks gaan maken, en naar buiten beschikbaar worden gesteld). De HTTPSoapPascalInvoker zorgt ervoor dat de vertaalde SOAP berichten worden vertaald naar ObjectPascal aanroepen van de juiste methoden. Ook het omzetten van eventuele input parameters en het teruggeven van het resultaat wordt door dit component verzorgt. De HTTPSoapDispatcher kan via zijn Dispatcher property en daarvan de Converter subproperty bij de HTTPSoapPascalInvoker (om de binnengekomen SOAP berichten door te geven aan de HTTPSoapPascalInvoker en die ervoor te laten zorgen dat de juiste ObjectPascal methode wordt aangeroepen).
De derde component, WSDLHTMLPublish, werkt niet direct samen met de eerste twee, maar heeft een niet minder belangrijke taak. Waar de eerste twee ervoor zorgen dat de afhandeling van binnenkomende SOAP berichten goed verloopt, zal de WSDLHTMLPublish juist de beschikbare interfaces en hun methoden (dynamisch) publiceren, zodat iedereen die gebruik wil maken van onze Web Service zelf kan zien wat er allemaal mee mogelijk is. Zonder dat je daar als bouwer nog extra tijd en moeite in hoeft te steken (alhoewel het vaak geen kwaad kan om het interface alsnog in "mensentaal" te documenteren, natuurlijk). SOAP Interface Zoals gezegd, aan de drie componenten op de web module hoeven we niets meer te doen. Integendeel, we kunnen onze aandacht beter besteden aan het ontwerpen van het SOAP interface van de "engine" die we willen bouwen.
Bewaar eerst de web module in sWebMod.pas (een SOAP Web Module) en het project dat we tot nu toe hebben in Euro.dpr (of een andere zinvolle naam). Voeg nu een nieuwe unit toe, door File | New en dan de New Unit te kiezen. Bewaar deze nieuwe unit onder de naam uEuro.pas en stop hier de source code voor het Euro interface in.
In dit geval wederom een Gulden-Euro conversie tool. Het interface zal IEuro heten, en moet afgeleid zijn van de IInvokable (een interface dat "van buiten af" te gebruiken is). Ik gebruik twee functies: de GuldenToEuro en de EuroToGulden, die allebei een bedrag als argument mee krijgen, en het geconverteerde bedrag als resultaat hebben. De klasse die het interface daadwerkelijk implementeert is de TEuro, en die is afgeleid van de TInvokableClass.
In ObjectPascal code ziet dat er als volgt uit:
  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.

euro42 web service onder linux (in windows' browser)

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).

web services import wizard

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).
Wat we ook doen, uiteindelijk moeten we op de Generate knop drukken om een import unit te laten genereren. Die zal de definitie van ons IEuro interface bevatten, maar zonder de implementatie (omdat die slechts bij de server aanwezig is - de client hoeft alleen maar van het interface af te weten om het SOAP object te kunnen gebruiken).
De gegenereerde unit, die ik bewaar in EuroImport.pas, ziet er als volgt uit:
  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.

euroclient mainform tijdens design-time

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).
De tweede property die we een waarde moeten geven is de Service property. Als we de vorige property een juiste waarde hebben gegeven gaat de Service property echter vrij eenvoudig: we hoeven alleen maar de combobox voor de waarde te openen om de enige mogelijkheid (IEuroservice) te kiezen. Als we een foutieve URL opgaven bij de WSDLLocation property, dan zullen we hier geen waarde voor de Service kunnen kiezen (een soort extra check dus).
Tot slot moeten we ook nog de Port property een waarde geven, en dit gaat net als bij de Service property: de enige waarde (IEuroPort) kiezen uit de combobox.

httprio instellingen

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.

euro web service in aktie!

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.


This webpage © 1999-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.