PI-RPC: A Platform-Independant Remote Procedure Call Architecture


Abstract

The introduction of Remote Procedure Call (RPC) services using XML as the encoding scheme simplified what was once a daunting aspect of distributed computing. However, the current work stands in a frozen state for the sake of compliance with existing software implementations. This proposal outlines a new model that builds on the existing basis of XML-RPC in a modular fashion, maintaining compatibility with the existing specification.

Contents:


1. Introduction

The application of XML methods and technologies to RPC development is not new. One of the underlying aspects of XML-- the platform independence offered by the representational syntax-- immediately suggests application in areas where architectural differences range from slight to significant. This is further aided by the ever-growing acceptance and embrace of XML technology by a diverse array of companies and vendors. As a result of this widespread sponsorship, XML applications and tools are widely available and easily accessible.

This proposal is not intended to ignore existing models and approaches already in use (see the next section). Rather, the intention within this document is to build upon the existing guidelines and specifications to define a more thorough framework. The basic premise started by the current XML-RPC effort is unchanged. That premise is:

The rest of this document will explain the proposed features, limitations and guidelines for this approach to RPC.

1.1. Existing Work in XML-RPC

Readers would benefit from familiarity with the existing specification for XML-RPC as presented at the UserLand XML-RPC Site. The ideas here are an extension of the UserLand specification, and thus by starting there one may get a more fundamental introduction to the concepts and topics.

This specification is not an attempt to compete directly with the Simple Object Access Protocol, or SOAP. The W3C group's technical recommedation on SOAP, http://www.w3.org/TR/SOAP/, outlines the current proposed specification. The page at Developmentor, http://develop.com/SOAP/, also provides some background information. The SOAP specification covers a wider range of services and applications. In fact, SOAP can be used as a transport/data model for implementing RPC. However, there is room for both specifications and PI-RPC provides a lighter-weight alternative, one that does not require name-space support in the underlying XML parser for example. This may well be a factor when selecting which technologies are applied to solving a given problem.

2. The Transaction Life-Cycle

The life-cycle of a PI-RPC transaction can be broken down into three primary parts, as illustrated in the following diagram:

PI-RPC Transaction Life Cycle

Figure 1. PI-RPC Transaction Life Cycle

This diagram should be sufficient to express both PI-RPC and XML-RPC transactions.

In this model, the roles and responsibilities are divided between three layers: client, server and transport. By careful design and implementation, the distinctions between these layers will greatly extend the flexibility of combined system. Understanding the life-cycle of a transaction will also help in defining the points at which errors (or faults) may occur. This will be of particular interest when defining the model for the expression and specification of error/fault messages.

3. The Transport Model

PI-RPC is based primarily on utilizing existing HTTP servers to enable the client/server connectivity. In doing so, the mechanism for initiating an RPC connection, transmitting the request and receiving the response is completely abstracted from the data model. Upon the HTTP layer may be added authentication and/or authorization, encryption or any other of the layers commonly associated with this protocol. Additionally, the array of existing software platforms may be taken into consideration when designing a server platform. However, transport need not be limited to HTTP. Other protocols that offer the same degree of seamless client/server interaction are viable candidates. HTTP will be presented first, as a reference, with other protocols following after the sections on request and response headers.

3.1. Using HTTP methods

In keeping with the intent to build on existing technology with little or no customization, marshalling a request on a HTTP-based server will use the HTTP POST method, rather than introducing a new method. The GET method is not inherently suited to this type of application, and the HEAD method should not be expected to return any relevant information beyond any PI-RPC-specific response headers. The URL portion of the request line should indicate a location which the server intuitively knows to associate the content with the RPC server-engine. No information from this path should be expected to be useful (or even available) to the server itself. No part of the path should be used as an indicator of methods/routines to call or data to transmit. Given that an RPC server is not expected to be following the Common Gateway Interface (CGI) model, there is no reason to expect any part of the CGI-centric environment to be present. Finally, the specification of the HTTP protocol level is specified. A sample request line may look like:

      POST /rpc_srv HTTP/1.0
      
