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.
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 anotherService
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: thisRandService
is an interface, whereas the publishedRandService
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
();
RandService
port
=
service
.
getRandServicePort
();
// sample calls
System
.
out
.
println
(
port
.
next1
());
System
.
out
.
println
();
List
<
Integer
>
nums
=
port
.
nextN
(
4
);
for
(
Integer
num
:
nums
)
System
.
out
.
println
(
num
);
}
}
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.
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
();
Console
.
WriteLine
(
"Call to next1():\n"
+
service
.
next1
());
Console
.
WriteLine
(
"\nCall to nextN(4):"
);
int
?[]
nums
=
service
.
nextN
(
4
);
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.
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.
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"
/>
</
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
>
</
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.