Bob Swart (aka Dr.Bob)
Delphi 6 Web Services (2) bouwen

Dit artikel is het tweede deel van een korte serie over Delphi 6 en Web Services. In het eerste deel liet ik zien hoe we bestaande Web Services kunnen gebruiken in Delphi 6 toepassingen, en in dit tweede deel zal ik laten zien hoe we zelf Web Services kunnen implementeren in Delphi 6.
Enkele Delphi 6 Web Services die ik heb geschreven zijn reeds beschikbaar voor daadwerkelijk gebruik, zie ook Dr.Bob's SOAP Bubbles.

Web Services
De vorige keer liet ik zien hoe we bestaande Web Services konden gebruiken in Delphi 6. Ik liet zien hoe we de NumberToWords en de BabelFish services konden combineren. Deze keer zal ik laten zien hoe we zelf een nieuwe Web Service kunnen bouwen in Delphi 6: een NumToStr die een getal omzet in Nederlandse woorden. Nadat we de Web Service gebouwd hebben zal ik uitleggen en demonstreren hoe deze gedeployed kan worden (en het eindresultaat is vandaag de dag beschikbaar op het internet, zodat je er met de informatie in het vorige artikel direkt gebruik van kan maken).

Delphi 6
Gebruik van Web Services in Delphi 6 is makkelijk, maar het bouwen van een nieuwe Web Service in Delphi 6 is niet veel moeilijker. Start Delphi 6 Enterprise, doe File | New | Other en ga weer naar de WebServices tab van de Object Repository. Hier zijn drie Wizards: eentje voor een nieuwe SOAP Server Application, eentje voor een Soap Server Data Module en eentje voor de Web Service Importer. De Web Service Importer hadden we de vorige keer al gebruikt, en deze keer hebben we de Soap Server Application wizard nodig om een nieuwe web service te starten.

Soap Server Application
De Soap Server Application Wizard vraagt het soort web server toepassing; de "schil" van de Web Service. De Web Service wordt op die manier een web server toepassing die WSDL produceert en zichzelf via SOAP beschikbaar stelt aan de buitenwereld. Maar voordat we aan de WSDL en SOAP gaan werken moeten we eerst nog het soort web server toepassing kiezen. De keuzemogelijkheden zijn ISAPI/NSAPI, standaard CGI, WinCGI, Apache (nieuw in Delphi 6) of een Web App Debugger toepassing. Deze laatste keus is ook nieuw in Delphi 6, en verschaft ons een uitermate handige manier om web server toepassingen te debuggen (inclusief de web service in dit geval). Het nadeel is dat een Web App Debugger executable niet zonder meer gedeployed kan worden (daarvoor moet je de web module eerst weer in een normale CGI, WinCGI, ISAPI/NSAPI of Apache web server toepassing hangen).

CGI Stand-alone executable
Aangezien de meeste ISPs het niet (of slechts moeizaam) toestaan om ISAPI or NSAPI DLLs op hun web server te deployen, kies ik hier meestal voor CGI. Nadeel is natuurlijk de performance, omdat een CGI web server toepassing voor iedere request opnieuw geladen moet worden (zelfs bij het opvragen van de WSDL). Voor real-world Web Services raad ik dan ook een dedicated web server aan waar je wel ISAPI/NSAPI of Apache DLLs mag neerzetten (het kost vaak een beetje, maar het resultaat is ook meteen een stuk beter).
Voor de demonstratie Web Service van deze keer kies ik echter voor een standaard CGI toepassing, waardoor ik hem in ieder geval makkelijk kan deployen.

Web Module
Zodra je in de Soap Server Application Wizard op OK klikt krijg je een normale web module, maar met drie speciale componenten: een HTTPSoapDispatcher, een HTTPSoapPascalInvoker en een WSDLHTMLPublish component. De eerste beantwoordt de binnekomende SOAP verzoeken, en verzend (dispatches) ze voor onze web module. Het tweede component is met name geschikt om de binnenkomende SOAP verzoeken (requests) te vertalen naar native method calls van onze web service (object). Het derde component tenslotte wordt alleen maar gebruikt om de beschrijving van de Web Service te produceren, in WSDL, aan de hand van het gepubliceerde invokable interface.