POST /rpc_srv HTTP/1.1

3.2. The request headers

Regardless of the transport layer in use, the request line is followed by the headers that provide supplemental information to the server handling the request. These are ordinary headers (again, in keeping with the intent of the transport model) in the standard format, several of which are identical in meaning to traditional HTTP server usage. The headers that should be present for a request to a PI-RPC server are:

Host
The host to which this request was directed.
User-Agent
An identifying string for the client software initiating the connection.
Content-Type
This should generally be "text/xml".
Content-Length
The size, in bytes, of the message body. Some transport layers may provide alternatives to this.
PI-RPC-Version
A specification of the version of the PI-RPC standard to which this request was crafted. For the current specification, assume this value to be "1.0".

3.3. The response headers

The server response should be in the form of a standard HTTP response code, or something analogous based on the underlying transport mechanism. The transport portion of the server model is not responsible for communicating the results or status of the RPC server engine. Thus, if the request is for a valid URL, and an HTTP server encounters no internal error in marshalling the request to the appropriate handler, then the response would be a simple "200 OK". Other HTTP response codes are valid as appropriate for internal errors that the server may encounter. The headers that the user agent may reliably expect from the PI-RPC server are:

Content-Length
The size, in bytes, of the response body. Again, dependant on the transport layer, there may be alternatives offered to this.
Content-Type
This should generally be "text/xml".
PI-RPC-Server
The identifying string from the RPC server that handled the request. The name is chosen so as to not conflict with the Server header that may be attached to the response by the HTTP server itself.

Note that this header set is based on the response originating from the XML-RPC server itself. In the event of a low-level transport layer error, user agents are responsible for how to deal with the message body (which will likely not be an XML document).

3.4. Using a SMTP-based approach

One alternate approach to implementing the transport layer would be to use SMTP. The challenge in using this approach would come from implementing security independant of the actual RPC server engine itself. Using SMTP as an outer layer offers the same basic advantages that HTTP does; it is a simple, easily expressed and easily implemented protocol for which a wide range of tools are already available. Just as a RPC server built upon HTTP does not need to function as a fully-featured web server, a SMTP-based implementation would not have to be the same daemon that functions as a mail transport agent, either.

Using SMTP as a transport layer is both hampered and aided by the fact that the connection is not bi-directional. As a detriment, there is no active connection for the server to use when returning the results of the request. Alternately, some servers may be designed to queue requests and serve results based on some external schedule or factors. For these types of servers, an active connection is of no use, and a method by which to deliver the results at a later point is necessary instead.

3.5. Using a basic TCP/IP approach

There is no restriction against using raw TCP/IP as the transport method for PI-RPC communication. For embedded applications (or others where memory footprint is an issue), this may even be preferable to either a HTTP or SMTP approach. Using the headers as defined for HTTP should prove sufficient to control the session and communicate status between the client and server.

Ideally a server and client system built upon direct TCP/IP layers would be able to handle both TCP and UDP styles of communication, possibly even a mixture of the two.

3.6. Other methods

4. The Message Architecture

Central to the goals and purpose of this specification is the model by which the data and requests are formed prior to sending to the server, and with which the results and responses are formed when the server answers. The message architecture is responsible for outlining not only the means by which actual data (numbers, character sequences, etc.) is encoded, but the complete process of creating and understanding requests and responses.

4.1. The body of a message

Both requests and responses are expressed as well-formed XML documents. This simple requirement is the whole basis for the concept that is XML-RPC, and by extension PI-RPC. By making a layer over an established standard such as XML, the responsibility of verifying the integrity (and possibly the validity) of the message is delegated to an XML-aware tool of the developer's own choosing, rather than imposing any more arbitrary restrictions or limitations. As with the choice of transport, a reasonably broad base of tools currently exists that address all variety of XML applications.

4.2. The data model

