Chapter 18 ■ rpC
332
In both cases, however, you can see that unambiguous textual representation has become the order of the day,
and it has replaced the older practice of sending raw binary data whose meaning had to be known in advance.
Of course, by this point you might be asking exactly what makes RPC protocols all that special. After all, the
choices I am talking about here—choosing a data format, sending a request, and receiving a response in return—are
not specific to procedure calls; but they are common to any meaningful network protocol! Both HTTP and SMTP,
to take two examples from previous chapters, must serialize data and define message formats. So again, you might
wonder: What makes RPC all that special? There are three features that mark a protocol as an example of RPC.
First, an RPC protocol is distinguished by its lack of strong semantics for the meaning of each call. Whereas HTTP
is used for the retrieval documents and SMTP supports the delivery of messages, an RPC protocol does not assign any
meaning to the data passed except to support basic data types like integers, floats, strings, and lists. It is instead up to
each particular API that you fashion using an RPC protocol to define what its calls mean.
Second, RPC mechanisms are a way to invoke methods, but they do not define them. When you read the
specification of a more single-purpose protocol like HTTP or SMTP, you will note that they define a finite number
of basic operations, like GET and PUT in the case of HTTP or EHLO and MAIL when you are using SMTP. But RPC
mechanisms leave it up to you to define the verbs or function calls that your server will support; they do not specify
them in advance.
Third, when you use RPC, your client and server code should not look very different from any other code that
uses function calls. Unless you know that an object represents a remote server, the only pattern you might notice
in the code is a certain caution with respect to the objects that are passed—lots of numbers, strings, and lists, but
typically not live objects like open files. However, while the kinds of arguments passed might be limited, the function
calls will “look normal” and not require decoration or elaboration in order to pass over the network.
Features of RPC
Besides serving the essential purpose of letting you make what appear to be local function or method calls, which
are in fact passing across the network to a different server, RPC protocols have several key features, and also some
differences, that you should keep in mind when choosing and then deploying an RPC client or server.
First, every RPC mechanism has limits on the kind of data you can pass. In fact, the most general-purpose RPC
mechanisms tend to be the most restrictive, because they are designed to work with many different programming
languages and thus can only support lowest-common-denominator features that appear in almost all of them.
The most popular protocols, therefore, support only a few kinds of numbers and strings; one sequence or list
data type; and then something like a struct or associative array. Many Python programmers are disappointed to learn
that only positional arguments are typically supported, because so few other languages at this point support keyword
arguments.
When an RPC mechanism is tied to a specific programming language, it is free to support a wider range of
parameters. In some cases, even live objects can be passed if the protocol can figure out some way to rebuild them on
the remote side. In this case, only objects backed by live operating system resources, like an open file, live socket, or
area of shared memory, become impossible to pass over the network.
A second common feature is the ability of the server to signal that an exception occurred while it was running
the remote function. In such cases, the client RPC library will typically raise an exception itself to tell the caller that
something has gone wrong. Of course live stack frames of the sort that Python makes available to an exception
handler typically cannot be passed back; each stack frame, after all, probably refers to modules that do not even exist
in the client program. But at least some sort of proxy exception that gives the right error message must be raised on the
client side of the RPC conversation when a call fails on the server.
Third, many RPC mechanisms provide introspection, which is a way for clients to list the calls that are supported
by that particular RPC service, and perhaps to discover which arguments they take. Some heavyweight RPC protocols
actually require the client and server to exchange large documents describing the library or API they support; others
just allow the client to fetch the list of function names and argument types; and other RPC implementations support
no introspection at all. Python tends to be a bit weak in supporting introspection because Python, unlike a statically
typed language, does not know which argument types are intended by the programmer who has written each
function.