- Web Service Overview
- Web Services for J2EE
- Using an RPC-style SOAP-Based Web Service
- Implementing an RPC-Style SOAP-Based Web Service
- Parameter Types and Type Mapping
- Summary
- Q&A
Using an RPC-style SOAP-Based Web Service
SOAP grew out of an effort to create an XML-based method invocation mechanism for distributed objects (primarily Microsoft's DCOM). As such, it is an ideal transport for method calls made over Web Services.
RPC-Oriented Web Services
Remote Procedure Calls (RPCs) made over Web-based protocols are essentially no different from those made over other protocols, such as IIOP, DCOM, or JRMP. The calls are usually synchronous (in other words, the client waits for the method to return before continuing). Zero or more parameters of varying types are passed into the call to provide information to process, and zero or more return values are generated to deliver the outputs of the remote method to the client. The remote method calls are delivered to some form of dispatcher at the remote server that determines which method should be called and arranges for the smooth flow of parameters and return values.
For RPC-style operation, SOAP implementations conform to the preceding description. The difference with SOAP (and other Web-based RPC mechanisms, such as XML-RPC) is that it uses standard, general-purpose transports, such as HTTP, together with a text-based method call description in XML. All of the parameters and return values are encoded in XML as part of the SOAP body, while information about the service and method to call are provided in the transport header and possibly the SOAP header. When sent over HTTP, the SOAP header and body are wrapped in another XML documentthe SOAP envelopeand this envelope forms the body of an HTTP POST request.
An HTTP-based SOAP message will be delivered to a SOAP router that takes the form of an HTTP servlet (for a Java implementation). The SOAP router will examine the HTTP and SOAP header information and decide how it should forward the message body. This will involve instantiating or calling a particular component or class that will receive the message. The SOAP router, or its helper classes, will also perform the conversion of the XML-based parameters into Java objects and primitives that can be passed as part of the service invocation. Figure 20.5 shows the operation of such a SOAP router. Note that the description of the Web Service is used by both the client and server to help determine the correct mapping between Java and XML for method calls and parameter types.
This is all good, but why go to this effort? Why not use an existing RPC mechanism, such as RMI or just use HTTP itself?
The justification for not using RMI or CORBA relates to commonality and security. There are at least three different distributed object protocols (CORBA, RMI, and DCOM), each of which has its adherents. The use of HTTP and XML provides a common protocol that is not tied to any vendor. Also, the protocols listed have great difficulty in penetrating most firewalls (not surprising, given their ability to invoke random functionality). However, HTTP (and SMTP) have general right of access through most firewalls, which makes it easier to integrate applications across organizational boundaries (after the security questions are sorted out).
Figure 20.5 A Java-based SOAP router.
CAUTION
From a developer's perspective, one of SOAP's greatest assets is its ability to penetrate firewalls. However, from an administrator's point of view, this presents the same types of problem as traditional RPC, namely the ability to target a random function call at an exposed server. Although the principle of SOAP is only a small step on from the invocation of server-side functionality such as CGI, great care should be taken to ensure adequate security when exposing Web Services. The overall security story for Web Services is still a work in progress.
Although raw HTTP is a good transport, it was created to exchange simple HTML messages. This does not provide the sophistication required for a distributed invocation environment. The use of a defined XML message format brings structure to this environment and allows for the interoperability of Web Service clients and servers from different vendorssomething that escaped CORBA until comparatively recently.
Now that you understand the architecture and motivation for RPC-style Web Services, you can install a Java-based Web Service environment and, through it, use and build your own Web Services.
Setting up Axis under Tomcat 4.0
The environment you will use for Web Service development in the first instance consists of the Tomcat servlet engine and the Axis Web Service toolkit, both from the Apache Software Foundation.
You can download Tomcat 4.0 from the Apache Software Foundation at http://jakarta.apache.org/tomcat/ or install it from the CD-ROM as follows:
Unzip the Tomcat 4.0 archive (jakarta-tomcat-4.0.1.zip) into an appropriate directory on your hard drive (an example from Windows would be C:\jakarta-tomcat-4.0.1).
In your personal or system environment, set the environment variable CATALINA_HOME to point to this directory.
You can download Axis from the Apache Software Foundation or install it from the CD-ROM as follows:
Unzip the Axis archive (xml-axis-alpha2-bin.zip) into an appropriate directory on your hard drive (an example from Windows would be C:\axis-1_0).
Copy the webapps\axis directory from the axis-1_0 distribution into Tomcat's webapps directory ({CATALINA_HOME}\webapps).
You need to install XML support for Axis from the Fall 01 JAX Pack (available from Sun or on the CD-ROM) as follows:
Unzip the JAX Pack archive (java_xml_pack-fall01.zip) into an appropriate directory on your hard drive (an example from Windows would be C:\java_xml_pack-fall01).
Copy crimson.jar and xalan.jar from the jaxp-1.1.3 directory into axis\WEB-INF\lib under Tomcat's webapps directory ({CATALINA_HOME}\webapps).
Tomcat and Axis are now installed with the appropriate XML support.
In the next section, you will create a client for a simple hello Web Service. First, you must install and test this simple Web Service as follows:
Install the class required for the HelloService by copying the webservices directory from the CD-ROM directory Day20\examples\HelloService to axis\WEB-INF\classes under Tomcat's webapps directory ({CATALINA_HOME}\webapps).
Start Tomcat by running the startup script/batch file in the {CATALINA_HOME}\bin directory.
To ensure that Tomcat and Axis are installed correctly, start a Web browser and point it at http://localhost:8080/axis/index.html. You should see a welcome screen from Axis.
Now deploy the hello server using the deployit batch file in the CD-ROM directory Day20\examples\HelloService.
Assuming that you had no errors, you have now deployed a simple Web Service called MyHelloService. In the long and distinguished tradition of curly-bracket-based languages, you will start with a variation on the Hello World! program.
Service Description Information
Your Web server now has a Web Service installed under it. The next step is to access that Web Service. However, before you can take advantage of the Web Service, you need the following information:
A definition of the service you are callingThis information corresponds to the traditional interface definition for an RPC or RMI server. The interface definition contains information about the methods available, the number and types of parameters, the type of any return values, and definitions of any complex types used as parameters.
The location of the serviceThis corresponds to the binding information used by RPC and RMI servers. This Web Service binding information lists the protocols over which you can call the available Web Service methods. For each supported protocol, there is also a URL indicating the location of a server that provides an implementation of that service for that protocol.
As you may have surmised by now, all of this information is provided by a WSDL description of the service.
Anatomy of a WSDL Document
The WSDL for MyHelloService is shown in Listing 20.1. It is worth taking a few moments to study this information because it provides a good insight into the way that Web Services work.
Listing 20.1 WSDL for the Hello Service (MyHelloService.wsdl)
1: <?xml version="1.0" encoding="UTF-8"?> 2: 3: <definitions 4: targetNamespace="http://localhost:8080/axis/services/MyHelloService" 5: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 6: xmlns:serviceNS="http://localhost:8080/axis/services/MyHelloService" 7: xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 8: xmlns="http://schemas.xmlsoap.org/wsdl/"> 9: 10: <message name="sayHelloToRequest"> 11: <part name="arg0" type="xsd:string"/> 12: </message> 13: 14: <message name="sayHelloToResponse"> 15: <part name="sayHelloToResult" type="xsd:string"/> 16: </message> 17: 18: <portType name="HelloServerPortType"> 19: <operation name="sayHelloTo"> 20: <input message="serviceNS:sayHelloToRequest"/> 21: <output message="serviceNS:sayHelloToResponse"/> 22: </operation> 23: </portType> 24: 25: <binding name="HelloServerSoapBinding" type="serviceNS:HelloServerPortType"> 26: <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> 27: <operation name="sayHelloTo"> 28: <soap:operation soapAction="" style="rpc"/> 29: <input> 30: <soap:body use="encoded" 31: encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 32: namespace="MyHelloService"/> 33: </input> 34: <output> 35: <soap:body use="encoded" 36: encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 37: namespace="MyHelloService"/> 38: </output> 39: </operation> 40: </binding> 41: 42: <service name="HelloServer"> 43: <port name="HelloServerPort" binding="serviceNS:HelloServerSoapBinding"> 44: <soap:address location="http://localhost:8080/axis/services/MyHelloService"/> 45: </port> 46: </service> 47: 48: </definitions>
The document consists of the following sections:
-
The XML prolog and root element (lines 18 and 48). The namespace declarations on the root element (definitions) show that all unqualified elements and attributes come from the WSDL schema. The soap prefix denotes types from the SOAP schema, while the xsd prefix denotes types from the W3C XML Schema definition. There is also a namespace defined for this service that is associated with the serviceNS prefix.
-
WSDL message definitions (lines 1016). These define two matched messagesa request and a response. The request (sayHelloToRequest) takes a single string parameter and the response (sayHelloToResponse) also returns a single string.
-
WSDL portType definitions (lines 1823). A portType is the equivalent if an interface definition. It contains one or more operation definitions, which in turn are built from the message definitions in the document. In this case, there is a single operation defined in the HelloServerPortType called sayHelloTo. This consists of the two messages, sayHelloToRequest and sayHelloToResponse, seen earlier.
-
Now that you have an interface (portType), you can define the protocols over which that interface can be accessed. The binding element (lines 2540) creates a binding, called HelloServerSoapBinding, between the HelloServerPortType and SOAP. Within this WSDL binding, a SOAP binding (soap:binding) is defined. Because SOAP can work with a variety of underlying transports and it can work in an RPC-centric or document-centric way, the attributes on the soap:binding indicate that it is an RPC-style binding that uses HTTP.
-
The WSDL operation is then mapped to a SOAP operation with input and output soap:body elements defined to map the request and response.
-
Finally, an instance of the service is defined in the WSDL service element (lines 4246). A WSDL service contains a list of WSDL port elements. Each port element defines a specific instance of a server that conforms to one of the WSDL bindings defined earlier.
Again, in the case of the simple Hello service, the service element (named HelloServer) contains a single WSDL port called HelloServerPort. This specifies that a server conforming to the HelloServerSoapBinding can be found at the given SOAP address, namely http://localhost:8080/axis/service/MyHelloService.
This is a very simple WSDL document defining a very simple service. WSDL documents are typically far longer and more complex. Because of this, WSDL is largely intended for manipulation by tools and applications.
Creating a Java Proxy from WSDL
Given the service description in Listing 20.1, the next step is to create a client that can use this service. The simplest way to do this is to have a tool generate a proxy for the service. This proxy will be a local object that will hide away a lot of the complexity associated with the mechanics of calling methods on the service.
You can apply the Apache Axis Wsdl2java tool to MyHelloService.wsdl as follows
java org.apache.axis.wsdl.Wsdl2java MyHelloService.wsdl
NOTE
To run the tools and compile the files, you must have the JAR files from the axis\WEB-INF\lib directory on your classpath, namely axis.jar, clutil.jar, crimson.jar, log4j-core.jar, wsdl4j.jar, and xalan.jar.
This will generate three Java files:
HelloServerPortType.java is a Java interface that represents the remote interface (or portType in WSDL terms). This is shown in Listing 20.2. Note that the interface looks like an RMI interface in that it extends java.rmi.Remote, and the method is defined as throwing java.rmi.RemoteException. The service proxy implements this interface, and the client should use the interface type to reference instances of the service proxy.
HelloServer.java is a factory class that creates instances of the service proxy. This is shown in Listing 20.3. The client instantiates a factory and then calls the getHelloServerPort method to obtain a service proxy. Two forms of this method are providedone that allows the client to specify the endpoint at which the service resides and the other that takes no arguments. The latter method will use the location information contained in the WSDL file when instantiating the service proxy.
HelloServerSoapBindingStub.java is the service proxy itself. Note that by using a separate interface to represent the portType and a factory for the creation of the proxy, the same client code can be used, regardless of the particular protocol binding. The code for HelloServerSoapBindingStub.java is not shown here because it is very similar to the "raw" SOAP code you will see shortly.
Listing 20.2 HelloServerPortType.java
1: /** 2: * HelloServerPortType.java 3: * 4: * This file was auto-generated from WSDL 5: * by the Apache Axis Wsdl2java emitter. 6: */ 7: 7: 8: public interface HelloServerPortType extends java.rmi.Remote { 9: public String sayHelloTo(String arg0) throws java.rmi.RemoteException; 10: }
Listing 20.3 HelloServer.java
1: /** 2: * HelloServer.java 3: * 4: * This file was auto-generated from WSDL 5: * by the Apache Axis Wsdl2java emitter. 6: */ 7: 8: public class HelloServer { 9: 10: // Use to get a proxy class for HelloServerPort 11: private final java.lang.String HelloServerPort_address = 12: "http://localhost:8080/axis/services/MyHelloService"; 13: public HelloServerPortType getHelloServerPort() { 14: java.net.URL endpoint; 15: try { 16: endpoint = new java.net.URL(HelloServerPort_address); 17: } 18: catch (java.net.MalformedURLException e) { 19: return null; // unlikely as URL was validated in wsdl2java 20: } 21: return getHelloServerPort(endpoint); 22: } 23: 24: public HelloServerPortType getHelloServerPort(java.net.URL portAddress) { 25: try { 26: return new HelloServerSoapBindingStub(portAddress); 27: } 28: catch (org.apache.axis.SerializationException e) { 29: return null; // ??? 30: } 31: } 32: }
You can now write a client application that uses these classes. The code for such an application is shown in Listing 20.4. This application simply takes the name passed as a parameter and sends it to the sayHelloTo method of the Web Service. You can see the creation of the HelloServer service proxy factory on line 20. The client then calls the getHelloServerPort method to obtain an instance of the service proxy (line 23). The client can then call the sayHelloTo method passing the given parameter (line 28). This method invocation is wrapped in a try-catch block to catch any potential RemoteException that may occur.
Listing 20.4 HelloServerClient.java Application That Uses Generated Service Proxy
1: import java.rmi.RemoteException; 2: 3: public class HelloServerClient 4: { 5: public static void main(String [] args) 6: { 7: String name = "unknown"; 8: 9: if (args.length != 1) 10: { 11: System.out.println("Usage: WebServiceSayHello <name>"); 12: System.exit(1); 13: } 14: else 15: { 16: name = args[0]; 17: } 18: 19: // Instantiate the factory 20: HelloServer factory = new HelloServer(); 21: 22: // Get a PortType that represents this particular service 23: HelloServerPortType service = factory.getHelloServerPort(); 24: 25: try 26: { 27: // Call the service 28: String response = service.sayHelloTo(name); 29: 30: System.out.println(response); 31: } 32: catch(RemoteException ex) 33: { 34: System.out.println("Remote exception: " + ex); 35: } 36: } 37: }
To test out your client, you should:
Compile the client code.
Ensure that the service is running (both Tomcat and the Axis server).
Run the client (with the appropriate classpath settings) as shown
prompt> java HelloServerClient Fred Hello Fred!
Calling the Web Service Through SOAP
You have now accessed the service through a service proxy based on WSDL. However, you can access the service directly through SOAP, should that be necessary. Indeed, some older toolkits may only provide a SOAP-level API and no WSDL-based tools, so this section looks quickly at how you would achieve the same effect directly with SOAP.
Listing 20.5 shows the code you would write under Apache SOAP 2.2 (the precursor to Axis) to call the Hello service using the SOAP API directly.
Listing 20.5 SoapSayHello.java Using the Apache SOAP 2.2 API
1: import java.net.*; 2: import java.util.*; 3: import org.apache.soap.*; 4: import org.apache.soap.rpc.*; 5: 6: public class SoapSayHello 7: { 8: private static String serviceUrn = "MyHelloService"; 9: private static String soapRouterUrl = "http://localhost:8080/axis/servlet/AxisServlet"; 10: 11: public static void main(String[] args) 12: { 13: String name = "unknown"; 14: 15: if (args.length != 1) 16: { 17: System.out.println("Usage: SoapSayHello <name>"); 18: System.exit(1); 19: } 20: else 21: { 22: name = args[0]; 23: } 24: 25: URL url = null; 26: 27: try 28: { 29: url = new URL(soapRouterUrl); 30: } 31: catch (MalformedURLException ex) 32: { 33: System.out.println("Exception: " + ex); 34: System.exit(1); 35: } 36: 37: Call call = new Call(); 38: 39: call.setTargetObjectURI(serviceUrn); 40: call.setMethodName("sayHelloTo"); 41: 42: Vector params = new Vector(); 43: 44: params.addElement(new Parameter("name", String.class, 45: name, Constants.NS_URI_SOAP_ENC)); 46: call.setParams(params); 47: 48: Response response; 49: 50: try 51: { 52: response = call.invoke(url, ""); 53: } 54: catch (SOAPException e) 55: { 56: System.err.println("Caught SOAPException (" + 57: e.getFaultCode() + "): " + 58: e.getMessage()); 59: return; 60: } 61: 62: if (!response.generatedFault()) 63: { 64: Parameter retVal = response.getReturnValue(); 65: Object value = retVal.getValue(); 66: 67: System.out.println(value != null ? "\n" + value : "I don't know."); 68: } 69: else 70: { 71: Fault fault = response.getFault(); 72: 73: System.err.println("Generated fault: "); 74: System.out.println (" Fault Code = " + fault.getFaultCode()); 75: System.out.println (" Fault String = " + fault.getFaultString()); 76: } 77: } 78: }
The first thing to notice is that the endpoint URL is now split into the service name and the SOAP router (lines 8 and 9). This SOAP router URL must be turned into a java.net.URL (lines 2535) for it to be used.
A SOAP Call is then instantiated (line 37) and populated with the service name (line 39) and the method name (line 40). The parameters for the call must be encoded as Parameter instances, specifying the parameter name, Java class, and encoding required. A java.util.Vector containing all of the parameters is then passed to the Call object (lines 4246).
The call is then made to the SOAP server using the invoke method (line 52). This is where the SOAP router URL is passed in. A SOAP Response is returned from invoke.
Now the result must be deciphered (lines 6276). This involves checking for an error, retrieving the Parameter object, extracting the actual returned object from it, and then casting this returned object to the appropriate type.
As you can see, the use of a proxy is preferable because it removes most of the complexity. This is why the Java APIs for creating and sending SOAP messagesJAX-RPC and JAXMboth work at a higher level than this. The benefits of using the WSDL-based proxy are that the client code is less complex, there is type safety by using the generated Java interface, and the client developer needs to know very little about the SOAP-level operations or indeed about SOAP itself.
A Half-Way House
There is a compromise that can be made between service-specific calling using a proxy and the use of raw SOAP. Axis provides a ServiceClient class that performs much of the code shown in Listing 20.5. In fact, all of the code from line 25 on can be effectively replaced by the following lines:
ServiceClient client = new ServiceClient(soapRouterUrl); String response = (String)client.invoke(serviceUrn, "sayHelloTo", new Object [] { name });
The service address, method name, and the parameters are all passed into the invoke method. Note that the last argument in the code shown creates a new array of type Object and populates it with a single element, which is the String containing the name provided by the user.
In this case, there is a lot more flexibility than with the WSDL proxy, because the method name and parameter can be specified at runtime. This allows for dynamic interaction with discovered services. However, the code shown is preferable to the SOAP code in Listing 20.5 because the code surrounding the call setup has been largely simplified.
Dynamic calling will be examined further tomorrow in the discussion surrounding the use of directory services.
Debugging a SOAP Interaction
As with any distributed environment, debugging Web Service interaction is a challenge. One of the main issues is knowing precisely what is being sent and received. To assist with this, Axis provides a tool called tcpmon that will monitor and display SOAP traffic.
The basic idea is that you target your client at a different port. The tcpmon utility listens on that port, logs the SOAP traffic arriving, and then passes it on to the real SOAP server port. SOAP traffic sent back is also logged. If you cannot change the client configuration, you could change the port on which the SOAP server listens. The tcpmon utility can then listen on the original SOAP server port and forward traffic on to the new port. Figure 20.6 shows how an instance of tcpmon can monitor inbound traffic from SOAP clients on port 8888, log the traffic, and then forward it on to the real SOAP router listening on port 8080.
Figure 20.6 The tcpmon utility monitoring SOAP calls on port 8888 and passing them on to the real SOAP router on port 8080.
To start the tcpmon utility, type
java org.apache.axis.utils.tcpmon
This will start a GUI through which the traffic will be displayed. When the GUI starts up, you will be prompted for the port on which to listen and also the port and host to which traffic should be forwarded. Figure 20.7 shows a monitor session being started that will listen on port 8888 and forward all traffic received on to localhost:8080.
TIP
The tcpmon utility allows you to set up multiple port/host/port mappings. Each will be displayed in its own tabbed pane.
Figure 20.7 Setting up the port configuration for tcpmon.
The request and response messages are displayed as pairs, as shown in Figure 20.8. This shows an interaction between the Hello service and a client that has been modified so that its target port is configurable. The client sends its request to port 8888 with a SOAPAction of MyHelloService/sayHelloTo. You can see the method invocation and the string parameter in the SOAP body. The response is shown in the right pane. In the response, the SOAP body contains a sayHelloToResponse message encapsulating the sayHelloToResult return value.
Figure 20.8 SOAP request and response to the Hello service seen through tcpmon.
The tcpmon utility will retain a history of messages sent back and forth through a particular port. You can then look back through a sequence of messages in your own time.