In this context, the term data model is used to refer to the overall approach to representing an RPC transaction. There are three primary elements that are important here:

The third item may provide data as well, but responses are treated as a general item due to the wider variety of possible returns.

4.2.1. Method naming and specification

Before presenting the structure of data representation, the approach to naming and specifying methods will be addressed. In a more specific environment, there are fundamental differences between subroutines that act as methods (generally in the context of an object-oriented language or system) and those that are considered procedures. However, in the context of PI-RPC, there is no real need for such distinction. The term method is used as a generic reference to any sort of procedure, in keeping with existing references on the subject. From this point on, only the word "method" will be used.

Methods are named using a class-style hierarchical series of name-segments, separated by single periods ("."). This expression of class hierarchy, relationship and naming convention is consistent in some part with the majority of languages in current popular use. It is an almost exact match for some, including Java and Python. Each component of the name may consist of the typical alphanumeric set from ISO-8859-1 encoding, plus the underscore character. The first character of a name element may not be a numeral. The following table shows some examples:

Valid Invalid
system Obj::Server
Non-alphanumeric character
rpc2_apache 3_guys_serving
First character may not be numeric

Each method that a server provides should have a minimum of two such segments to the name. While there is no explicit upper-limit, it would likely be bounded by limitations on the complexity of the code itself. By requiring at least two elements, the risk of name clash is greatly reduced, and it becomes more clear where different areas of server functionality begin and end (see the section on server identity, below). As a general rule, choosing the initial name segment so as to identify the server and/or vendor to some degree is the ideal goal. Thus, a method called vendorA.newsContentDaily is clearly distinct from another method brandX.newsContentDaily.

[ Top of sub-section ]

4.2.2. Basic and complex data specification

The types of data that are represented in PI-RPC may be thought of as belonging to one of two groups: cardinal types, those which express a single base value such as an integer, string, etc., and compsite types, those such as arrays and structures that are built by combining or collating several cardinals into a single entity. As with all other things XML, data is represented textually, using container tags to quantify it.

Cardinals

The basic (cardinal) types in PI-RPC are:

Name Description Example
int or i4 Four-byte signed integer data <int>25</int>
<i4>-12</i4>
double A double-precision, signed floating-point number <double>3.14159</double>
string A character sequence (subject to the language encoding of the XML document), possibly zero-length <string>Hello, world</string>
<string></string>
boolean A zero (0) indicating false, or a one (1) indicating true <boolean>1</boolean>
dateTime.iso8601 A value representing time and/or date, using the ISO 8601 standard format. <dateTime.iso8601>
  20001205T16:37:55-07:00
</dateTime.iso8601>
base64 Based64-encoded binary data <base64>
  WE1MLVJQQyBTcGVjaWZpY2F0aW9u
</base64>

If a value is specified in an PI-RPC message without one of these container tags, it should be presumed to be string.

Composites

There are two types of composite data: The array and the struct.

An array item is simply a sequence of zero or more items. The items contained by the array are not required to all be of the same type, nor are they required to be cardinal types. An array may contain struct entities or even other array entities. The items in an array are ordered, but not otherwise distinguished from each other. The elements each have an implied index number that starts from zero as the first element and increases accordingly. An array is represented as:

      <array>
        <value><int>12</int></value>
        <value><string>Apple</string></value>
        <value><boolean>0</boolean></value>
        ...
      </array>
    

In keeping compatibility with existing XML-RPC implementations, the set of value containers may themselves be contained within a <data></data> pair. This is not required for this specification, however.

Conversely, the elements within a struct composite data type are not ordered, but are in fact given distinctive names by which they may be referred. These constructs are similar in nature and design to Python's dictionary data-type or Perl's hash table. As with the array type, the values referenced within a struct are not limited to cardinals. They may represent an array or another struct. The basic form of is:

      <struct>
        <member>
          <name>int_val</name>
          <value><int>12</int></value>
        </member>
        <member>
          <name>string_val</name>
          <value><string>Apple</string></value>
        </member>
        <member>
          <name>bool_val</name>
          <value><boolean>0</boolean></value>
        </member>
        <member>
          <name>struct_val</name>
          <value>
            <struct>
              ...
            </struct>
          </value>
        </member>
        ...
      </struct>
    

