In this article, I give a brief picture that highlights exactly what interoperability in Web services means. To do so, I am going to pull an example out of O'Reilly's recently published Programming Web Services with SOAP, which I coauthored. The example is the ubiquitous Hello World application evolved into a SOAP-based Web service. (I bet you were expecting me to say it was a stock quote service, right? Not this time.) In the book, we implement Hello World in the Java, Perl, and .NET environments, and we demonstrate how to invoke each of them from the other two environments.
Saying Hello
To get started, let's write the three Hello World service implementations, starting with Perl.
Example 1: Hello World module in Perl (Hello.pm)
package Hello; sub sayHello { shift; my $self = "Hello " . shift; }
That was easy enough. Now let's write the code that will expose the Hello module as a Web service. To do so, we will be using SOAP::Lite for the Perl package written by Programming Web Services with SOAP coauthor Pavel Kulchenko. Using SOAP::Lite, we will create a Perl CGI script that can be deployed into just about any Web server environment.
Example 2: Hello World CGI Script (Hello.cgi)
use SOAP::Transport::HTTP; SOAP::Transport::HTTP::CGI -> dispatch_to('Hello') -> handle ;
Related Reading Programming Web Services with SOAP |
Copy the Hello.pm file to your Perl @inc folder and the Hello.cgi file to your Web servers' cgi-bin root directory and, assuming SOAP::Lite has been properly installed, the Hello World Web service has been deployed and is ready for action.
Now let's do the same thing in Java. Start by creating the Hello World Java class.
Example 3: Hello World in Java (Hello.java)
public class Hello { public String sayHello(String name) { return "Hello " + name; } }
Then, after compiling the class, we will deploy it as a Web service using Apache SOAP version 2.2. Apache currently has two SOAP implementations available, version 2.2 and the 3.0 alpha 3, also known as Axis. Because Axis is still highly unstable, I decided to stick with the older, stable version for this example.
The first step to deploying the Hello World Java Web service is to create the Apache SOAP deployment descriptor. The deployment descriptor is an XML file that tells Apache SOAP everything it needs to properly dispatch an incoming SOAP request to the Java class that is responsible for handling it, which in our case is the Hello.java
class in Example 3. Our deployment descriptor is shown in Example 4.
Example 4: Hello World Apache SOAP deployment descriptor (Hello.xml)
<dd:service xmlns:dd="http://xml.apache.org/xml-soap/deployment" id="urn:Hello"> <dd:provider type="java" scope="Application" methods="sayHello"> <dd:java class="Hello" static="false" /> </dd:provider> <dd:faultListener>org.apache.soap.server.DOMFaultListener</dd:faultListener> <dd:mappings /> </dd:service>
In Programming Web Services with SOAP, we go into significantly more detail about what each of the elements in the deployment descriptor mean. For now though, we're going to skip over it to keep moving with the discussion about interoperability. Make sure that Apache SOAP is properly installed in your Java Web server environment (I use Apache Tomcat 4), and ensure that your Web server is running and that the compiled Hello class in Example 3 is located somewhere in your Web servers classpath. Then, you can deploy the Java Web service using the following command line, where hostname is the name of your Web server, port is the TCP/IP port your Web server is listening on, and Hello.xml is the path to the deployment descriptor in Example 4. Prior to executing this statement, make sure that the soap.jar, mail.jar, and activation.jar libraries that ship with Apache SOAP are in your Java classpath.
java org.apache.soap.server.ServiceManagerClient / http://hostname:port/soap/servlet/rpcrouter deploy Hello.xml
The Service Manager Client should return a positive response indicating that the service has been deployed.
Two down, one to go. The next step is to implement the Hello World service within .NET. To complete this step, you have to have the .NET Framework SDK installed. If you don't, or if you're running a non-Windows operating system, please just follow along, you'll get the idea.
Example 5: Hello World in .NET (Hello.asmx)
<%@ WebService Language="C#" Class="Hello" %> using System.Web.Services; [WebService(Namespace="urn:Hello")] public class Hello { [ WebMethod ] public string sayHello(string name) { return "Hello " + name; } }
Once written, copy the Hello.asmx file into your Microsoft Internet Information Server's wwwroot directory and .NET will take care of the rest. The Hello World .NET Web service has been deployed.
Now What?
Now for the interesting part. At this point we have three distinct Web services implemented in three different programming languages that all do the exact same thing: They say "Hello." The test now is to see whether or not three identical services written in three different programming languages can be invoked in such a way that the only difference between calls is the location of the service. If so, then we've accomplished our goal of interoperability.
Let's start with Perl calling the Java implementation.
Example 6: Say Hello to Perl from Java (HelloClient.pl)use SOAP::Lite; my $proxy = shift; my $name = shift; print "\n\nCalling the SOAP Server to say hello\n\n"; print "The SOAP Server says: "; print SOAP::Lite -> uri('urn:Hello') -> proxy($proxy) -> sayHello($name) -> result . "\n\n";
To invoke this script, simply use the following command line, substituting in the appropriate values for your environment.
Perl HelloClient.pl http://hostname:port/soap/servlet/rpcrouter Your-Name
If all goes well, the output you should see is:
Calling the SOAP Server to say hello The SOAP Server says: Hello Your-Name
What do you think about the progress being made toward Web services interoperability? | |
|
Congratulations. You've just demonstrated the fundamental advantage of using Web services: interoperability. Prior to the advent of SOAP, calling a simple Java class from within Perl was a proverbial pain in the neck. Now let's see if it works for calling .NET. In theory, we should be able to use the exact same Perl script in order to call the .NET Hello World Web service since there is no difference in the types of parameters the Java and .NET implementations expect. (Both expect a single string value that contains your name.) So, let's try it. Invoke the same Perl command line, substituting the location of the Hello.asmx file for the Apache SOAP rpcrouter servlet.
Perl HelloClient.pl http://hostname/hello.asmx Your-Name
The result is not quite what you'd expect. It doesn't work. What happened to interoperability? Well, as with all brand new technologies, Web services still have a bit more wrinkles that need to be worked out. In this case, differences in the way the SOAP::Lite for Perl and .NET implement the SOAP version 1.1 protocol cause an incompatibility between the two. In order for a SOAP::Lite application to call a .NET service, you have to modify the client script a bit to account for the differences. We see the modified script in Example 7. I've highlighted the changes that need to be made.
Example 7: Modified HelloClient.pl for .NET
use SOAP::Lite; my $proxy = shift; my $name = shift; print "\n\nCalling the SOAP Server to say hello\n\n"; print "The SOAP Server says: "; print SOAP::Lite -> uri('urn:Hello') -> on_action(sub{sprintf '%s/%s', @_ }) -> proxy($proxy) -> sayHello(SOAP::Data->name(name => $name)->uri('urn:Hello')) -> result . "\n\n";
Basically, there are two problems that need to be fixed.
The first deals with the SOAP-defined SOAPAction HTTP header that must be defined for all SOAP messages sent over HTTP. By default, both SOAP::Lite and .NET automatically generate values for the SOAPAction header based on the operation that is being invoked. Unfortunately, SOAP::Lite and .NET don't automatically come up with the exact same value. Because .NET rejects SOAPAction headers that it does not recognize, we need to coerce SOAP::Lite to create a value consistent with .NET's view of the world. While it is less than obvious, that is what the on_action(sub{sprintf '%s%s', @_ })
line is doing in the Example 7 script.
The second problem is that .NET requires all parameters to be named and namespace-qualified. As a scripting language, parameters in Perl are neither named nor typed. To solve the problem, we have to make use of SOAP::Lite's named parameter functionality, setting both the name and XML namespace URI to values that .NET can easily recognize. We see that being done with the code SOAP::Data->name(name => $name)->uri('urn:Hello')
Now run the script again, and you'll get the result you expected.
Calling the SOAP Server to say hello The SOAP Server says: Hello Your-Name
But did making those changes to our Perl-client-script interfere with our already established Perl-to-Java interoperability? Fortunately, not in this case because the modifications were so minor that Apache SOAP is able to effectively ignore them. (Apache SOAP ignores the SOAPAction header and is capable of deserializing the parameter with or without explicit naming.) If you're not sure, just run the Perl client against the Java Web service and you'll see the expected result.
What Interoperability Means
The fundamental goal of interoperability in Web services is to blur the lines between the various development environments used to implement services so that developers using those services don't have to think about which programming language or operating system the services are hosted on. While there is a lot of progress yet to be made, this simple demonstration shows that, at least on a basic level, the tools are available to enable us to meet that goal.
To see more examples of interoperability between Web services, pick up a copy of Programming Web Services with SOAP and walk through the various samples. To participate in the growing effort to improve interoperability between the SOAP and Web services implementations, check out the SOAP Builders group.