Clients Against the RandService

The claim that SOAP-based services are language-neutral needs to be taken on faith a bit longer. The first client against the RandService is in Java but the two thereafter are in C# and Perl. Starting with a Java client will help to clarify how the critical service contract, the WSDL document, can be put to good use in writing a client. The WSDL will be studied in detail, but putting the WSDL to work first should help to motivate the detailed study.

A Java Client Against the RandService

Recall the XML Schema (see Example 4-3) that the Endpoint publisher generates dynamically when the RandService is published. The publisher likewise generates a WSDL, which can be requested as follows:

% curl http://localhost:8888/rs?wsdl

JDK 1.6 and greater ship with a utility, wsimport, that uses a WSDL to generate Java classes in support of programming a client against the service described in the WSDL. Here is how the utility can be used in the current example:

% wsimport -p client -keep http://localhost:8888/rs?wsdl

The -p flag stands for “package”: the utility creates a directory named client and puts the generated Java code in this directory/package. The -keep flag generates source (.java) as well as compiled (.class) files; without this flag, only compiled files would be in the client directory. Sixteen files are generated in total, half source and half compiled. Among these are files with names such as Next1 and Next1Response, the very names of the classes generated at the publication of the RandService. In any case, these client-side artifacts correspond to SOAP types described in the XML Schema document for the RandService.

How are the wsimport-generated files to be used? Two of these are of special interest:

  • The class RandServiceService begins with the name of the published SOAP service, RandService, and has another Service stuck on the end. The @WebService annotation could be used to specify a less awkward name but, for now, the key point is that this class represents, to the client, the deployed web service.
  • The interface RandService has the same name as the published service but there is a critical difference: this RandService is an interface, whereas the published RandService is a class. This interface, like any Java interface, declares methods—hence, the interface declares the operations encapsulated in published service and thereby specifies the invocation syntax for each operation. In this example, there are two such operations: next1 and nextN.

The RandServiceService and RandService types are used in an idiomatic way to write the Java client against the service. The RandClient (see Example 4-6) is a sample client that illustrates the idiom.

Example 4-6. A Java client built with wsimport-generated artifacts

import client.RandServiceService;
import client.RandService;
import java.util.List;

public class RandClient {
    public static void main(String[ ] args) {
        // set-up
        RandServiceService service = new RandServiceService(); 1
        RandService port = service.getRandServicePort();       2
        // sample calls
        System.out.println(port.next1());                      3
        System.out.println();
        List<Integer> nums = port.nextN(4);                    4
        for (Integer num : nums) System.out.println(num);      5
    }
}

The RandClient imports two types from the wsimport-generated artifacts: the class RandServiceService and the interface RandService. In the setup phase of the client code, the class’s no-argument constructor is invoked to create an object that represents, on the client side, the service itself (line 1). Once this object is constructed, there is a get call with a distinct pattern:

service.get<name of interface type>Port() // line 2 pattern

In this case, the interface is named RandService and so the call is:

service.getRandServicePort() // line 2

This get method returns a reference to an object that encapsulates the two operations in the RandService, next1 and nextN. The reference is named port, although any name would do, for reasons that will become clear once the WSDL is studied in detail. The port reference is then used to make two sample calls against the service. On a sample run, the output was:

53378846         // from line 3
-818435924       // from lines 4 and 5
104886422
1714126390
-2140389441

The first integer is returned from the call to next1 and the next four integers from the call to nextN.

The RandClient does reveal an oddity about the wsimport-generated artifacts. In the RandService, the method nextN begins:

