2011. június 24., péntek

Using SOAP with Delphi


Problem/Question/Abstract:

Using SOAP with Delphi

Answer:

Introduction

The growth of the Internet has recently opened a completely new world of possibilities that were extremely difficult if not impossible to achieve ten years ago. The access to information has apparently become easier and the ability to get your data anytime and from anywhere in the world is considered pretty much a normal thing these days. With an Internet connection and a browser you are immediately able to check what's the latest and greatest home computer, compare it with others, buy it and monitor its delivery until it gets to your door.

Unfortunately, as it often happens, when it comes to us developers it is not that easy to build what makes a user's life so easy. Data exchange, collaboration and cooperation are actually some of the most complex areas developers have to deal with. The devil is in the detail they say… Well, that area is definitely full of details.

Component software is targeted to make data exchange, collaboration and cooperation easier. Technologies such as Corba and COM+ provide us with the backbone to make applications talk seamlessly to each other, regardless of the language used to build them or their location. This is possible because they define a standard way to describe the services and the clients that access them know what to ask for and how to ask for it.

When it comes to the Internet, the solution that was perfect for your LAN based application doesn't work anymore. Scalability and standards become a real issue since you cannot predict the number of clients that will access your system and, worst of all, you don't know what is accessing your system. With so many standards around a standard client is the last thing you should expect.

Not too long ago a new acronym begun to spread across the web: SOAP, the Simple Object Access Protocol. This new, XML based standard promises the ultimate solution to all our problems. It promises to deliver a universally supported standard and to do it in one of the most scalable ways. Many companies such as Borland, Microsoft and IBM are moving fast in order to make this happen. Borland's Delphi 6 and Kylix have SOAP support built in. Microsoft provides the SOAP SDK 1, is working on version 2 and the future .Net platform will offer even greater support for this technology. IBM on the other side is providing a Java-based implementation.

SOAP?

However, what is SOAP? Should you use it and if so, how can you use it today?

SOAP enables you to encode complex data such as objects or procedure call parameters into an xml string (called a "SOAP Packet"). Because SOAP uses XML, it is not dependent on any particular language or operating system. SOAP packets can be stored in a database, posted in an email or a message queue or transmitted via HTTP. The most common use for SOAP is likely to be remote procedure calls implemented with SOAP transmitted over HTTP

There's nothing really complex or unique about SOAP, except maybe its simplicity.

As of today there's very little out there for a Delphi developer. Your best chance is to use what Microsoft provides at http://msdn.microsoft.com/xml with the SOAP SDKs version 1, and the beta of version 2 (currently Release Candidate 0 ). In my sample code you will find a Delphi component that wraps it and exposes some additional events.

It's worth noticing also Dave Nottage's PureSOAP. This is a simple implementation that doesn't support complex objects but comes with full source code that may be of some interest. You can find it at http://www.puresoftwaretech.com/puresoap

Luckily for us, As soon as Delphi 6 will be released, we will have much more to play with.

A practical example

In order to demonstrate a possible way to use SOAP today, I developed an example that is downloadable directly from here. The example includes the TSOAPClient component that wraps the equivalent SOAPClient COM component included in the Microsoft SOAP SDK version 2 (Release Candidate 0).

Be aware that the WSDL file definition has changed from Beta 1 to RC0. I updated the msdelphi.com source files in order to work with the latest version (Release Candidate 0). These files will not work properly with Beta 1. The files in CodeCentral are still the old ones.

The example demonstrates how to get a stock quote from a web server using SOAP.

In a future article, I will demonstrate how a similar component can be developed using Delphi, with or without COM. It's worth noticing that SOAP does not require COM at all. The only reason for which I have chosen this approach is that creating a full-blown SOAP component would have been too much overhead for this introduction to SOAP.

The instructions on how to install the example are contained in the file Readme.txt

The example

The SOAP server is a standard MTS object has only one method that given a ticker symbol returns its value. The method is defined as:

function GetQuote(const Symbol: WideString): Double

The client is a simple form that allows the user to enter a ticker symbol and displays the value that is returned by the server.



This is the sequence of events that occurs after the user presses the “Get Quote" button:

The TSOAPClient asks the web server for an XML file that describes the interface of the SOAP server.

The web server returns a standard Web Services Description Language (WSDL) file.

The client is now ready to invoke any method on the server and prepares the a GetQuote message which then sends to the server

The web sever grabs the message and passes it to the SOAPServer COM object

The SOAPServer object reads the SOAP message and invokes the GetQuote method of our test COM object

After the execution of the COM call, the SOAPServer packages a response returning either the ticker quote or an error and sends it to the client

The client finally displays the result

Demystifying SOAP – Client side

