Thứ Sáu, 13 tháng 6, 2014

What Is XML-RPC? [XML-RPC]

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 wacreated by
Dave Winer of UseLand 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
incominXML-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
dont 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 tthe 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:

$time  = xmlrpc('http://time.xmlrpc.com/RPC2',  array('currentTime.getCurrentTime' => array()));

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
User-Agent:   Drupal (+http://drupal.org/) Content-Length:   118
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

Lets 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:

$state_name  = xmlrpc('http://betty.userland.com/RPC2',  array('examples.getStateName' => array(3)));
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
requestsInside 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 whapoint 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 frobetty.userland.com without
giving the state number, which is a required parameter:
$state_name  = xmlrpc('http://betty.userland.com/RPC2', array('examples.getStateName'));
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:
$state_name  = xmlrpc('http://betty.userland.com/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 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. 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 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 othe
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. Lets 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:
specURL                http://www.xmlrpc.com/spec specVersion              1
faults_interop:
specURL                http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php specVersion       20010516
system.multicall
specURL               http://web.archive.org/web/20101015050132/http://www.xmlrpc.com/ discuss/msgReader$1208
specVerson             1
introspection
specURL                http://scripts.incutio.com/xmlrpc/introspection.html specVersion                                1

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