XML-RPC is an XML application designed to enable remote procedure calls (RPC) over the Internet. In Java lingo, a procedure call is just a method invocation. In some other languages like C it might be called a function call. However, it just means that some named chunk of code somewhere is invoked with a list of argument values of particular types. The procedure may or may not return a single value of a known type; i.e. in Java terms it may or may not return void. A remote procedure call is one in which the called procedure is not necessarily running on the same host as the calling procedure.
XML-RPC was hardly the first effort to invent a syntax for remote procedure calls. There have been numerous attempts previously including CORBA and Java’s own Remote Method Invocation (RMI). However, prior to XML-RPC none of these technologies lived up to their hype. The reason is they were too complex, too binary, too opaque, and too platform dependent.
XML-RPC is a clear case of the triumph of worse is better. It bit off the 90% of the problem that gave developers the features they actually needed. It ignored the 10% of the problem that caused 90% of the complexity in previous RPC systems. Features it ignores completely include garbage collection, object stubs and skeletons, callbacks, activation, object serialization, and more. What it provides is a simple, easy-to-comprehend means of sending a method name and a list of arguments from one system to another. And it turns out that this may be the only thing that’s needed in many, many actual systems.
At a very high level, the fundamental idea of XML-RPC is this: An XML document that contains a method name and some arguments is sent to a Web server using HTTP POST. The server invokes the method with the specified arguments. Then it wraps up the return value of the method in another XML document, and sends that back to the client.
For example, let’s suppose you want to invoke the getQuote() method in the program running at the URL http://stocks.cafeconleche.org/quotes.cgi. This method takes as an argument a string containing a stock symbol and returns a double containing the current price of the stock. That is, in Java parlance its signature looks like this:
public static double getQuote(String symbol);
Encoded as an XML-RPC document, the request looks quite different, but the same information is still present. Example 2.6 demonstrates.
Example 2.6. An XML-RPC request document
<?xml version="1.0"?> <methodCall> <methodName>getQuote</methodName> <params> <param> <value><string>RHAT</string></value> </param> </params> </methodCall>
The root element of an XML-RPC request document is methodCall. This has a methodName child element whose content is the ASCII name of the method to invoke. The methodCall element also has a params child element containing the arguments to pass to the method. Each argument is encoded as a param element that contains a value element. The value element contains a child element identifying the type of the param, and this child element contains the actual value passed, as an ASCII string. Here the type of the single argument is string. Other possibilities include int, boolean, double, dateTime.iso8601, and base64. i4 is allowed as an alias for int, but is not a different type.
The XML-RPC client (which is normally not a web browser) will post this document to the server as shown in Example 2.7 using the MIME media type text/xml. It must provide the correct content length so that the server knows when the client has finished sending.
Example 2.7. POSTing an XML-RPC request document
POST /quotes.cgi HTTP/1.0 Host: stocks.cafeconleche.org Content-Type: text/xml Content-length: 167 <?xml version="1.0"?> <methodCall> <methodName>getQuote</methodName> <params> <param> <value><string>RHAT</string></value> </param> </params> </methodCall>
The server then responds with an HTTP header and a response document as shown in Example 2.8. The HTTP header is the standard sort of HTTP header you’d see with any successful web request.
Example 2.8. An XML-RPC response
HTTP/1.0 200 OK Date: Mon, 16 Jul 2001 20:12:37 GMT Server: Apache/1.3.12 (Unix) mod_perl/1.24 Last-Modified: Mon, 16 Jul 2001 20:12:37 GMT Content-Length: 140 Connection: close Content-Type: text/xml <?xml version="1.0"?> <methodResponse> <params> <param> <value><double>4.12</double></value> </param> </params> </methodResponse>
This is the basic format of all successful XML-RPC responses. A methodResponse root element contains a single params child element which contains a single param child element which contains a single value element. The value element contains a single value of a type indicated by its child element, double in this example. The only thing that can change is the type and content of the response data.
So far I’ve been talking as if the program on the server that receives and processes this request is a Java class with the appropriately named methods, but that isn’t necessarily so. The server program could be written in Perl, Python, C, C#, C++, AppleScript, Rexx, or any of dozens of other languages. Whatever language the program is written in, it may or may not have an actual procedure named “getQuote”. All that’s required is that the server which receives this document has some way of dispatching the request to an appropriate process. Sometimes the server may parse the XML and send the raw data to the processor. Other times, it may send the entire XML document. It might even transform the XML document into another form and send that. What happens on the server doesn’t really matter as long as it eventually sends back its answer as an XML document in the proper format. XML very neatly and almost completely decouples the implementation from the interface. In this respect, at least, XML-RPC is a huge improvement over competing technologies like RMI, DCOM, and CORBA, all of which make way too many assumptions about how the remote end of the connection is implemented.
The implicit XML-RPC model of how services are implemented is a lot closer to C than to Java. This is not to say you can’t use Java to write XML-RPC clients or servers — you most certainly can — just that an XML-RPC procedure call is more like a C function call than a Java method call and that XML-RPC data types are more like C’s data types than Java’s object types. Fortuitously, XML-RPC doesn’t have any concept of pointers (the root of all C’s evils); but it does have structs and arrays for handling combinations of values and lists of values. These constructs can nest. That is, a struct member can be another struct or an array; and an array element can be a struct or another array. This enables you to pass fairly complex data structures to remote procedures.
An XML-RPC array is represented as an array element. The array element contains a single data element which contains zero or more value elements. These are the same value elements used in param elements. Thus each value element contains a type element such as int or string which contains the actual data. For example, here’s an array that contains four stock symbols, each of which is a string:
<array> <data> <value><string>RHAT</string></value> <value><string>SUNW</string></value> <value><string>ASKJ</string></value> <value><string>COVD</string></value> </data> </array>
Unlike arrays in C or Java, the elements of an XML-RPC array do not have to share the same type. Here’s an array that contains a string and two doubles:
<array> <data> <value><string>RHAT</string></value> <value><double>4.12</double></value> <value><double>4.25</double></value> </data> </array>
You can use an array element wherever you would use an int or string or other type element. For example, Example 2.9 is a request for four stock quotes:
Example 2.9. An XML-RPC request that passes an array as an argument
<?xml version="1.0"?> <methodCall> <methodName>getQuote</methodName> <params> <param> <value> <array> <data> <value><string>RHAT</string></value> <value><string>SUNW</string></value> <value><string>ASKJ</string></value> <value><string>COVD</string></value> </data> </array> </value> </param> </params> </methodCall>
The response might also contain an array with four prices, as shown in Example 2.10:
Example 2.10. An XML-RPC response document that returns an array
<?xml version="1.0"?> <methodResponse> <params> <param> <value> <array> <data> <value><double>4.12</double></value> <value><double>13.68</double></value> <value><double>1.93</double></value> <value><double>0.78</double></value> </data> </array> </value> </param> </params> </methodResponse>
In some sense this indicates an overloaded method since in one example the getQuote() method is taking a string and in another it’s taking an array of strings. However, this may not necessarily map to an overloaded method on the server. Indeed, it may not map to a method named getQuote() at all. This could all just be an illusion perpetrated by the server to provide an easy to understand client interface to a very differently organized system.
A struct is a collection of variables. For those readers who went straight to Java and never had the misfortune of struggling with C, a struct is a poor man’s class. It has fields but no methods, and all the fields are public.
An XML-RPC struct is represented by a struct element. Each member of the struct is represented by a member element. Each member element has a name child and a value child. For example, this struct lists a stock symbol and a limit price:
<struct> <member> <name>symbol</name> <value><string>RHAT</string></value> </member> <member> <name>limit</name> <value><double>2.25</double></value> </member> </struct>
As with arrays, you can use a struct anywhere you’d use one of the simple type elements such as int or string. For example, Example 2.11 is an XML-RPC request that represents a limit order. In a limit order, three values are required: the stock to buy, the price you’re willing to buy at, and the expiration date of the order.
Example 2.11. An XML-RPC Request that passes a struct as an argument
<?xml version="1.0"?> <methodCall> <methodName>bid</methodName> <params> <param> <value> <struct> <member> <name>symbol</name> <value><string>RHAT</string></value> </member> <member> <name>limit</name> <value><double>2.25</double></value> </member> <member> <name>expires</name> <value><dateTime.iso8601>20020709T20:00:00</dateTime.iso8601></value> </member> </struct> </value> </param> </params> </methodCall>
Responses can also contain structs. You’ll see an example of this in the next section when we talk about faults.
It’s not at all uncommon for a procedure call to fail. In Java this causes an exception. In XML-RPC this causes a fault. For example, getQuote() might fail if the client passes in a nonexistent stock symbol. A fault response is almost exactly like a successful response except that a fault element replaces the params element. The value of the fault is always a struct containing two members: faultCode, an int, and faultString, a string. Example 2.12 demonstrates a fault for an unknown stock symbol.
Example 2.12. An XML-RPC fault
<?xml version="1.0"?> <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value><int>23</int></value> </member> <member> <name>faultString</name> <value><string>Unknown stock symbol ABCD</string></value> </member> </struct> </value> </fault> </methodResponse>
When the server faults, the HTTP request still succeeds. The server still uses the 200 OK response code in the HTTP header. Other HTTP error codes like 404 Not Found or 500 Internal Server Error are returned only if something goes wrong with the request at the HTTP level, not in the XML-RPC invocation.
There’s no official DTD or schema for XML-RPC. Nonetheless, it’s straightforward to write one or both. Indeed such a DTD may be a more easily understandable description of what is and isn’t allowed than the prose specification.
Example 2.13 is a simple DTD for XML-RPC. It states that a methodCall contains one methodName and one params in that order, that a methodResponse contains one params or one fault, that a value element can contain an i4, int, string, datetime.iso8601, double, base64, struct, or array, and so forth.
Example 2.13. A DTD for XML-RPC
<!ELEMENT methodCall (methodName, params)> <!ELEMENT methodName (#PCDATA)> <!ELEMENT params (param*)> <!ELEMENT param (value)> <!ELEMENT value (i4|int|string|dateTime.iso8601|double|base64|struct|array)> <!ELEMENT i4 (#PCDATA)> <!ELEMENT int (#PCDATA)> <!ELEMENT string (#PCDATA)> <!ELEMENT dateTime.iso8601 (#PCDATA)> <!ELEMENT double (#PCDATA)> <!ELEMENT base64 (#PCDATA)> <!ELEMENT array (data)> <!ELEMENT data (value*)> <!ELEMENT struct (member+)> <!ELEMENT member (name, value)> <!ELEMENT name (#PCDATA)> <!ELEMENT methodResponse (params | fault)> <!ELEMENT fault (value)>
There are also many things this DTD does not say. For example, it does not say that the value inside a fault must be a struct or that each i4 element contains an integer between -2,147,483,648 and 2,147,483,647. DTDs cannot make statements such as these. Schemas, however, can.
This DTD is informative, not normative. There is no official DTD for XML-RPC. I just made this one up after reading the XML-RPC specification. You can include a document type declaration in your XML-RPC documents, but it’s much more common to leave it out as is the case for all the examples in this chapter. Furthermore many parsers used in practice for reading XML-RPC documents are not validating and will not consider the contents of any DTD.
Example 2.14 is a medium complex schema for XML-RPC. It says everything the DTD says and then some. All elements are strictly typed. In the case of string and boolean, the XML-RPC types don’t quite match the schema types so new, more restricted types were derived from the standard base types. In addition the complex types are context dependent. For instance, structs normally contain one or more members, but the struct inside a fault always contains exactly two members, one of which has the name faultCode and the other of which has the name faultString.
Example 2.14. A Schema for XML-RPC
<?xml version="1.0"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!-- The only two possible root elements are methodResponse and methodCall so these are the only two I use a top-level declaration for. --> <xsd:element name="methodCall"> <xsd:complexType> <xsd:all> <xsd:element name="methodName"> <xsd:simpleType> <xsd:restriction base="ASCIIString"> <xsd:pattern value="([A-Za-z0-9]|/|\.|:|_)*" /> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="params" minOccurs="0" maxOccurs="1"> <xsd:complexType> <xsd:sequence> <xsd:element name="param" type="ParamType" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:all> </xsd:complexType> </xsd:element> <xsd:element name="methodResponse"> <xsd:complexType> <xsd:choice> <xsd:element name="params"> <xsd:complexType> <xsd:sequence> <xsd:element name="param" type="ParamType"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="fault"> <!-- What can appear inside a fault is very restricted --> <xsd:complexType> <xsd:sequence> <xsd:element name="value"> <xsd:complexType> <xsd:sequence> <xsd:element name="struct"> <xsd:complexType> <xsd:sequence> <xsd:element name="member" type="MemberType"> </xsd:element> <xsd:element name="member" type="MemberType"> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> <xsd:complexType name="ParamType"> <xsd:sequence> <xsd:element name="value" type="ValueType"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="ValueType" mixed="true"> <!-- I need to figure out how to say that this is either a simple xsd:string type or that it contains one of these elements; but that otherwise it does not have mixed content --> <xsd:choice> <xsd:element name="i4" type="xsd:int"/> <xsd:element name="int" type="xsd:int"/> <xsd:element name="string" type="ASCIIString"/> <xsd:element name="double" type="xsd:decimal"/> <xsd:element name="Base64" type="xsd:base64Binary"/> <xsd:element name="boolean" type="NumericBoolean"/> <xsd:element name="dateTime.iso8601" type="xsd:dateTime"/> <xsd:element name="array" type="ArrayType"/> <xsd:element name="struct" type="StructType"/> </xsd:choice> </xsd:complexType> <xsd:complexType name="StructType"> <xsd:sequence> <xsd:element name="member" type="MemberType" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="MemberType"> <xsd:sequence> <xsd:element name="name" type="xsd:string" /> <xsd:element name="value" type="ValueType"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="ArrayType"> <xsd:sequence> <xsd:element name="data"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="ValueType" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> <xsd:simpleType name="ASCIIString"> <xsd:restriction base="xsd:string"> <xsd:pattern value="([ -~]|\n|\r|\t)*" /> </xsd:restriction> </xsd:simpleType> <xsd:simpleType name="NumericBoolean"> <xsd:restriction base="xsd:boolean"> <xsd:pattern value="0|1" /> </xsd:restriction> </xsd:simpleType> </xsd:schema>
This schema is informative, not normative. There is no official schema for XML-RPC. I just wrote this one up after reading the XML-RPC specification. You should not include xsi:noNamespaceSchemaLocation attributes in your XML-RPC documents, but you might be able to use some other extra-document means of attaching the schema. For instance, the Xerces‑J parser lets you set the http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation property to the location of the schema. However, very few XML-RPC services would be likely to support this. If you use a schema with XML-RPC, it should be used purely for validation, not for attaching default attributes to elements or anything else that might affect the document’s content.
This example does effectively demonstrate the relative power of DTDs and schemas. The schema identifies all possible, legal XML-RPC documents. Almost anything that satisfies this schema is a legal XML-RPC document, and anything that does not satisfy the schema is not a legal XML-RPC document. [1] The DTD only gets half that far. All documents that do not satisfy the DTD are not legal XML-RPC documents. However, not all documents that do satisfy the DTD are legal XML-RPC documents. Things the schema says that the DTD does not include:
The only two legal root elements are methodCall and methodResponse
A params element that’s a child of a methodCall can have any number of param child elements, but a params element that’s a child of a methodResponse element must have exactly one param child element (i.e. context dependent content models)
i4 and int elements must contain an integer between -2,147,483,648 and 2,147,483,647.
String values and method names can only use ASCII characters.
The value of a fault must be a struct with exactly two members.
There are other things the schema says that the DTD doesn’t, but this gives you the idea. Schemas are both more descriptive and proscriptive than DTDs.
[1] The three XML-RPC requirements that can't be specified in the W3C XML Schema Language are:
One of the two members in a fault struct must have the name faultCode and an int value and the other must have the name faultString and a string value.
A value element can contain either an ASCII string or a type element such as int, but not a type element and an ASCII string.
Copyright 2001, 2002 Elliotte Rusty Harold | elharo@metalab.unc.edu | Last Modified August 22, 2002 |
Up To Cafe con Leche |