A Very Short History of Web Services

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.

From DCE/RPC to XML-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(2d6ead46-05e3-11ca-7dd1-426909beabcd), 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.

Distributed Object Architecture: A Java Example

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).

Downloading a stub in Java RMI

Figure 1-3. Downloading a stub in Java RMI

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 to the Rescue

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).

Java/XML conversions

Figure 1-4. Java/XML conversions

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.