The same rules apply to naming the members of a struct as apply to the names of methods.

[ Top of sub-section ]

4.2.3. Method signatures

Some current programming languages handle method parameters in a flexible fashion allowing for trivial promotion and demotion between cardinal and composite data types. This feature (often the source of strong debate) is not universally available, and thus PI-RPC requires calls to methods to conform to known signatures, depending on the method actually being called.

A method signature is a simple representation of the number and type of parameters that the method call expects. It includes the promised type of the return value as the first specified element. A method is not limited to a single signature. There may be as many signatures as the developer wishes to support, though identical signatures should not be expected to yield different results. There is no means by which to specify this. Also, a method must at the very least return some sort of value. The value may be as simple as a boolean, and may not actually be used. But in order to prevent the possibility of redundant signatures, there must be at least one element present, and the first element is always considered to be the return type of the method. Some sample signatures and their explanations:

Signature Meaning
int The method takes no arguments, and returns an int.
int int double The method takes an int and double, in that order, and returns an int.
struct string string Method takes two string parameters, and returns a struct.
  Invalid; a signature must have at least one element.
struct array int The method takes an array and an int, returning a struct. Since there is no promotion of the trailing cardinal value to being part of the list (as a language such as Perl would do), the receiving method must be expecting this exact signature, not just an array as a single input parameter.

It is not the role of the method signature to specify any boundary limitations for array parameters, or specific members for struct parameters. These should be addressed in the method and/or API documentation.

When a method offers more than one signature, the signatures may be ranked by the specificity of the argument lists. Such a ranking only serves to establish a consistent means by which to order an otherwise unordered list. Since the PI-RPC specification does not allow for promotion or demotion of data types, matching of parameters to signatures is still a case of exact or not at all. There is no need for the ability to judge the "better" of two signatures to apply to a given argument series. The sorting system is simple: cardinal types are considered the most specific, while struct types are next and array types are considered the least specific. The list of available signatures is compared one element at a time, such that the number of arguments is only relevant when the first n are identical in weight. The following table is in sorted order. Note that signatures that are exactly the same level of specificity are considered equal, and thus any order within that subset is acceptable:

Signature Relative Rank Explanation
int 1 A simple method with no arguments, returning a cardinal
struct 2 Return a struct, but takes no arguments
struct int 3 Slightly higher ranking due to argument
struct string Same ranking as the previous-- no implicit order between these two
struct array 4 Higher still, since the argument type is higher than the previous two
array 5 Again, despite no arguments to the method, this is ranked higher
array int 6 This would be the highest of the examples here

[ Top of sub-section ]

4.2.4. The structure of requests

A request follows the PI-RPC Document Type Description. Two possible top-level container tags are allowed, with one being a composite of the other. Thus, explanation will begin with the base-level element, the methodCall tag.

A request to execute a single remote method starts with the <methodCall></methodCall> tag-pair. This pairing is valid as the top-level tag of the entire XML document. The requesting client must then specify what method is to be executed, and provide any data arguments that should be passed to the method. Below is a simple example of a request body:

      <?xml version="1.0"?>
      <!DOCTYPE methodCall SYSTEM "pi-rpc.dtd">
      <methodCall>
        <methodName>system.methodHelp</methodName>
        <params>
          <param>
            <value><string>system.listMethods</string></value>
          </param>
        </params>
      </methodCall>
    

The above is a valid, simple request that conforms to both this standard and the existing XML-RPC specification cited earlier. It requests that a method named system.methodHelp be executed. The method will receive a list of parameters with exactly one item: a string value of "system.listMethods". As is covered later, both of these names are recommended pre-defined methods allowing clients to derive basic server information autonomously.

