Shakespeare on the Web

By: Nick Hodges

Abstract: This article shows how to produce a basic web service application that spews Shakespearean insults.

Shakespearean Insult Web Service Many of you remember my article on Shakespearean Insults. Some of you even emailed me improvements to the code. I appreciate that. The code was not at the genius level, but it was pretty fun. Well, good old Will may have lived centuries ago, but his Insult generator lives in the twenty-first century, so it is time for it to become a Web Service. Lucky for me, Delphi 6 is here, and moving the generator to the world of Web Services couldn't be easier.

Web Services are a very hot topic, and certainly the phrase is currently pegging the buzzword meter. Web Services are really nothing than a platform neutral means of making remote procedure calls across any network. One computer asks for some information, and the other provides it. The current way to make this happen is with SOAP, or Simple Object Access protocol. SOAP is an XML-based protocol that is really lightweight, and thus perfect for inter-platform and internet communication. The World Wide Web Consortium has the specification for it here. The specification is cool and all, and you'd do well to learn it, but naturally Delphi 6 takes all that complicated stuff and makes it easy to build a web service. Pathetically easy, actually. So easy, that we Delphi developers have no excuse not to immediately inundate sites like http://www.xmethods.com with hundreds of web services in the next few weeks. I've done my part there, so the rest is up to you all. After a read of this article, you won't have any excuse not to get your name up in lights.

Delphi 6 supports web services by building object wrappers around the SOAP protocol, and by managing the use of Web Services Description Language (WSDL) based documents. WSDL is the language used to describe a web service, and if you have access to the WSDL page for a service, you can easily use Delphi 6 to build a client to access it. Anyone else that understands WSDL can use the service as well. Delphi 6 uses its typical and powerful approach to SOAP and WSDL. If you know what you are doing, you can get down into the bowels of the protocols and configure it as you wish, and if you know almost nothing about SOAP and WSDL, you can still use Delphi's wizards and classes to easy use its power. Cool, huh?

To build the web service, here's what I did. First, I went to File|New|Other... and then selected the Web Services. I saw this as a result --

I selected the Soap Server Application item, and then I saw this --

I selected the CGI option and pressed the Ok Button. I saved the unit as ShakesMain. Then I saw this, a web module with three components on it --

The three components are as follows:

  • THTTPSoapDispatcher -- This class manages the calls to the CGI executable, dispatching the actions as in a WebSnap application. It provides customized support for SOAP applications by providing standard actions that provide basic information about the web service
  • THTTPSOAPPascalInvoker -- This is the workhorse component of the web service. It is the one that takes the SOAP request and converts it over to use your Object Pascal implementation of that request.
  • TWSDLHTMLPublish-- This component publishes the WSDL for the web service, so that others can access it easily and use the web service.
There is nothing really remarkable about the resulting code for the unit --

unit ShakesMain;

interface

uses
  SysUtils, Classes, HTTPApp, WSDLPub, SOAPPasInv, SOAPHTTPPasInv,
  SoapHTTPDisp, WebBrokerSoap;

type
  TWebModule1 = class(TWebModule)
    HTTPSoapDispatcher1: THTTPSoapDispatcher;
    HTTPSoapPascalInvoker1: THTTPSoapPascalInvoker;
    WSDLHTMLPublish1: TWSDLHTMLPublish;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WebModule1: TWebModule1;

implementation

{$R *.DFM}

end.
This application now will support any number of web services that you care to add to it. And to add a web service, you merely have to add in interface and an implementation of an interface. But first, a discussion about a new feature for Delphi 6 interfaces -- RTTI. Delphi 6 now will provide Run-time Type Information for Interfaces as well as for classes. What this means for web service builders is that you can, given a name for an interface method, get the implementation for that interface method. Previously, you could only do this for classes. What that means is that Delphi 6 can now call a method that is defined by an interface, rather than defined by a class. Since SOAP requests are nothing more than text, Delphi 6 can now take a name of a interface method, and invoke its implementation. That may not sound like a big deal, but it is the key thing to make this whole web services thing work.

How that works in Delphi 6 specifically is that there is a new interface declaration called IInvokable declared like this

{$M+}
  IInvokable = interface(IInterface)
  end;
{$M-}

Notice that the declaration is surrounded by the {$M+} compiler pragma, which indicates runtime information. This means that this interface and all its descendants can use RTTI to make method calls through the interface to the implementation of that interface.

So, the next step is to take advantage of this new feature of Delphi 6 to build a web service. To do that, we need to declare a new interface that descends from IInvokable and that declares how to get a string with a Shakespearean insult in it. I declared the following unit --

unit ShakesIntf;

interface