Bewaar de web module in file SWebMod.pas en het project als NumberToWordsInDutch.dpr voor we verder gaan.

Invokable Interface
We hebben nu een web service skelet, maar nog niks om daadwerkelijk te publiceren naar de buitenwereld. Hiervoor moeten we twee dingen doen: een interface definiëren (en naar buiten publiceren), en vervolgens het gedrag van dit interface ook daadwerkelijk implementeren (aan de server kant). Zoals we de vorige keer al een beetje zagen moet het interface afgeleid zijn van IInvokable. In onze voorbeeld zal ik een IDutch interface afleiden van IInvokable, en er één methode aan toevoegen: NumToStr. De unit met de source code voor het interface (nog zonder de implementatie) is als volgt: follows:

  unit Dutch;
  interface
  uses
    InvokeRegistry;

  type
    IDutch = interface(IInvokable)
      ['{3E707BC1-AD77-4D55-9083-6A59887BFE89}']
      function NumToStr(Num: LongInt): String; stdcall;
    end;

  implementation

  initialization
    InvRegistry.RegisterInterface(TypeInfo(IDutch));
  end.
We moeten RegisterInterface aanroepen teneinde het IDutch interface aan te melden in de Invokable Registery.
Tip: toets Ctrl+Shift=G om een unieke GUID value in de code editor te krijgen.

Interface Implementation
We hebben nu het interface IDutch gedefinieerd, maar nog niet geïmplementeerd. En zonder dat laatste kunnen web service clients er in feite nog niks mee. Gelukkig is de implementatie eveneens vrij eenvoudig (alhoewel de listing van de routine NumToStr zelf vrij lang is).
Merk op dat we de klasse TDutch (de implementatie van IDutch) eveneens moeten registeren in de Invokable Registry.

  unit DutchImp;
  interface
  uses
    InvokeRegistry, Dutch;

  type
    TDutch = class(TInvokableClass, IDutch)
      function NumToStr(Num: LongInt): String; stdcall;
    end;

  implementation

  function TDutch.NumToStr(Num: LongInt): String;
  begin
    if Num >= 1000000 then
      if (Num mod 1000000) = 0 then
        Result := NumToStr(Num div 1000000) + 'miljoen'
      else
        Result := NumToStr(Num div 1000000) + 'miljoen ' +
                  NumToStr(Num mod 1000000)
    else
      if Num >= 1000 then
        if (Num mod 1000) = 0 then
          Result := NumToStr(Num div 1000) + 'duizend'
        else
          Result := NumToStr(Num div 1000) + 'duizend ' +
                    NumToStr(Num mod 1000)
      else
        if Num >= 100 then
          if (Num mod 100) = 0 then
            Result := NumToStr(Num div 100) + 'honderd'
          else
            Result := NumToStr(Num div 100) + 'honderd' +
                      NumToStr(Num mod 100)
        else
          case (Num div 10) of
   5,6,7,9: if (Num mod 10) = 0 then
              Result := NumToStr(Num div 10) + 'tig'
            else
              Result := NumToStr(Num mod 10) + 'en' +
                        NumToStr(Num div 10) + 'tig';
         8: if Num = 80 then
              Result := 'tachtig'
            else
              Result := NumToStr(Num mod 10) + 'entachtig';
         4: if Num = 40 then
              Result := 'veertig'
            else
              Result := NumToStr(Num mod 10) + 'enveertig';
         3: if Num = 30 then
              Result := 'dertig'
            else
              Result := NumToStr(Num mod 10) + 'endertig';
         2: if Num = 20 then
              Result := 'twintig'
            else
              Result := NumToStr(Num mod 10) + 'entwintig';
       0,1: case Num of
              0: Result := 'nul';
              1: Result := 'een';
              2: Result := 'twee';
              3: Result := 'drie';
              4: Result := 'vier';
              5: Result := 'vijf';
              6: Result := 'zes';
              7: Result := 'zeven';
              8: Result := 'acht';
              9: Result := 'negen';
             10: Result := 'tien';
             11: Result := 'elf';
             12: Result := 'twaalf';
             13: Result := 'dertien';
             14: Result := 'veertien';
             15: Result := 'vijftien';
             16: Result := 'zestien';
             17: Result := 'zeventien';
             18: Result := 'achttien';
             19: Result := 'negentien'
            end
          end
  end {NumToStr};

  initialization
    InvRegistry.RegisterInvokableClass(TDutch);
  end.
