Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
Kylix 2 Enterprise Web Services
Kylix 2 has been shipping a few days as I write this article.
Yet, it wasn't hard to start using the new features, since they are so much alike the ones we've been using in Delphi 6 in the past months.
In this article, I want to move from XML coverage in Delphi 6 (what I've done in the past months) to SOAP support - in both Delphi 6 and Kylix 2.
This time, I want to start with the steps to build a Web Service in Kylix 2 using SOAP.
Next time, I continue the story and turn it into a cross-platform architecture by writing both a Kylix 2 and a Delphi 6 Client for my Kylix 2 Web Service.
This and more next time, but first let's build a Kylix 2 Web Service.
Web Services
The topic of web services is hot.
In fact, there's hardly a place where it isn't mentioned these days.
Personally, I see web services are a natural evolution of more "regular" web server applications.
A web service can be seen as a web server application without the user interface - an engine.
Just like we've been able to split stand-alone applications into clients and servers, and latter adding middle-ware, we can now distribute the server-side engines as "web services", using HTTP, FTP or even e-mail as communication protocol.
If we look inside a web service, we see a SOAP object (some functional "interface" accessible through the Simple Object Access Protocol), which functional description - the available methods and their arguments - is made available using WSDL - the Web Service Description Language (a language based on XML).
Kylix 2 Enterprise
Note that you'll need Kylix 2 Enterprise edition to be able to build Web Services.
If you want to try it without buying Kylix 2 Enterprise, yet, then you can always download the 60-day trial edition from the Borland website.
It'll still work when we get back here next month, so why wait?
K2 SOAP Server Application
To start a Soap Server application in Kylix 2, you have to start a new application using File | New, then go to the WebServices tab of the Object Repository.
There, you'll see three icons with descriptions that may not be complete (at least, I can't see the complete text in my Red Hat 6.0 installation that I still use):
Just to explain, the first icon stands for the "SOAP Server Application", the second is the "SOAP Server Data Module" and the third is the "Web Service Importer". Today, we'll only use the first icon, next time we'll also use the third icon (and the second icon is described on my website at http://www.drbob42.com/soap42.htm already).
Once you click on OK, you get a new Web Module, but with three "web service" components already placed: a HTTPSoapDispatcher (to dispatch the incoming SOAP requests), a HTTPSoapPascalInvoker (to call the correct Pascal method based on a Soap request) and finally a WSDLHTMLPublish component (which will generate the WSDL to describe the functionality of our web service to the outside world).
We don't have to do anything with these components: they are ready to be used. We only have to focus on the task at hand: build some kind of "engine", and define it's "interface" to it can be published and used. As engine example, I've decided to use something with a little bit of relevance for some European countries: the Euro conversion (as of 1/1/2002 a number of European countries are converting to the Euro as currency). This kind of "non-visual conversion" is a typical and well-suited example to implement using a web service.
Euro Conversion
There are 12 countries that participate in the Euro.
Which also mean 12 different conversion rates.
For each country, we need the 3 letter acronym for the national currency as well as the conversion rate to Euros.
Then, we can make 24 functions that can convert to and from each individual currency.
These 24 methods will define the interface of our "Euro" web service.
However, since other countries could participate in the future as well, it may be more practical to have just two general "Conversion" methods: FromEuro and ToEuro, each taking a shortstring (the name of the currency) and currency value as argument, returning a converted currency value.
This way, it would be very easy to add new countries (just make sure we can understand the new currency strings, instead of adding new methods and thereby changing the interface).
Euro Interface
Let's start with the interface first.
If we want to export the interface using a web service - in other words make it available as an "invokable interface" from the outside world, then we have to derive it from IInvokable.
We also need to register our interface in the InterfaceRegistry, as implemented in unit Euro.pas, which can be seen in Listing 1:
unit Euro; interface uses InvokeRegistry; type IEuro = interface(IInvokable) function FromEuro(const Currency: WideString; Amount: Double): Double; stdcall; function ToEuro(const Currency: WideString; Amount: Double): Double; stdcall; end; implementation initialization InvRegistry.RegisterInterface(TypeInfo(IEuro)); end.Note that the two methods of our IEuro interface are declared using the stdcall calling convention. This is required, so other environments can actually call these methods.
Euro Implementation
The list of currency acronyms and conversion rates (1 EURO = X) can be obtained from a number of sources - and please don't blame me if I mistyped a digit here.
I have defined a type TEuroCurrency that consist of two fields: a Currency string (only three characters are used) and a Conversion field of type Double).
Now that we have the conversion rates, it's time to write the two conversion functions FromEuro and ToEuro, which are part of the TEuro class.
TEuro must be derived from TInvokableClass, implementing the IEuro interface.
Both methods from the IEuro interface have two arguments: a literal string containing the currency as well as a double containing the current amount (either in the local currency when calling ToEuro, or in Euros when calling the FromEuro function).
They return a Double value again.
Using a pre-initialised list (array) of Currencies ensures that the of "EuroCountries" can easily be extended in a future version of this web service.
The complete implementation of TEuro is available in unit EuroImpl.pas, which can be seen in Listing 2:
unit EuroImpl; interface uses Euro, InvokeRegistry; const EuroCountries = 12; type TEuroCurrency = record Currency: String[3]; Conversion: Double; end; const Currencies: Array[1..EuroCountries] of TEuroCurrency = ( (Currency: 'FIM'; Conversion: 5.94573), (Currency: 'ITL'; Conversion: 1936.27), (Currency: 'NLG'; Conversion: 2.20371), (Currency: 'ESP'; Conversion: 166.386), (Currency: 'BEF'; Conversion: 40.3399), (Currency: 'ATS'; Conversion: 13.7603), (Currency: 'LUF'; Conversion: 40.3399), (Currency: 'DEM'; Conversion: 1.95583), (Currency: 'FRF'; Conversion: 6.55957), (Currency: 'IEP'; Conversion: 0.787564), (Currency: 'PTE'; Conversion: 200.482), (Currency: 'GRO'; Conversion: 340.750)); type TEuro = class(TInvokableClass, IEuro) function FromEuro(const Currency: WideString; Amount: Double): Double; stdcall; function ToEuro(const Currency: WideString; Amount: Double): Double; stdcall; end; implementation { TEuro } function TEuro.FromEuro(const Currency: WideString; Amount: Double): Double; var curr: Integer; begin curr := 1; while (curr <= EuroCountries) and (Currencies[curr].Currency <> Currency) do Inc(curr); if curr <= EuroCountries then Result := Amount * Currencies[curr].Conversion else Result := 0 { invalid country code } end; function TEuro.ToEuro(const Currency: WideString; Amount: Double): Double; var curr: Integer; begin curr := 1; while (curr <= EuroCountries) and (Currencies[curr].Currency <> Currency) do Inc(curr); if curr <= EuroCountries then Result := Amount / Currencies[curr].Conversion else Result := 0 { invalid country code } end; initialization InvRegistry.RegisterInvokableClass(TEuro); end.Note that apart from registering the IEuro interface (as is done in unit Euro.pas), we also need to register the implementation class TEuro itself.
Euro Deployment
Now that we've defined the IEuro interface and implemented the TEuro class, it's time to deploy the Euro42 web service.
Since we wrote it as CGI stand-alone executable, we only have to copy it to the httpd/cgi-bin directory of a Linux machine.
There are no further deployment needs (apart from the fact that legally you are not allowed to deploy applications made with the 60-day trial edition of Kylix 2, in case you don't use the "full" Kylix 2 Enterprise version for your web service).
In order to test the web server, you can start a browser and type the full URL to the web server application, appending it with the /wsdl string in order to request the Web Service Description Language index.
This will give an overview of all SOAP interfaces (and implementations) available within the web service.
As you can see, there is a WSDL for IWSDLPublish (the actual publisher of our interfaces) and there is a WSDL for IEuro - the web service we're interested in. The former will be available in all web services that you build with Kylix 2 (or Delphi 6) in this way.