type
  IShakespeare = interface(IInvokable)
    ['{945C9B49-0116-42E0-97BC-ED3D75E02D5A}']
    function GetShakespeareInsult: string; stdcall;
  end;

implementation

uses InvokeRegistry;

initialization
  InvRegistry.RegisterInterface(TypeInfo(IShakespeare));

end.
The only thing unusual here is the call to InvRegistry.RegisterInterface. This method is actually needed by Delphi clients to make it easier to find invokable interfaces without having to look them up in the WSDL document. It takes the type information for the interface being registered so that a Delphi application can access that type information to use the interface. (We will be using this interface on the client side to build a Delphi client to access this server below.)

If you have registered Delphi 6, you will be able to download some extra goodies for it. One of those little goodies will be a wizard that will build the interface and the implementation framework for your web service. It looks like this --

The box is filled out as I used it to make the interface for this application. Basically, you just supply a name for the interface, and the unit to hold the interface, and the class to descend from, and it does the rest. The right side of the dialog shows you the resulting names of everything, so you can verify it all before building it. It doesn't do much, but it will save a lot of typing.

Of course, an interface isn't any good without an implementation, so here's my very straightforward implementation of the simple class.
unit ShakesImpl;

interface

uses ShakesIntf, ShakesInsult, InvokeRegistry;

type

  TShakespeareInsult = class({TInterfacedObject,}TInvokableClass, IShakespeare)
  public
    function GetShakespeareInsult: string; stdcall;
  end;

implementation

function TShakespeareInsult.GetShakespeareInsult: string;
begin
  Result := RandomShakespeareanInsult; // From the ShakesInsult unit
end;

procedure ShakespeareFactory(out obj: TObject);
begin
  obj := TShakespeareInsult.Create;
end;

initialization
  Randomize; // Needed to ensure a different insult gets generated for each CGI instance
  InvRegistry.RegisterInvokableClass(TShakespeareInsult{, ShakespeareFactory});

end.
Now this unit has a little more meat on the bones. The first thing to notice is that the class declaration descends from a class called TInvokableClass, and that it implements, naturally, the IShakespeare interface. TInvokableClass looks like this --
  TInvokableClass = class(TInterfacedObject)
  public
    constructor Create; virtual;
  end;

TInvokableClass's constructor doesn't do anything except call inherited, but note that it is declared as virtual. That means that the Invokation Registry (see the InvokeRegistry unit) knows how to and can create instances of TInvokableClass descendants in the same manner that Delphi's streaming mechanism can create classes based only on the class name. Otherwise, TInvokableClass acts just like TInterfacedObject in that it is reference counted and knows how to do its own lifetime management.

In the declaration of the implementation class above, you may have noticed that some of the code was commented out. As mentioned above, web service implementations normally descend from TInvokableClass. However, there may be times when you don't want to do that and you may need to use another class. You can, naturally, do that if you want, but you will have a little extra work to do. In this case, you need to create a Factory procedure for your class that takes a single out parameter of type TObject. Then, you need to call the overloaded version of InvRegistry.RegisterInvokableClass, passing in the procedure as a parameter. The code above can do this, and you can change the comments around to do this if you are feeling a bit adventurous. It will all work fine if you descend from TInterfacedObject instead.

The next thing to notice is that there is an initialization section that again makes use of the InvRegistry singleton class. The call to RegisterInvokableClass lets your web service know about this invokable class so that it can subsequently be matched up with the call that comes from the SOAP request. This will be a string holding the class name, and just as the Delphi streaming classes can create an object based only on a string holding the class name, the web service will take the information from the SOAP request, look up the class, create an instance of it with the virtual constructor, and call the method. This is where the power of RTTI for interfaces comes in. Since the service will reveal only the interface, it uses that RTTI information, matches it up with the Invocation Registry, and executes the call. Pretty slick, indeed.

Well, that's it. That's pretty much all there is to it. Add these two units to the project, save the project as ShakespeareWS.dpr, compile it, and you are all set. You're done. That's all. Nothing else to do. The service is there. Don't believe me? Can't believe that it was that simple? Well, lets deploy the thing, take a look at it, and then build a client to prove that it works. (By the way, building the client to access this or any other properly constructed web service is even easier. I am telling you, Delphi 6 will have those guys at MS hopping mad. How does Borland always seem to be a day earlier a dollar ahead of the guys that write the specifications?)

To deploy the application, all you need to do is place it in a virtual directory of your web server and make sure that directory has execute privileges. For instance, my web service can be found here --

http://www.nickhodges.com/bin/ShakespeareWS.exe/wsdl/IShakespeare

