In-Depth

Write a Web Service Client

Web services have become a standard for building client/server applications. Learn an approach for using the JAX-RPC SI toolkit to generate a Web service's client-side code.

Web services are emerging as a standard for building client-server applications, and you can use a variety of techniques to write them, including using toolkits. Among the toolkits available for Java are the open source Axis and the JAX-RPC standard implementation written by Sun Microsystems.

We'll focus on a step-by-step approach to writing Web service clients using Sun's JAX-RPC standard implementation (JAX-RPC SI) as its Web service toolkit. This toolkit is solid, capable of producing Web services for deployment in a production environment; it is essentially the reference implementation for the JAX-RPC standard; and the JAX-RPC specification is widely supported. For instance, both BEA and IBM support JAX-RPC, and in fact you can deploy a Web service produced by Sun's implementation into an IBM WebSphere server. In addition, Sun's implementation tracks various other specifications, such as WS-Security, that are becoming increasingly important.

To write a client, you need a server to program against. Several servers are available, such as Google or eBay, but here we'll use Amazon's Web service. However, knowing the service we want to use simply isn't good enough. The fact that I know that Amazon has a Web service doesn't really get me anywhere. I need to know how to call that Web service: What operations does it support? What parameters should I pass? What return values do I get?

These questions demonstrate that to write a Web service, there must be a description of that Web service available. In traditional client-server systems, servers have a description language; for example, both DCOM and CORBA use something called Interface Definition Language (IDL). DCOM and CORBA IDLs are not the same language—that is, a CORBA client could not consume a DCOM server's IDL to describe remote services available to clients.

Describe the Service
In Web services, Web Services Description Language (WSDL) takes the place of IDL by providing a description of the Web service. WSDL is an XML grammar that is machine consumable. Code generators can read it. Humans can also read it, but it is not the easiest language to consume or to produce. A colleague of mine, Simon Horrell, is fond of saying, "WSDL is like the sun: you can't live without it, but if you stare at it for too long, you'll go blind!"

Amazon provides a Web services toolkit that you can download from the Amazon site. This toolkit provides documentation for the Amazon Web service, and it also specifies the location of the WSDL document that describes the Web service, which is also available at Amazon's site. If you open this WSDL document, don't be too concerned if it looks very complex (it is), but do be aware that it provides a more or less complete description of the Web service. While you don't need the Amazon toolkit, you will need a developer token, which you can also download. Amazon uses this token to track usage of the Web service and to check that the service is not being abused.When you write a Web service there are many approaches you can take, but they fall broadly into two categories. You can use the tools provided by the toolkit of choice to consume the WSDL and generate client-side stubs that make the call; or you can use lower-level APIs to write the Web service by hand.

Using these two approaches entails a trade-off. The first approach (consuming WSDL) is relatively easy; however, it is not very flexible. Many tools are available that will do the heavy lifting for you. While the tools are easy to use and the code they generate is easy to use too, you may find that the code may not be able to do all you want it to do and that it may not perform as well as necessary. For example, you may need to access the raw HTTP traffic, and it may be that the generated code does not let you do that.

The second approach is more difficult, even though you will get a lot more flexibility. Writing the code from scratch can be challenging and time consuming, but will give you ultimate flexibility and performance. There are different ways of doing this: you can use the Java java.net.URL and related classes, or you can use java.net.Socket and work at a lower level. HTTP APIs such as those offered by the Jakarta Commons project are also available. Typically you will use code-generation tools, and you may occasionally need to drop down to handwritten code for specific parts of the client.

Down to the Stub
To begin, we'll use Sun's JAX-RPC SI to build the clients (and the servers). You can download the Java Web Services Development Pack (JWSDP) from Sun's Java developers' site. Once you download and install the toolkit, it includes the JAX-RPC SI. During the install you will be asked to choose a specific Web server. You can choose to download a Web server or select no Web server at this point.

Other details about installation are a topic for another article; just be aware that we'll use %JWSDP-HOME% to specify the location where the JWSDP was installed. After installation, ensure that you have %JWSDP-HOME%/jaxrpc/bin on the system path. This directory contains Windows batch files and Unix shell scripts to launch the tools used as part of the JWSDP. You can also add %JWSDP_HOME%/jaxb/bin to the path for additional tools.

JAX-RPC provides a tool that reads WSDL and generates client-side stubs. These stubs are Java classes and interfaces that will be used by our code. The stubs provide a client-side interface to the server-side functionality. For example, if our server offered a Maths service with a method called add, the generated stub would also have a method called add. Our client code will call a method on the stub, and the stub implementation code will take the parameters to that method and turn the Java method call into a Web service request. This request will be sent over HTTP to the server and will use SOAP as the RPC protocol (see Figure 1).

The client calls the stub, the stub translates the call into a SOAP message, and the stub sends it using RPC. The listening server receives the SOAP message and (in all likelihood) translates it into a method call at the server. If the server is written in Java, the SOAP message is turned into a Java call. If the server is a .Net server, the call will probably be to a C# or a VB.Net object. The server's return values are translated back to SOAP and then returned to the stub, which translates the returned SOAP message into a Java response.

The wscompile tool generates the stubs, and it has loads of options. For this discussion we need only be concerned about running wscompile to generate client-slide code. The primary input to wscompile is a configuration file (typically called config.xml). This file is read by wscompile and is used to specify information such as the location of the WSDL document used to generate the client. Or, if we are generating a server you can also specify Java class names to be used for the server-side generation. In this case we are generating client-side code, so the configuration file should contain the WSDL location.

If you plan to follow along with the code developed here, you should create a directory to contain all the pieces that will be used, and then in that directory create a src, a classes, and an etc directory. The src directory will contain the source code that we write, the classes directory will contain the compiled code, and the etc directory will hold the configuration information. We will create this config.xml file in the etc directory:

<?xml version="1.0" encoding=
  "UTF-8"?>
<configuration xmlns=
  "http://java.sun.com/xml/ns/
  jax-rpc/ri/config">
  <wsdl location=
    "http://soap.amazon.com/
    schemas3/
    AmazonWebServices.wsdl"
    packageName=
    "javapro.amazon" />
</configuration>

Configuration Elements
What does the config.xml file contain? For one thing, all elements are in the http://java.sun.com/xml/ns/jax-rpc/ri/config namespace. The configuration element is always the root element, and then there is a wsdl element, which contains two attributes: location and packageName. The location attribute is obviously the location of the WSDL file to read. The packageName attribute is used by wscompile as the name of the package into which the generated code goes. The location can be a local file or it can be a URL.

Now run wscompile from the command line passing the -help flag (see Figure 2). As you can see, lots of flags and features are available. Those that we care about for now are -gen (or -gen:client, which is the same); -d, which tells wscompile where to put the compiled files; and -s, which is the location of the generated sources. You can also use -verbose, which lets you know what wscompile is doing, and -keep, which specifies keeping the generated source files. You also need to pass the configuration file as a parameter to wscompile:

[client]$ wscompile.sh 
  -verbose -gen -d classes 
  -s src -keep etc/config.xml

This parameter produces reams of output detailing the code that has been generated for the Web service. The wscompile tool generates the source and compiles it to classes. If the -keep flag is not passed, the sources will be lost. There is a lot of code here, but we will be using only a small part of it for the client.

A WSDL document contains a service element. This element provides the highest-level description of a Web service—essentially its name. A service (in WSDL) is a collection of ports, and a port is a high-level description of how to call the service. In particular, a port contains the address of the Web service. If you look at the generated classes you will see two files: AmazonSearchService.java and AmazonSearchService_Impl.java. These files are an interface and an implementation class, respectively, that represent the service defined in Amazon's WSDL. There is also an interface called AmazonSearchPort.java that corresponds to the WSDL port definition. If you look at the Port interface, you'll see that it contains a list of all the operations that can be performed by the Amazon Web service. A stub class implements this service interface. That stub is called AmazonSearchPort_Stub.java, and this is the class our client will use (see Listing 1).

Much of the implementation code has been elided to make the important data easier to see. Note that it is the getAmazonSearchPort() method that creates an instance of AmazonSearchport_Stub, which is the stub. The stub is initialized with a handlerChain (see Listing 2).

Again you can safely ignore much of this code; it is implementation detail. However, notice that the method is passed an AuthorRequest structure and returns a ProductInfo structure. Also note that the URL we will send the request to is set as part of the method with the call to:

setProperty 
  HttpClientTransport.
  HTTP_SOAPACTION_PROPERTY, 
  "http://soap.amazon.com");

Create the Client
Now let's write the client code, which in fact is fairly trivial (see Listing 3). In general, a JAX-RPC client looks like the code you see in Listing 3: create the service implementation, ask it for the required Web service port, and call the appropriate method. The specifics in it are that to call the authorSearchRequest() method you have to initialize the AuthorRequest object with the author name. In this case, you want to look in the books section, you want "lite" as opposed to detailed data, and you limit the search so that it contains the keyword "Web." The other important value you need to set is the developer token. I haven't put a value here for obvious reasons!

The URL is hard coded into the stub. The URL doesn't often change, but suppose you need to send the request to a different URL. You may have a service that is located on multiple servers, maybe for failover reasons or maybe for load balancing purposes, or possibly you want to send the request and response through a trace tool that will show the SOAP data being sent in each direction. To do any of these tasks you can change the Web service address in the stub:

AmazonSearchPort_Stub port = (
  AmazonSearchPort_Stub)impl.
  getAmazonSearchPort();

port._setProperty(
  AmazonSearchPort_Stub.
  ENDPOINT_ADDRESS_PROPERTY, 
  "http://localhost:9090/onca/
  soap3");

Notice that the return from getAmazonSearchPort() is cast to be the implementation class rather than the interface so we can set the end-point address, which is set to call localhost.

You can then run a trace tool such as tcpmon from Axis or HttpProxy written by me (the code for XXX-TKO-XXXX is available online at www.javapro.com). This tool is set to listen to requests on the port that the client is going to call and forwards them to the original port (see Figure 3).

You use these tools to examine the SOAP and HTTP if there are problems that are hard to track down. If you are behind a proxy you need to tell the Java runtime to call through that proxy. You do that by setting Java system properties:

public static void main(
  String[] args)
{
  System.setProperty( 
    "proxySet", "true" );
  System.setProperty( 
    "http.proxyHost", 
    "myproxy" );
  System.setProperty( 
    "http.proxyPort", "8080" );

  System.setProperty(
    "http.proxyUser", "xxx");
  System.setProperty(
    "http.proxyPassword", 
    "yyy");
  ...
}  

Web services are rapidly becoming a standard for building client/server applications. Web services can be written in many ways, including by hand or using toolkits. There are various toolkits available for Java including the open source Axis and the JAX-RPC SI written by Sun. We have touched on aspects of using the JAX-RPC SI to implement a Web services client by using wscompile to read WSDL and generate the necessary client-side code. The next step will be to look at how to write servers using JAX-RPC.

We haven't yet discussed other aspects, such as managing HTTP sessions, HTTP authentication, and managing errors, as well as the roles various Java specifications such as SOAP with Attachments API for Java (SAAJ) and Java Architecture for XML Binding (JAXB) play in the Web services realm. Then there are the less well-trodden aspects of Web services, such as managing attachments, understanding WSDL, and the differences between the rpc/encoded and document/literal style services. These are topics better served for the future. Stay tuned.