A remote procedure call is when one program asks another program to execute a function. XML-RPC is a standard for
remote procedure calls where the call is encoded with XML and sent over HTTP. The XML- RPC protocol was created by
Dave Winer of User Land Software in collaboration with Microsoft (see www.xmlrpc.com/spec). It’s specifically targeted at
distributed web-based systems talking to each other, as when one Drupal site asks another Drupal site for some information.
There are two players when XML-RPC happens. One is the site from which the request originates, known as the client.
The site that receives the request is the server.
Prerequisites for XML-RPC
If your site will be acting only as a server, there’s nothing to worry about because incoming XML-RPC requests use the
standard web port (usually port 80). The file xmlrpc.php in your Drupal installation contains the code that’s run for an
incoming XML-RPC request. It’s known as the XML-RPC endpoint.
For your Drupal site to act as a client, it must have the ability to send outgoing HTTP requests. Some hosting companies
don’t allow this for security reasons, and your attempts won’t get past their firewall.
XML-RPC Clients
The client is the computer that will be sending the request. It sends a standard HTTP POST request to the server. The body of this request is composed of XML and contains a single tag named <methodCall>. Two tags, <methodName> and
<params>, are nested inside the <methodCall> tag. Let’s see how this works using a practical example.
XML-RPC Client Example: Getting the Time
The site that hosts the XML-RPC specification (www.xmlrpc.com/) also hosts some test implementations. In our
first example, let’s ask the site for the current time via XML-RPC:
You’re calling Drupal’s xmlrpc() function, telling it to contact the server time.xmlrpc.com with the path RPC2, and to
ask that server to execute a method called currentTime.getCurrentTime(). You’re not sending any parameters along with the call.Drupal turns this into an HTTP request that looks like this:
POST /RPC2 HTTP/1.0
Host: time.xmlrpc.com
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
<methodName>currentTime.getCurrentTime</methodName>
<params></params>
</methodCall>
The server time.xmlrpc.com happily executes the function and returns the following response to you:
HTTP/1.1 200 OK Connection: close Content-Length: 183
Content-Type: text/xml
Date: Wed, 23 Apr 2008 16:14:30 GMT
Server: UserLand Frontier/9.0.1-WinNT
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
<dateTime.iso8601>20080423T09:14:30</dateTime.iso8601>
</value>
</param>
</params>
</methodResponse>
When the response comes back, Drupal parses it and recognizes it as a single value in ISO 8601 international date format. Drupa
then helpfully returns not only the ISO 8601 representation of the time but also the year, month, day, hour, minute, and second
components of the time. The object with these properties is assigned to the $time variable, as shown in Figure 19-1.
Figure 19-1. Result of XML-RPC call to get the current time
The important lessons here are as follows:
• You called a remote server and it answered you.
• The request and response were represented in XML.
• You used the xmlrpc() function and included a URL and the name of the remote procedure to call.
• The value returned to you was tagged as a certain data type.
• Drupal recognized the data type and parsed the response automatically.
• You did this all with one line of code.
XML-RPC Client Example: Getting the Name of a State
Let’s try a slightly more complicated example. It’s more complicated only because you’re sending a parameter along with the name of the remote method you’re calling. UserLand Software runs a web service at betty.userland.com that has the 50 US states listed in alphabetical order. So if you ask for state 1, it returns Alabama; state 50 is Wyoming. The name of the method is examples.getStateName. Let’s ask it for state number 3 in the list:
This sets $state_name to Arizona. Here’s the XML Drupal sends (we’ll ignore the HTTP headers for clarity from now on):
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value>
<int>3</int>
</value>
</param>
</params>
</methodCall>
Here’s the response you get from betty.userland.com:
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>Arizona</value>
</param>
</params>
</methodResponse>
Notice that Drupal automatically saw that the parameter you sent was an integer and encoded it as such in your request. But what’s happening in the response? The value doesn’t have any type tags around it! Shouldn’t that be <value><string>Arizona</string></value>? Well, yes, that would work as well; but in XML-RPC a value without a type is assumed to be a string, so this is
less verbose. That’s how simple it is to make an XML-RPC client call in Drupal—one line:
$result = xmlrpc($url, array($method => array($param_1, $param_2, $param_3...)), $options);
Handling XML-RPC Client Errors
When dealing with remote servers, much can go wrong. For example, you could get the syntax wrong; the server could be offline;
or the network could be down. Let’s take a look at what Drupal does in each of these situations.
Network Errors
Drupal uses the drupal_http_request() function in includes/common.inc to issue outgoing HTTP requests, including XML-RPC
requests. Inside that function, the PHP function fsockopen is used to open a socket to the remote server. If the socket cannot be
opened, Drupal will set either a negative error code or a code of 0, depending on which platform PHP is running on and at what point inopening the socket the error occurs. Let’s misspell the name of the server when getting the state name:
$method = 'examples.getStateName';
$state_name = xmlrpc($url, array($method => array(3))); if ($error = xmlrpc_error()) {
if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could not be opened.');
}
drupal_set_message(t('Could not get state name because the remote site gave an error: %message (@code).',
array('%message' => $error->message, '@code' => $error->code)));}
This will result in the following message being displayed:
Could not get state name because the remote site gave an error: Outgoing HTTP request failed because the socket
could not be opened. (-19891355).
HTTP Errors
The preceding code will work for HTTP errors, such as when a server is up but no web service is running at that path. Here, we ask drupal.org to run the web service, and drupal.org points out that there is nothing at http://drupal.org/RPC2:
$state = xmlrpc('http://drupal.org/RPC2', array('examples.getStateName')); if ($error = xmlrpc_error()) {
if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could not be opened.');}
drupal_set_message(t('Could not get state name because the remote site gave an error: %message (@code).',
array('%message' => $error->message, '@code' => $error->code)));
This will result in the following message being displayed:
Could not get state name because the remote site gave an error: Not Found (404).
Call Syntax Errors
Here’s what is returned if you can successfully reach the server but try to get a state name from betty.userland.com without
giving the state number, which is a required parameter:
The remote server returns the following:
<?xml version="1.0"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value>
<int>7</int>
</value>
</member>
<member>
<name>faultString</name>
<value>
<string>Can't evaluate because the name “0” hasn’t been defined.</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>
The server was up and our communication with it is fine; the preceding code is returned with an HTTP response code of 200 OK. The error is identified by a fault code and a string describing the error in the XML response. Your error-handling code would
be the same:
if ($error = xmlrpc_error()) { if ($error->code <= 0) {
$error->message = t('Outgoing HTTP request failed because the socket could not be opened.');}
drupal_set_message(t('Could not get state name because the remote site gave an error: %message (@code).',
array('%message' => $error->message, '@code' => $error->code)));
This code results in the following message being displayed to the user:
Could not get state name because the remote site gave an error: Can't evaluate the expression because the name
“0” hasn’t been defined. (7).
Note that when you report errors, you should tell three things: what you were trying to do, why you can’t do it, and additional
information to which you have access. Often a friendlier error is displayed using drupal_set_message() to notify the user, and a
more detailed error is written to the watchdog and is view able at Reports -> Recent log messages.
A Simple XML-RPC Server
As you’ve seen in the XML-RPC client examples, Drupal does most of the heavy lifting for you. Let’s go through a simple server
example. You need to do three things to set up your server:
1. Define the function you want to execute when a client request arrives.
2. Map that function to a public method name.
3. Optionally define a method signature.
As usual with Drupal, you want to keep your code separate from the core system and just plug it in as a module. So here’s a
brief module that says “hello” via XML-RPC. Create the sites/all/modules/ custom/remotehello/remotehello.info file:
name = Remote Hello
description = Greets XML-RPC clients by name.
package = Pro Drupal Development core = 7.x
Here’s remotehello.module:
<?php
/**
* Implements hook_xmlrpc().
* Map external names of XML-RPC methods to PHP callback functions.
*/
function remotehello_xmlrpc() {
$methods['remoteHello.hello'] = 'xmls_remotehello_hello'; return $methods;}
/**
* Greet a user.
*/
function xmls_remotehello_hello($name) { if (!$name) {
return xmlrpc_error(1, t('I cannot greet you by name if you do not provide one.'));}
return t('Hello, @name!', array('@name' => $name));}
Mapping Your Method with hook_xmlrpc()
The xmlrpc hook describes external XML-RPC methods provided by the module. In our example, we’re providing only one
method. In this case, the method name is remoteHello.hello. This is the name that requestors will use, and it’s completely
arbitrary. A good practice is to build the name as a dot-delimited string using your module name as the first part and a descriptive
verb as the latter part.
The second part of the array is the name of the function that will be called when a request for remoteHello.hello comes in. In
our example, we’ll call the PHP function xmls_remotehello_hello(). As you develop modules, you’ll be writing many functions.
By including “xmls” (shorthand for XML-RPC Server) in the function name, you’ll be able to tell at a glance that this function
talks to the outside world. Similarly, you can use “xmlc” for functions that call out to other sites. This is particularly good
practice when you’re writing a module that essentially calls itself.
When your module determines that an error has been encountered, use xmlrpc_error() to define an error code and a helpful string describing what went wrong to the client. Numeric error codes are arbitrary and application-specific.
Assuming the site with this module lives at example.com, you’re now able to send your name from a separate Drupal installation (say, at example2.com) using the following code:
$method_name = 'remoteHello.hello';
$name = t('Joe');
$result = xmlrpc($url, array($method_name => array($name)));
$result is now "Hello, Joe."
Automatic Parameter Type Validation with hook_xmlrpc()
The xmlrpc hook has two forms. In the simpler form, shown in our remotehello.module example, it simply maps an external
method name to a PHP function name. In the more advanced form, it describes the method signature of the method—that is,
what XML-RPC type it returns and what the type of each parameter is (see www.xmlrpc.com/spec for a list of types).
Here’s the more complex form of the xmlrpc hook for remotehello.module:
/**
* Implements hook_xmlrpc().
* Map external names of XML-RPC methods to callback functions.
* Verbose syntax, specifying data types of return value and parameters.
*/
function remotehello_xmlrpc() {
$methods = array();
$methods[] = array('remoteHello.hello', // External method name. 'xmls_remotehello_hello',
// PHP function to run. array('string', 'string'), // The return value's type,// then any parameter types.
t('Greets XML-RPC clients by name.') // Description.);return $methods;}
Figure 19-2 shows the XML-RPC request life cycle of a request from an XML-RPC client to our module. If you implement the
xmlrpc hook for your module using the more complex form, you’ll get several benefits. First, Drupal will validate incoming types
against your method signature automatically and return -32602: Server error. Invalid method parameters to the client if
validation fails. (This also means that your function will be pickier—no more automatic type coercion, like accepting the string
'3' if theinteger 3 is meant!) Also, if you use the more complex form of the xmlrpc hook, Drupal’s built-in XML-RPC methods
system.methodSignature and system.methodHelp will return information about your method. Note that the description you
provide in your xmlrpc hook implementation will be returned as the help text in the system.methodHelp method, so take care
to write a useful description.
Figure 19-2. Processing of an incoming XML-RPC request
Built-In XML-RPC Methods
Drupal comes with several XML-RPC methods enabled out of the box. The following sections describe these built-in methods.
system.listMethods
The system.listMethods method lists which XML-RPC methods are available. This is the response a Drupal site will give
when queried for which methods it provides:
// Get an array of all the XML-RPC methods available on this server.
$methods = xmlrpc($url, array('system.listMethods'));
The response from the server follows:
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
<array>
<data>
<value>
<string>system.multicall</string>
</value>
<value>
<string>system.methodSignature</string>
</value>
<value>
<string>system.getCapabilities</string>
</value>
<value>
<string>system.listMethods</string>
</value>
<value>
<string>system.methodHelp</string>
</value>
<value>
<string>remoteHello.hello</string>
</value>
</data>
</array>
</value>
</param>
</params>
</methodResponse>
The content of $methods is now an array of method names available on the server:
('system.multicall', 'system.methodSignature', 'system.getCapabilities', 'system.listMethods', 'system.methodHelp', 'remoteHello.hello').
system.methodSignature
This built-in Drupal XML-RPC method returns an array of data types. Listed first is the data type of the return value of the
function; next come any parameters that a given method expects. For example, the remoteHello.hello method returns a string and
expects one parameter: a string containing the name of the client. Let’s call system.methodSignature to see if Drupal agrees:
// Get the method signature for our example method.
$signature = xmlrpc($url, 'system.methodSignature', array('remoteHello.hello'));
Sure enough, the value of $signature becomes an array: ('string', 'string').
system.methodHelp
This built-in Drupal XML-RPC method returns the description of the method that is defined in the xmlrpc hook implementation
of the module providing the method.
// Get the help string for our example method.
$help = xmlrpc($url, 'system.methodHelp', array('remoteHello.hello'));
The value of $help is now a string: it greets XML-RPC clients by name.
system.getCapabilities
This built-in Drupal XML-RPC method describes the capabilities of Drupal’s XML-RPC server in terms of which specifications are
implemented. Drupal implements the following specifications:
xmlrpc:
faults_interop:
system.multicall
specVerson 1
introspection
system.multiCall
The other built-in method worth mentioning is system.multiCall, which allows you to make more than one XML-RPC method
call per HTTP request. For more information on this convention (which isn’t in the XML-RPC spec), see the following URL
(note that it is one continuous string):
Summary
After reading this chapter, you should
• Be able to send XML-RPC calls from a Drupal site to a different server.
• Understand how Drupal maps XML-RPC methods to PHP functions.
• Be able to implement simple and complex versions of the xmlrpc hook.
• Know Drupal’s built-in XML-RPC methods.
Không có nhận xét nào:
Đăng nhận xét