From the client perspective, SOAP method invocation is generally done using a proxy that simulates the interface of the SOAP server on the client side.

When you press the “Get Quote" button in the client application the following code is executed:

procedure TMainForm.bGetQuoteClick(Sender: TObject);
var
  quote: currency;
begin
  // Retrieves the WSDL information only the first time…
  if SOAPClient.Connected or SOAPClient.Connect then
  begin
    // Invokes the GetQuote method
    quote := SOAPClient.Client.GetQuote(eSticker.Text);
    // Displays the result
    ShowMessage(eSticker.Text + ' is worth ' + FloatToStr(quote) + '$');
  end;
end;

What is happening here is that the client is asking the server to provide a description of the interface of the StockQuote service. In SOAP this is achieved by loading a Web Services Description Language (WSDL) XML file.

In the example, this is accomplished by setting the WSDLURI property and calling the method Connect. The WSDL file that describes the StockQuote service looks like this:

<?xml version='1.0' encoding='UTF-8' ?>
<definitions  name ='StockQuote'   targetNamespace = 'http://tempuri.org/wsdl/'
xmlns:wsdlns='http://tempuri.org/wsdl/'
xmlns:typens='http://tempuri.org/type'
xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
xmlns:xsd='http://www.w3.org/2001/XMLSchema'
xmlns:stk='http://schemas.microsoft.com/soap-toolkit/wsdl-extension'
xmlns='http://schemas.xmlsoap.org/wsdl/'>
  <types>
    <schema targetNamespace='http://tempuri.org/type'
      xmlns='http://www.w3.org/2001/XMLSchema'
      xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'
      xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
      elementFormDefault='qualified'>
    </schema>
  </types>
  <message name='StockQuote.GetQuote'>
    <part name='Symbol' type='xsd:string'/>
  </message>
  <message name='StockQuote.GetQuoteResponse'>
    <part name='Result' type='xsd:double'/>
  </message>
[..]
  <service name='StockQuote' >
    <port name='StockQuoteSoapPort' binding='wsdlns:StockQuoteSoapBinding' >
      <soap:address location='http://localhost/SOAP/StockQuote.ASP' />
    </port>
  </service>
[..]

As you can see, it says that the interface of the StockQuote SOAP service exposes one method called GetQuote. This method has string parameter called &#8220;Symbol" and returns a floating point.

Towards the end of the file, you will find another important information: the <service> tag that contains information on the destination of the SOAP messages. The URL specified in this section will be used by the client as HTTP destination of the SOAP message.

After the client has loaded and processed this file, it becomes aware of the interface of the service and knows what it can ask for and how to ask for it. The next step is to invoke the method GetQuote using the Client property of the SOAPClient.

If you are not familiar with Variant method calls and late binding, I recommend reading Binh Ly's article at http://www.techvanguards.com/com/concepts/automation.htm

After you call GetQuote, the proxy converts the method name and the parameters you invoked into a standard SOAP message and delivers it through HTTP to the destination. It is worth saying that you can implement the same behavior by building an object that implements IDispatch. You would just need to provide your own implementation of the methods GetIDsOfNames and Invoke. Another possible approach would be creating a regular Delphi class that would have a method such as:

function SOAPInvoke(aMethodName: string; someParameters: array of OleVariant):
  OleVariant;

It is also possible to send a SOAP message using other protocols than HTTP. Although it is the most commonly used, nothing stops you from using regular sockets or even an e-mail.

The server

On the server side, a listener is constantly waiting to receive SOAP requests.In this example, since we are using HTTP, the web server is the listener and the target of the SOAP messages is the ASP file StockQuote.asp. Remember how this was specified in the WSDL file the client initially received.

The SOAP Message that is received in this particular case is:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <m:GetQuote xmlns:m="http://tempuri.org/message/">
      <Symbol>BORL</Symbol>
    </m:GetQuote>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

As you can see, the message not only indicates the name of the method that has to be invoked in the node, but also specifies the names of the parameters that the method needs.

This instead, is part of the ASP file StockQuote.asp that illustrates what's happening on the server side:

<%@ LANGUAGE=VBScript %>
<% Response.ContentType = "text/xml"%>
<%
  [..]
  Set SoapServer = Server.CreateObject("MSSOAP.SoapServer")
  SoapServer.Init WSDLFilePath, WSMLFilePath
  SoapServer.SoapInvoke Request, Response, ""
  [..]
%>

As you can see, the SOAPServer COM object is created and the SOAP message is delivered to it in the last line by passing the Request object to the SOAPServer.SoapInvoke method. In this case, since we are using the Microsoft SDK, we can only invoke methods of COM objects.