The methodCall tag may contain either or both of exactly two other tags: methodName and params. The methodName tag is considered unnecessary in this specification (see below). It specifies the method to call, using the naming conventions and limitations outlined earlier in the section on naming methods. In this specification, it is considered preferable to provide the method name by using the name attribute of the methodCall tag:

      <methodCall name="system.methodHelp">
      ...
    

This offers two advantages: first, it simplfies parsing of the document in that the methodName tag is already a very narrowly-defined value. There is no benefit to expressing the value with the flexibility that a broad-spectrum container tag offers. XML parsers are just as efficient in their handling of element attributes. Secondly, in using this approach the method name itself is made known to the parsing engine at an earlier stage. While the difference may be negligible for smaller servers with simple interfaces, having the name readily available at the point in parsing that the methodCall initialization is taking place may open the door for optimizations, depending on the architecture of the server implementation.

The params container is more involved. It is used to contain one or more occurances of <param></param> containers. It cannot be empty; the params container itself may be omitted if the purpose is to express an empty argument list. In turn, each of the param tags must contain exactly one <value></value> pairing. This specification of a value is identical to that used in the delimiting of values contained in array and struct composite types. That is to say, the value within these containers may be any of the defined cardinal or composite types. It may also be ordinary character text data, in which case the absense of a type-defining container will cause it to be treated as string data.

Initiating a completely new connection for each XML-RPC request can easily become tedious, and could represent a significant slow-point in an application. It is for this reason that the PI-RPC specification supports the notion of the methodCallSet. This top-level tag allows for the client to send one or more method calls in a single request, without counting on HTTP keep-alive functionality or other technologies that may not be universally available (or worse, implemented differently from one server to the next). Syntactically, it only introduces the capability of including more than one methodCall block within the containing tags. Functionally, there is more involved, and more options available to the client developer as a result..

The methodCallSet has a single attribute, "serial", that is used to indicate whether the set of methods must be evaluated in the exact order that they are specified in. Since this is likely the default, this attribute defaults to "yes". In this case, the response (see the next section) will adhere to the same ordering. Nothing else need be done. In many cases, there is no reason to consider an unordered set. But relaxing the ordering would allow servers that have the option to further distribute their work among several identical machines, to run the methods using some degree of parallelism. Each methodCall block within a methodCallSet is itself a correctly-formed XML document that conforms to the DTD (assuming that it was successfully parsed initially, of course). Then, a meta-server would have the option of breaking a set of method calls up into the same number of individual, single-method-call messages for distribution to back-end servers.

To do this, it is necessary to tag each methodCall with a unique identifier, so that the server can return data that the client may correctly associate with the originating request. To accomodate this, the methodCall tag supports an attribute called "sequence". The contents of the attribute may be any string of character data that is valid as an XML identifier (the same limitation given to struct-member names and method names). Additionally, each must be unique, and if one method has a sequence attribute, then all should have them (there is an exception to this, explained below). If the methodCallSet is marked as not being serial (serial="no"), then in that case all of the method declarations do in fact require sequence attributes.

Uniquely tagging a specific method call in a methodCallSet provides another benefit. The return value from one method call may be directly used in a subsequent call. This can only be done if the first call is tagged with a sequence attribute. By allowing a special variant into the <value> container to refer to a given sequence, complex composite method-call-chains may be constructed. Available only to the the requests side, the container tag <reference></reference> is used for this purpose. The content specified within it must exactly match a sequence tag in the current methodCallSet. For example:

      <?xml version="1.0"?>
      <!DOCTYPE methodCallSet SYSTEM "pi-rpc.dtd">
      <methodCallSet>
        <methodCall name="system.listMethods" sequence="_aaa">
          <params>
            <param>
              <value><string>system.</string></value>
            </param>
          </params>
        </methodCall>
        <methodCall name="system.methodHelp">
          <params>
            <param>
              <value><reference>_aaa</reference></value>
            </param>
          </params>
        </methodCall>
      </methodCallSet>
    