Click on that link, and you will get the WSDL definition of the Shakespearean Insult Generator web service, available for the whole world to access. Pretty cool, huh? Especially since I slaved for hours carefully crafting that WSDL, getting it just right, making sure every tag was properly declared, and carefully checking it for typos. NOT! That page was created for me by Delphi and the WSDLHTMLPublish component. Automatically. Just like that. That component and the THTTPSoapDispatcher automatically created the file and added the action and link to my application. I did no extra work other than that described above to make the WSDL for the web service appear. The folks using MS's SOAP toolkit are going to wet their pants when they see how easy this all is. Man, this is so cool, I can hardly stand it. I've got to put on a sweater, it's so cool.

Normally, to advertise your web service, you create a page that describes it, and I've done that here --

http://www.nickhodges.com/Delphi/Web_Services/web_services.html

I've also registered it at http://www.xmethods.com, which is a repository for SOAP servers on the Internet.

That's all you really need to do to deploy it. Once it your WSDL page is viewable on the Internet, anyone can come along and call it. So, if your enemies start calling you these strange, fairly unintelligible names that sound vaguely like Middle English, you can blame me.

Next up is building the client. I know there are some skeptics out there that don't believe that this think will actually work, because we hardly did anything. Well, you are sadly mistaken. This thing works like a charm.

First, pretend like you never wrote the web service that you have above. You just ran across this thing on xMethods, and you want to give it a try. You don't have ShakesIntf on your computer, so you don't have any idea how the web service is declared or implemented. Next, create a new project with a single blank form. Drop a label and a button on it, and arrange it all pretty so that you can press the button and then put some text in the label. Give the button a smartasss caption like "Insult me!". Next, navigate to your web server, and point your browser at the WSDL page for your web service. Copy the URL to the clipboard. In my case, it is http://www.nickhodges.com/bin/ShakespeareWS.exe/wsdl/IShakespeare. Yours will obviously have a different domain name. If your web server is on your development machine, your URL will likely be something like http://localhost/scripts/ShakespeareWS.exe/wsdl/IShakespeare

Then, go to File|New|Other, and click on the WebServices tab. You'll see this --

Select the "Web Services Importer" icon, and press OK. You'll get a wizard that looks like this --

In the edit box, put the URL for your WSDL page that you found just a minute ago. Then press Ok. You'll then get a new unit that looks amazingly familiar. It is an interface declaration that should match exactly the one you built for your server. You can thus use this interface to call the web service. We'll get that in a minute.

This just keeps getting cooler and cooler, huh?

Save this unit as ShakesIntfGen.pas and add its name to the uses clause of the implementation section in your project's main unit.

Then, go to the Web Services page on your component palette and drop a THTTPRIO component on your form. Save the project as ShakesClient.dpr, and the form as ShakesClientMain.pas.

Once again, copy to the clipboard the URL for your WSDL document. Paste this value into the HTTPRIO.WSDLLocation property. Next, set the Service property to IShakespeareservice and the Port property to IShakespearePort. (These values are automatically parsed out of the WSDL and made available to you in the Object Inspector. Just use the drop down comboboxes to set them.) Then, double click the button, and make it's OnClick event handler look like this --

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption := (HTTPRIO1 as IShakespeare).GetShakespeareInsult;
end;
Compile and run the project. Push the button. That is it. If you fainted, get up, breathe normally, and quit your job at Microsoft.

What is happening here is what you'd expect to happen if you have been keeping up. The THTTPRIO component knows how to read a WSDL and make it available to you via the interface that you registered in the import unit. It uses that interface to construct a SOAP call to the web service, makes the call, parses the results and provides the insult as a good old Delphi string. On the server side, your server grabbed the SOAP request, parsed it for the call, invoked a copy of your implementation object by using the interface RTTI, called the method, got the results, formed a SOAP response, and sent the SOAP response back to the client. All that is a rather complicated process using complicated communications protocols, but you did it by writing almost no code, and by not having to know much at all about how the protocols worked. Heck, even Visual Basic programmers should be able to figure out how to write web services with Delphi.

This is a pretty simple example to show you how things work. However, you can build web service servers that provide data using any normal scalar types. You can use SOAP as the protocol to communicate with a DataSnap server. And let me be clear, too -- your servers can be accessed by non-Delphi clients, and your Delphi clients can access servers built with tools other than Delphi.

So, there you have it. I told you it was pathetically easy. But you've come to expect that kind of thing from Delphi, right?

(By the way, the code for this article can be downloaded at http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=16217)


Nick Hodges is Grand Poobah of Hardthink, Inc., a software development company specializing in Delphi development. He is also a member of TeamB and the 2001 Borland Conference Advisory Board. He can be reached at nick@hardthink.com, and at http://www.nickhodges.com

Server Response from: ETNASC02