Nothing would stop us from creating a similar component that would invoke methods of a Corba object or anything else you can imagine (an old COBOL application, a standard executable, a Java class, etc). The SOAP stub on the server will be specific to the platform you chose to adopt in-house. Microsoft obviously automated the translation of SOAP messages into COM calls. Other companies are currently doing the same for Corba and Java objects. This is the key behind the SOAP idea: you are completely free to use any technology you want to develop your in-house application . Whenever you need to expose some of these services to the outside world, you just put the appropriate SOAP translator on top of it.

The following diagram illustrates this:



Now, the last part of the puzzle is the response the server sends back to the client. Successful or failed responses must follow a standard too.

This is how a successful result would look in our previous example:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <m:GetQuoteResponse xmlns:m="http://tempuri.org/message/">
      <Result>100</Result>
    </m:GetQuoteResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

In the event of any error instead (such an exception in the COM object) the standard format of a SOAP error message would look like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
        <faultcode>SOAP-ENV:Server</faultcode>
<faultstring>WSDLOperation: Executing method GetQuote failed</faultstring>
<faultactor>http://tempuri.org/action/StockQuote.GetQuote</faultactor>
<detail>
[..]
<mserror:description>A symbol must be specified</mserror:description>
[..]
        </detail>
     </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The elements faultcode, faultstring and detail are accessible through the TSOAPClient. They are the standard way for SOAP to signal an error. The detail node has been extended in this case to provide additional error information. The subnodes msXXXX provide information that is usually available when trapping COM exceptions. You are free to put as much information as you need to in the detail tag and still be compliant to the SOAP standard.

You can find a sample of each of these files (the request, the response and the error) in the Web directory included in the sample code.

Pros and cons

As any technology, SOAP comes with its sets of pros and cons.

Simplicity and the fact that is becoming an industry accepted standard are probably the most important two pros for SOAP but another critical element plays a major role when developing internet application: scalability. SOAP is commonly used on top of HTTP although almost any other transport mechanisms can be used. When it's used like this and the stateless HTTP request/response model is maintained, SOAP provides a higher scalability than any other protocol (COM's DCE-RPC, Corba's IIOP or Java's JRMP). There are multiple reasons behind this statement but the most important is the fact that HTTP is stateless in nature.

You can read more about this in the book "Understanding SOAP" mentioned at the end of this article. I'd also like to mention that using SOAP in a browser-based application could lead to unprecedented results in terms of ease of coding and functionality. For instance, instead of building URL strings such as http://myserver/ GetQuote?Symbol=BORL, more natural and object oriented calls such as StockServer.GetQuote('BORL') can now be easily performed.

On the server side the result is similar: the need to access the Form or QueryString properties of the Request object becomes superfluous and you can let the SOAP listeners do the job for you. You only need to code your COM/Corba objects and the activation is taken care of by SOAP .

Where SOAP falls short today is in security and data compression. XML is a textual representation of information and if the network you are running on is not secure, packets are extremely easy to sniff and then read with an application as simple as Notepad.

XML and the SOAP convention for packaging messages add a lot of extra overhead.

The example above demonstrated how costly sending a simple floating point number became (357 bytes). This is obviously an extreme example and sending such a small packet wouldn't really affect performances that much.

Conclusion

SOAP won't replace technologies like COM or Corba for in-house development for a long time, if ever. These technologies and the tools built on top of them deliver wider functionality than what SOAP offers today. Application servers such as Borland AppServer or Microsoft Transaction Server allow object pooling, just in time activation and much more. SOAP is mostly meant as an Internet lingua franca . Its stateless nature perfectly fits the Internet. LAN based application usually don't suffer of bandwidth limits, reliable communication or other problems as much as a wide area connection does.

Its simplicity and the fact that is quickly becoming a standard are key factors.

SOAP is the perfect candidate for those areas in which a system needs to exchange data or to use services provided by third parties. SOAP-enabled web services will lead to a more inter operable and collaborative Internet, which in turn may make development easier, less bug-prone and ultimately standardized.

Resources

If you want to know more about SOAP, the following resources may be very helpful:

The SOAP specification (version 1.1) is located at http://www.w3.org/TR/2000/NOTE-SOAP-20000508/

The Microsoft SOAP SDKs and other interesting articles can be found at http://www.msdn.microsoft.com/xml/default.asp

The Java based IBM Web Services Toolkit runtime environment can be found at http://www.alphaworks.ibm.com/tech/webs ervicestoolkit

The book &#8220;Understanding SOAP" written by Scrinber and Stiver and published by Sams is a great and detailed source of information on this topic.

Special thanks

I want to express my most sincere gratitude to John Beyer, Renzo Barduagni, Dave Nottage and Mark Chambers for helping me in reviewing this article and in providing excellent feedback.

Nincsenek megjegyzések:

Megjegyzés küldése