In this example, the client asks the server first for a list of methods it provides whose names contain the substring system.; the resulting list is then passed onward to a second call which can take an array like the one returned by the first call. The response (barring errors) would be the first method's result, a list of methods, followed by the second method's result, a list of the help-strings for those methods. This example also illustrates why a client might code a request wherein not all of the methodCall instances have sequence attributes provided. In this case, the two cannot be executed independently of each other, since the latter requires the result from the former. Since the default behavior is to consider the list ordered and thus process it serially, this meets the requirements. But in larger cases, there is no need to force serial behavior on a server designed to support parallel execution.

The methodCall tag has yet one more attribute to offer, called "dependancy". This is a list of one or more references upon which the current method call is reliant in order to fill in its own argument list. The sequence values are separated by spaces, and order is not important. They exist only to inform the server that when scheduling the current methodCallSet, it must do so such that the providing methods are executed before the dependant one is. This is best illustrated by extending the earlier example:

      <?xml version="1.0"?>
      <!DOCTYPE methodCallSet SYSTEM "pi-rpc.dtd">
      <methodCallSet serial="no">
        <methodCall sequence="_aaa">
          ...
        </methodCall>
        <methodCall sequence="_aab">
          ...
        </methodCall>
        <methodCall name="system.listMethods" sequence="_aac">
          <params>
            <param>
              <value><string>system.</string></value>
            </param>
          </params>
        </methodCall>
        <methodCall name="system.methodHelp" sequence="_aad"
          dependancy="_aac">
          <params>
            <param>
              <value><reference>_aac</reference></value>
            </param>
          </params>
        </methodCall>
        ...
      </methodCallSet>
    

In this example, there are two calls prior to the system.listMethods call, and likely several more following the system.methodHelp call. It is possible that no others are referenced in any other parts. However, all methodCall tags have sequence attributes. Additionally, the call that requires the back-reference specifies to the server that it is dependant upon the value yielded by the methodCall tagged as "_aac". Correctly implementing such scheduling, particularly in very large cases, is not trivial. Because of this, servers are not expected to provide such parallel scheduling. As a result, it is necessary for the client to ensure that any methodCall that requires a back-reference from another call occurs after its dependancy in the methodCallSet. In cases of extreme dependancies, it may still be necessary to make more than one call from the client to the server.

[ Top of sub-section ]

4.2.5. The structure of responses

A response also follows the PI-RPC Document Type Description. Responses are more complex in general, as the client must allow for both success and failure, and react accordingly. Referring to the section on server response, the underlying HTTP server is not necessarily the party that will inform a client when there has been an error. If the communication of the request, handling of the payload, and return of the response executed without any problems with the HTTP server, then there is nothing out of the ordinary for that layer to report. The responsibility to report RPC errors lies within the RPC server.

Setting that aside for the time being, the structure of a response looks very similar to that of a request. It may have one of two top-level tags, where one is just a composite of the other.

The core element for a response is the methodResponse tag. Like methodCall, this wraps exactly one response to exactly one request. A simple example:

      <?xml version="1.0"?>
      <!DOCTYPE methodResponse SYSTEM "pi-rpc.dtd">
      <methodResponse>
        <param>
          <value><int>42</int></value>
        </param>
      </methodResponse>
    

In maintaining compatibility with earlier drafts of XML-RPC, the single <param></param> container may be enclosed by an outer <params></params> container. This is not necessary, and should be considered deprecated. Additionally, the response may omit the param container entirely, specifying the value directly.

The methodResponse tag allows for two attributes: name and sequence. Both of these correspond to similar attributes from the originating methodCall construct. The name attribute is wholly optional, allowed for client/server relationships where tracking such information may be of use. It should reference the name of the method that was called to generate this particular response. The second attribute is only required if the request included a sequence value on the methodCall tag. In that case, the attribute is required on the response, so that the client may reliably match the response to the request. This will become clearer when response sets are covered. As with requests, there may be a mix of tagged and un-tagged responses. Unlike requests, the sequence values are not used anywhere within a response to implement backwards-references.

