Web services evolved from the RPC (Remote Procedure Call) mechanism in DCE (Distributed Computing Environment), a framework for software development from the early 1990s. DCE includes a distributed filesystem (DCE/DFS) and a Kerberos-based authentication system. Although DCE has its origins in the Unix world, Microsoft quickly did its own implementation known as MSRPC, which in turn served as the infrastructure for interprocess communication in Windows. Microsoft’s COM/OLE (Common Object Model/Object Linking and Embedding) technologies and services were built on a DCE/RPC foundation. There is irony here. DCE designed RPC as a way to do distributed computing (i.e., computing across distinct physical devices), and Microsoft cleverly adapted RPC to support interprocess communication, in the form of COM infrastructure, on a single device—a PC running Windows.
The first-generation frameworks for distributed object systems, CORBA (Common Object Request Broker Architecture) and Microsoft’s DCOM (Distributed COM), are anchored in the DCE/RPC procedural framework. Java RMI (Remote Method Invocation) also derives from DCE/RPC, and the method calls in Java EE (Enterprise Edition), specifically in Session and Entity EJBs (Enterprise Java Bean), are Java RMI calls. Java EE (formerly J2EE) and Microsoft’s DotNet are second-generation frameworks for distributed object systems, and these frameworks, like CORBA and DCOM before them, trace their ancestry back to DCE/RPC. By the way, DCE/RPC is not dead. Various popular system utilities (for instance, the Samba file and print service for Windows clients) use DCE/RPC.
DCE/RPC has the familiar client/server architecture in which a client invokes a procedure that executes on the server. Arguments can be passed from the client to the server and return values can be passed from the server to the client. The framework is platform- and language- neutral in principle, although strongly tilted toward C in practice. DCE/RPC includes utilities for generating client and server artifacts (stubs and skeletons, respectively). DCE/RPC also provides software libraries that hide the transport details. Of interest now is the IDL (Interface Definition Language) document that acts as the service contract and is an input to utilities that generate artifacts in support of the DCE/RPC calls. An IDL document can be short and to the point (see Example 1-1).
Example 1-1. A sample IDL document that declares the echo
function
/* echo.idl */
[
uuid
(
2
d6ead46
-
05
e3
-
11
ca
-
7
dd1
-
426909
beabcd
),
version
(
1.0
)]
interface
echo
{
const
long
int
ECHO_SIZE
=
512
;
void
echo
(
[
in
]
handle_t
h
,
[
in
,
string
]
idl_char
from_client
[
],
[
out
,
string
]
idl_char
from_server
[
ECHO_SIZE
]
);
}
The IDL interface named echo
, identified with a machine-generated UUID (Universally Unique IDentifier),
declares a single function with the same name, echo
. The names are arbitrary and need not be the same.
The echo
function expects three arguments, two of which are
in
parameters (that is, inputs into the remote procedure) and one of which is an out
parameter (that
is, an output from the remote procedure). The first argument, of built-in
type handle_t
, is required and points to an RPC data structure. The function echo
could but does
not return a value, because the echoed string is returned instead as an out
parameter. The IDL specifies
the invocation syntax for the echo
function, which is the one and only operation in the service.
Except for annotations in square brackets to the left of the three echo
parameters, the syntax of the
IDL is essentially C syntax. The
IDL document is a precursor of the WSDL (Web Service Description Language) document that provides a
formal specification of a web service and its operations. The WSDL document is discussed at length in
Chapter 4 on SOAP-based services.
There is a Microsoft twist to the IDL story as well. An ActiveX control under Windows is a DLL (Dynamic Link Library) with an embedded typelib, which in turn is a compiled IDL file. For example, suppose that a calendar ActiveX control is plugged into a browser. The browser can read the typelib, which contains the invocation syntax for each operation (e.g., displaying the next month) in the control. An ActiveX control is thus a chunk of software that embeds its own interface. This is yet another inspired local use of a technology designed for distributed computing.
In the late 1990s, Dave Winer of UserLand Software developed XML-RPC, a technology innovation that has
as good a claim as any to mark
the birth of web services. XML-RPC is a very lightweight RPC system with support for
elementary data types (basically, the built-in C types together with a
boolean
and a datetime
type) and a few simple commands. The original specification is about seven
pages in length. The two key features are the use of XML marshaling/unmarshaling
to achieve language neutrality and reliance on HTTP (and, later, SMTP)
for transport. The term marshaling refers to the conversion of an in-memory object (for instance, an
Employee
object in Java) to some other format (for instance, an XML document); unmarshaling
refers to the inverse process of generating an in-memory object from, in this example,
an XML document. The marshal/unmarshal distinction is somewhere between close to and identical
with the serialize/deserialize distinction. My habit is to use the distinctions interchangeably.
In any case, the O’Reilly open-wire Meerkat service and the WordPress publishing platform are based on
XML-RPC.
Two key differences separate XML-RPC, on the one side, from DCE/RPC and its off-shoots, on the other side:
- XML-RPC payloads are text, whereas DCE/RPC payloads are binary. Text is relatively easy to inspect and process with standard, readily available tools such as editors and parsers.
- XML-RPC transport uses HTTP rather than a proprietary system. To support XML-RPC, a programming language requires only a standard HTTP library together with libraries to generate, parse, transform, and otherwise process XML.
As an RPC technology, XML-RPC supports the request/response pattern. Here is the XML request to
invoke, on a remote machine, the Fibonacci function with an argument of 11. This
argument is passed as a 4-byte integer, as the XML start tag <i4>
indicates:
<?
xml
version
=
"1.0"
>
<
methodCall
>
<
methodName
>
fib
<
methodName
>
<
params
>
<
param
><
value
><
i4
>
11
</
i4
></
value
></
param
>
</
params
>
</
methodCall
>
The integer 11 occurs in the XML-RPC message as text. An XML-RPC library on the receiving end needs to extract 11 as text and then convert the text into a 4-byte integer in the receiving language such as Go or Java. Even this short example illustrates the idea of having XML—in particular, data types expressed in XML—serve as the leveling mechanism between two different languages involved in an XML-RPC exchange.
XML-RPC is deliberately low fuss and lightweight. SOAP, an XML dialect derived straight from XML-RPC, is considerably heavier in weight. From inception, XML-RPC faced competition from second-generation DOA systems such as Java EE (J2EE) and AspNet. The next section considers the challenges inherent in DOA systems. These challenges sustained and eventually intensified interest in lighter-weight approaches to distributed computing—modern web services.
What advantages do web services have over DOA technologies such as Java RMI? This section addresses the question with an example. Java RMI (including the Session and Entity EJB constructs built on Java RMI) and DotNet Remoting are examples of second-generation distributed object systems. Consider what a Java RMI client requires in order to invoke a method declared in a service interface such as this:
import
java.util.List
;
public
interface
BenefitsService
extends
java
.
rmi
.
Remote
{
public
List
<
Benefit
>
getBenefits
(
Emp
emp
)
throws
RemoteException
;
}
The interface appears deceptively simple in that it declares only the
method named getBenefits
, yet the interface likewise hints at what makes
a Distributed Object Architecture so tricky.
A client against this BenefitsService
requires a Java RMI stub, an instance
of a class that implements the BenefitsService
interface. The stub is downloaded automatically
from the server to the client as part of the Java RMI setup (see Figure 1-3).
Once the stub setup is done,
the getBenefits
method is executed as a stub method; that is, the stub acts as the
client-side object making a remote method call through one of stub’s encapsulated methods.
The call thus has the following syntax:
Emp
fred
=
new
Emp
();
//...
List
<
Benefit
>
benefits
=
rmiStub
.
getBenefits
(
fred
);
// rmiStub = reference
Invoking the getBenefits
method
requires that the byte codes for various Java classes, standard and
programmer-defined, be available on the client machine. To begin, the client
needs the class Emp
, the argument type for the getBenefits
method, and the class
Benefit
, the member type for the List
that the method getBenefits
returns.
Suppose that the class Emp
begins like this:
public
class
Emp
{
private
Department
department
;
private
List
<
BusinessCertification
>
certifications
;
private
List
<
ClientAccount
>
accounts
;
private
Map
<
String
,
Contact
>
contacts
;
...
}
The standard Java types such as List
and Map
are already available on the client side because
the client is, by assumption, a Java application. The challenge involves the
additional, programmer-defined types such as Department
,
BusinessCertification
, ClientAccount
, and Contact
that are needed
to support the client-side invocation of a remotely executed method. The
setup on the client side to enable a remote call such as:
Emp
fred
=
new
Emp
();
// set properties, etc.
List
<
EmpBenefits
>
fredBenefits
=
rmiStub
.
getBenefits
(
fred
);
is significant, with lots and lots of bytes required to move from the server down to the client just for the setup. Anything this complicated is, of course, prone to problems such as versioning issues and outright errors in the remote method calls.
Java RMI uses proprietary marshaling/unmarshaling and proprietary transport, and DotNet does the same. There are third-party libraries for interoperability between the two frameworks. Yet a Java RMI service can be expected to have mostly Java clients, and a DotNet Remoting service can be expected to have mostly DotNet clients. Web services represent a move toward standardization, simplicity, and interoperability.
Web services simplify matters in distributed computing. For one thing, the client and service typically
exchange XML or equivalent documents, that is, text. If needed, non-text bytes can be exchanged
instead, but the preferred payloads are text. The exchanged text can be inspected, validated,
transformed, persisted, and otherwise processed using readily available,
nonproprietary, and often free tools. Each side, client and service,
simply needs a local software library that binds language-specific types
such as the Java String
to XML Schema or comparable
types, in this case xsd:string
. (In the qualified name xsd:string
, xsd
is a namespace
abbreviation
and string
is a local name. Of interest here is that xsd:string
is an XML type rather
than a Java type.) Given these Java/XML bindings, relatively uncomplicated library modules can
convert from one to the other—from Java to XML or from XML to Java (see Figure 1-4).
Processing on the client side, as on the service side, requires only locally available libraries and utilities. The complexities, therefore, can be isolated at the endpoints—the service and the client applications together with their supporting libraries—and need not seep into the exchanged messages. Finally, web services are available over HTTP, a nonpropriety protocol that has become standard, ubiquitous infrastructure; HTTP in particular comes with a security extension, HTTPS, that provides multifaceted security services.
In a web service, the requesting client and the service need not be coded in the same language or even in the same style of language. Clients and services can be implemented in object-oriented, procedural, functional, and other language styles. The languages on either end may be statically typed (for instance, Java and Go) or dynamically typed (for example, JavaScript and Ruby). The complexities of stubs and skeletons, the serializing and deserializing of objects encoded in some proprietary format, give way to relatively simple text-based representations of messages exchanged over standard transports such as HTTP. The messages themselves are neutral; they have no bias toward a particular language or even family of languages.
The first code example in this chapter, and all of the code examples in Chapter 2 and Chapter 3, involve REST-style services. Accordingly, the next section looks at what REST means and why the REST-style service has become so popular. From a historical perspective, REST-style services can be viewed as a reaction to the growing complexity of SOAP-based ones.
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.