De implementatie staat in unit DutchImp, terwijl de interface in de unit Dutch staat.

Deployment
Aangezien ik de web service toepassing in dit artikel als een CGI web server toepassing ben begonnen, kan ik het resultaat deployen op mijn Win2000-hosted website te http://www.eBob42.com. De locatie van de web service is http://www.eBob42.com/cgi-bin/NumberToWordsInDutch.exe, en ook zonder /WDSL argumenten krijg je zinvolle informatie: de textuele beschrijving van de web service zelf en het IDutch interface. Voor de WSDL zelf moet je http://www.eBob42.com/cgi-bin/NumberToWordsInDutch.exe/wsdl/IDutch gebruiken.

Using NumberToWordsInDutch
Vorige maand gebruikte ik de Web Services Importer om van de (dynamisch opgehaalde) WSDL een import unit te maken. Echter, wie goed heeft opgelet zal al gezien hebben dat de resulterende interface definitie erg lijkt op de interface definitie die we deze keer zelf hebben gemaakt voor IDutch. En da's niet zo gek, want de taak van de Web Services Importer is inderdaad om het interface volledig te reproduceren (op basis van de WSDL), en als alles goed is komt dat inderdaad op dezelfde unit neer.
Dat heeft een direkt gevolg voor web services die in Delphi 6 zelf zijn geschreven. Als je die namelijk weer in Delphi 6 wilt gebruiken, dan hoef je geen nieuwe import unit te laten genereren (met de Web Services Importer), maar kun je gewoon de originele import unit zelf gebruiken. En da's dan weer een van de redenen om inderdaad zoveel mogelijk de interface definitie in een andere unit te plaatsen dan de interface implementatie (bij ons zijn dat Dutch.pas en DutchImp.pas).
Als een korte demonstratie van het gebruik van de NumberToWordsInDutch web service (en het IDutch interface), moet je een nieuw Delphi 6 project starten, een HTTPRIO component op het main form zetten en de WSDLLocation property naar http://www.eBob42.com/cgi-bin/NumberToWordsInDutch.exe/wsdl/IDutch laten wijzen (ook al kunnen we de lokale import unit gebruiken, tijdens run-time moet de client wel degelijk de daadwerkelijke remote web service kunnen vinden natuurlijk). De Service property van de HTTPRIO component moet IDutchservice worden, en de Port property moet IDutchPort worden. Hierna kun je bijvoorbeeld meteen de in FormCreate de volgende code schrijven:

  procedure TForm1.FormCreate(Sender: TObject);
  begin
    Caption := (HTTPRIO1 AS IDutch).NumToStr(42)
  end;
Dit verandert de caption van het main form in "tweeenveertig". Niet echt een heel zinvolle demo, maar het gaat om het idee, nietwaar? Wie meer informatie wil hebben over het gebruik van Web Services in Delphi 6 moet het artikel van vorige maand nog maar eens lezen.

Dit artikel is een vrije vertaling van mijn artikel over Delphi 6 Web Services geschreven voor de TechRepublic website. Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via . Wie meer wil weten over XML, SOAP en het gebruik (en de bouw) van Web Services moet zeker eens overwegen om zich in te schrijven voor een van mijn Delphi Clinics.


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