public int[ ] nextN(...

The return type is int[ ], an array of int values. In the wsimport-generated interface RandService, the method nextN begins:

public List<Integer> nextN(...

The wsimport utility is within its rights to replace int[ ] with List<Integer>, as a List has a toArray method that returns an array; with automatic boxing/unboxing, the Java types Integer and int are interchangeable in the current context. The point is that the programmer typically needs to inspect at least the wsimport-generated interface, in this example RandService, in order to determine the argument and return types of every operation.

A final, obvious point about the interaction between the Java client and the Java service deserves mention: the SOAP is completely transparent. The underlying SOAP libraries generate the SOAP on the sending side and parse the SOAP on the receiving side so that the Java code on both sides can remain agnostic about what type of payload is being sent and received. SOAP transparency is a major selling point for SOAP-based services.

A C# Client Against the RandService

The next client is in C#, a DotNet language similar to Java; DotNet has a wsdl utility similar to Java’s wsimport utility. The wsdl utility can be targeted at the dynamically generated WSDL for the RandService:

% wsdl http://localhost:8888/rs?wsdl

This command generates a single file with an awkward name: RandServiceService.cs (see Example 4-7).

Example 4-7. A C# client, built with wsdl-generated code, against the RandService

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

// This source code was auto-generated by wsdl, Version=4.0.30319.1.
...
public partial class RandServiceService :
        System.Web.Services.Protocols.SoapHttpClientProtocol {
    private System.Threading.SendOrPostCallback next1OperationCompleted;
    private System.Threading.SendOrPostCallback nextNOperationCompleted;

    public RandServiceService() { this.Url = "http://localhost:8888/rs"; }
    ...
    public int next1() {
        object[] results = this.Invoke("next1", new object[0]);
        return ((int)(results[0]));
    }
    ...
    public System.Nullable<int>[]
             nextN([System.Xml.Serialization.XmlElementAttribute(
                Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] int arg0) {
        object[] results = this.Invoke("nextN", new object[] {arg0});
        return ((System.Nullable<int>[])(results[0]));
    }
    ...
}
...

The code excised from the C# RandServiceService class supports asynchronous calls against the Java RandService. Java, too, supports both synchronous (blocking) and asynchronous (nonblocking) calls against a web service’s operations, as a sample client against the RandService later illustrates. For now, only synchronous calls are of interest. Here is a sample C# client that uses the wsdl-generated code to make calls against the RandService:

class RandClient {
  static void Main() {
     RandServiceService service = new RandServiceService();      1
     Console.WriteLine("Call to next1():\n" + service.next1());  2
     Console.WriteLine("\nCall to nextN(4):");
     int?[] nums = service.nextN(4);                             3
     foreach (int num in nums) Console.WriteLine(num);
  }
}

The C# client code is simpler than its Java counterpart because the new operation with the no-argument constructor RandServiceService() (line 1) creates an object that encapsulates the client-side operations next1 and nextN. The C# code does not require the getRandServicePort() call from the Java client. The call to next1 (line 2) is basically the same in the C# and Java clients, but the C# call to nextN has unusual syntax. The return type int?[] (line 3) signifies an integer array that may have null as its value; the type int[] signifies an integer array that cannot be null. On a sample run, the C# client output is:

Call to next1():
680641940
Call to nextN(4):
1783826925
260390049
-48376976
-914903224

The C# example does illustrate language interoperability for SOAP-based services, although C# and Java are at least cousins among programming languages. The next sample client is written in a language quite different from Java.

A Perl Client Against the RandService

The final client (see Example 4-8) against the Java RandService is in Perl. This client makes it easy to display the SOAP messages that go back and forth between client and service; the Perl library SOAP::Lite has an excellent, easy-to-use tracer.

Example 4-8. A Perl client against the RandService

#!/usr/bin/perl -w

use SOAP::Lite +trace => 'debug';
use strict;

my $soap =                                                               1
    SOAP::Lite->uri('http://rand/')->proxy('http://localhost:8888/rs/');
my $num = $soap->next1()->result();                                      2
print "Response is: $num\n";                                             3

In line 1, the Perl client constructs a SOAP::Lite object (the reference is $soap) that communicates with the RandService. The uri value of http://rand/ is the namespace that identifies a particular service available at the proxy (that is, the URL) value of http://localhost:8888/rs. A given service endpoint, a URL, could host any number of services, with a URI identifying each. In line 2, the call to next1 returns a SOAP message:

<?xml version="1.0" ?>
<S:Envelope
    xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Body>
    <ns2:next1Response xmlns:ns2="http://rand/">
      <return>1774649411</return>
    </ns2:next1Response>
  </S:Body>
</S:Envelope>

The cascaded call to result (also line 2) extracts the value 1774649411 from the SOAP envelope, and the value is assigned to the variable $num. The client program prints the value and exits. This Perl-to-Java request again confirms the language transparency of a SOAP-based service.

The Perl client is especially useful because of its trace capabilities. Example 4-9 is the HTTP request that the Perl client generates on a sample run; Example 4-10 is the HTTP response from the Java service. In the request, the body of the POST request contains a SOAP envelope, so named because of the local name Envelope in the XML tag’s qualified name soap:Envelope.

Example 4-9. The HTTP request from the Perl client to the RandService

POST http://localhost:8888/rs HTTP/1.1
Accept: text/xml
Accept: multipart/*
Accept: application/soap
Content-Length: 420
Content-Type: text/xml; charset=utf-8
SOAPAction: ""

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
               xmlns:tns="http://rand/"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <tns:next1 xsi:nil="true" /> 1
   </soap:Body>
</soap:Envelope>

Example 4-10. The HTTP response from the RandService to the Perl client

HTTP/1.1 200 OK
Content-Type: text/xml;charset="utf-8"
Client-Peer: 127.0.0.1:8888
Client-Response-Num: 1
Client-Transfer-Encoding: chunked

<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <ns2:next1Response xmlns:ns2="http://rand/">
         <return>1774649411</return>                     1
      </ns2:next1Response>
   </S:Body>
</S:Envelope>

Also in the request, this code in line 1 means that the next1 operation takes no arguments:

<tns:next1 xsi:nil="true"/>

The HTTP response is more complicated than the request because there is a return value (line 1):

<return>1774649411</return>

The WSDL document specifies that the response from the RandService occurs in an element tagged return.

The examples so far illustrate that the WSDL document can be used even if its detailed structure remains unknown. Now is the time to take a close look at how the WSDL is structured.

Get Java Web Services: Up and Running, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.