When there is an error in the execution of the method, the server must report this within the confines of the methodResponse construct. To signal this to the client, the tag fault is used. This container tag denotes that the result of the operation was an error (or an exception, in object parlance). Within the fault tag should be a single value tag, the contents of which are a struct. The struct should have two member specifications: one called "faultCode" of type int, and the other called "faultString" of type string. While the flexibility of the struct allows for the server to pass additional information, this is not recommended at this point, since the prior specification for XML-RPC limited the fault report to only these two parts. However, this specification does not impose such a limitation. Such extension should probably hinge on compatibility with known user agents. An example of an error response:

      <?xml version="1.0"?>
      <!DOCTYPE methodResponse SYSTEM "pi-rpc.dtd">
      <methodResponse sequence="_aaa">
        <fault>
          <value>
            <struct>
              <member>
                <name>faultCode</name>
                <value><int>101</int></value>
              </member>
              <member>
                <name>faultString</name>
                <value>
                  <string>Unknown method: system.listMethods()</string>
                </value>
              </member>
            </struct>
          </value>
        </fault>
      </methodResponse>
    

For a more specific treatment of common errors that servers and clients alike should understand, see the next sub-section, the error and exception models.

When the request is a methodCallSet, the response must use the tag, methodResponseSet. This tag allows for the combining into one response all the individual response values from the individual requests. Like the other tag, this one supports an attribute called "serial", used to denote whether the responses are guaranteed to follow the same order as the original set of requests. Also akin to methodCallSet, a specification of "serial='no'" means that all methodResponse tags must have a sequence tag. A collected response might be:

      <?xml version="1.0"?>
      <!DOCTYPE methodResponse SYSTEM "pi-rpc.dtd">
      <methodResponseSet>
        <methodResponse name="system.listMethods" sequence="_aaa">
          <value>
            <array>
              <value>
                <string>system.methodSignature</string>
              </value>
              <value>
                <string>system.methodHelp</string>
              </value>
              ...
            </array>
          </value>
        </methodResponse>
        <methodResponse name="system.methodHelp">
          <value>
            <array>
              <value>
                <string>[ info for system.methodSignature ]</string>
              </value>
              <value>
                <string>[ info for system.methodHelp ]</string>
              </value>
              ...
            </array>
          </value>
        </methodResponse>
      </methodResponseSet>
    

Note that this example is written to match one of the request examples above. In collected responses, it is very possible that some method calls may succeed while some others generate faults. The client is responsible for handling such situations.

[ Top of sub-section ]

4.3. The error and exception models

Due to the separated nature of the PI-RPC server with regards to the transport provider (the two may be tightly integrated, or may be completely separate processes), it is necessary to completely encapsulate the reporting of errors within the response body the server returns to the client. The basic mechanics of this were explained above (the fault tag and related details), but in this section will be discussed the associate of specific faults (or classes of faults) with particular types of failures. As with the blurred distinction between object methods and ordinary procedures, this section treats the concepts of errors and exceptions as functionally identical. While there are differences between the concepts, the differences are largely language-specific, and as such do not apply in the RPC sense.

In the above diagram, not all of the boxes represent points at which a RPC error case would be feasible. Both boxes in the Client Layer division represent the user agent. How the user agent, be it library or application, reports problems is up to the developer of that component. Errors there would be either due to communication problems in contacting the server, or syntactical issues when decoding the response. These represent topics not relevant to this specification.

The middle division represents the transport layer itself. Errors here would be HTTP-oriented. If the error is on the client-to-server stage of the cycle, then it falls to the user agent to deal with and report it. If it falls on the server-to-client side of things, then the server isn't able to reach the client anyway, limiting it to only local logging or some similar approach. The user agent would only be aware that the request never yielded a response.

Within the last division, any of the stages may be the source of a fault report. This makes sense when it is taken into consideration that such responses may only be generated by the server to begin with. The points along the way, in order, at which an error may be signaled are:

  1. Entry into the first box. This could mean that the server is refusing to accept the request (failed authentication, blackout period, etc.).
  2. Execution of the first box. This would mean a decoding failure when parsing the request itself.
  3. Execution of the second box. This could mean an error or exception raised by the method itself, a mis-matched signature, or even that the requested method is unknown to the server.
  4. Execution of the third box. Should the method produce data output that somehow thwarts efforts by the encoder to translate it into XML, an error would be raised at this point.

To implement this, the model for reporting errors (or exceptions) is based on a simple, widely-accepted standard of expressing them as a combination of a numerical (integer) code coupled with a descriptive text string (whose encoding matches the encoding of the response document itself). The approach to these error cases needs to be flexible enough to allow not only an intuitive set of basic errors, but also to permit server developers to expand upon the list with their own errors and codes, which fit logically within the existing structure.

In a purely object-oriented environment, this could be achieved through classes, sub-classing and inheritance. This is not a viable option, given the limitations of PI-RPC. Instead, the proposal is to constrain the numerical error codes such that the value therein reflects the area where the fault originated. This leaves the text portion available for more flexibility in expression. Borrowing from the HTTP response code model:

Range Group Meaning
100 - 199 Pre-Accept Errors in this category would be those covering cases where the server is refusing to accept the request for processing at all. This allows for the server to signal such an event even if the HTTP transport layer would otherwise report a successful tranmission to the client.
200 - 299 Decode Request This group reflects errors encountered when the server is unable to fully understand the request body and convert it to a native format prior to dispatch. XML syntactic errors, as well as any internal issues that arise during the decoding process would fall under this heading.
300 - 399 Dispatch Request If the error stems from the dispatch of the method, either due to an inability to dispatch or due to an error reported from the method itself, it will be covered by this category. In general, the method would not itself construct the error response, but rather the server would incorporate any feedback the method provided when constructing the response.
400 - 499 Encode Response The last of the clearly-defined group of errors would cover cases in which the server was unable to correctly encode the successful method response into a success-oriented response message for the client. Reasons for this could include methods returning data that does not conform to expected standards, or internal server issues at this stage in the transaction lifecycle.
500 Error This would be a generic catch-all error code for anything not adequately described by any of the previous categories.

In addition to the table above, this specification offers the following set of errors as a basis for consistency across server implementations:

Code Text
100 Server is not accepting requests
200 Invalid XML content in request
300 Requested method is unknown
301 Requested method has no matching signature
400 Server unable to encode method response
500 Internal Server Error

The messages given above are meant as examples, to clearly define the numerical codes. Actual messages sent should adhere to the language-encoding of the outgoing document.

As covered in the section on the structure of responses, the expression of an error in PI-RPC is done via the return of a struct value, containing two members (name faultCode and faultString). The original XML-RPC specification limits the structure to these two members. This specification does not, it only requires that both of those actually be present. If a server wishes to attach additional information on additional members, it is free to do so.

5. Goals of the Specification

The purpose of this document has not been to impose an inflexible standard to address the problem set at hand. Rather, the flexibility of XML offers an incredible potential for the future of RPC development. The specific goals that this document hopes to set forth are:

6. Conclusion

Appendix A. Credits

The primary basis for this document is the existing XML-RPC specification at the UserLand XML-RPC Site.

The concept of the system.* namespace as a standardized set of server features, and much of the suggested standardized methods are taken from the PHP classes for XML-RPC, as documented and developed at usefulinc.com. The concept is also used with effectiveness by O'Reilly Network's Meerkat open API.

Appendix B. References

  1. Extensible Markup Language (XML) 1.0 (Second Edition)
  2. Simple Object Access Protocol (SOAP) 1.1
  3. Scarab: Open Source Communications Framework
  4. A Summary of the International Standard Date and Time Notation, by Markus Kuhn