Performing Remote Procedure Calls With PEAR XML_RPC

      Comments Off on Performing Remote Procedure Calls With PEAR XML_RPC

Remote Control
Identity Crisis
Value Added
A Simple Request
Service With A Smile
Meeting Bugs
Not My Type

Fault-y Towers
Access Granted
About the Author

Remote Control

If you’re familiar with C or C++, you’ve probably already heard of Remote Procedure Calls
– they’re the framework by which procedures on a server are remotely executed by other
clients on the network. RPCs are interesting, because they make it possible to design
services that can be used independently of the client’s operating environment. So long as
both client and server understand the RPC protocol, they can communicate with each other
to execute commands and process return values.

Now, you might not know this, but your favorite language and mine, PHP, has been shipping
with support for XML-based Remote Procedure Calls since PHP 4.1.0. This support makes PHP
ideal for developers looking to design the next generation of cutting-edge Web services,
and to create network-based applications using existing, widely used protocols like XML
and HTTP.

The catch? Not too many people know how to do it.

That’s where this tutorial comes in. Over the next few paragraphs, I’ll be demonstrating
how you can use PHP, in combination with the ultra-cool PEAR XML_RPC package, to add
powerful new capabilities to your Web applications. Keep reading.

Identity Crisis

Before getting into the code, a few words about XML-RPC. According to the primer
available on the official XML-RPC Web site (http://www.xmlrpc.com/), XML-RPC is:

If that was a little too geeky for you, allow me to simplify things. XML-RPC is a
client-server paradigm that builds on existing Internet technologies to simplify the task
of invoking procedures and accessing objects across a network. It uses XML to encode
procedure invocations (and decode procedure responses) into a package suitable for
transmission across a network, and HTTP to actually perform the transmission.

At one end of the connection, an XML-RPC server receives XML-RPC requests containing
procedure calls, decodes them, invokes the named functions and packages the responses
into XML-RPC response packets suitable for retransmission back to the requesting client.
The client can then decode the response packets and use the results of the procedure
invocation in whatever manner it chooses. The entire process is fairly streamlined and,
because of its reliance on existing standards, relatively easy to understand and use.

Here’s a quick example of what a XML-RPC request for the remote procedure
getFlavourOfTheDay() might look like:


<?xml version="1.0"?>

<methodCall>

 <methodName>getFlavourOfTheDay</methodName>

 <params>

  <param>

   <value><string>Tuesday</string></value>

  </param>

 </params>

</methodCall>


And here’s what the response might look like:


<?xml version="1.0"?>

<methodResponse>

 <params>

  <param>

   <value><string>blueberry</string></value>

  </param>

 </params>

</methodResponse>

In this case, XML is used to format a request for the remote procedure
getFlavourOfTheDay(), and HTTP is used to transfer the request from the
client to the server. At the server, the XML-formatted request is decoded, the procedure
is executed with the given parameter, and the return value is re-encoded back into XML
and transmitted to the client. The client then decodes the XML, extracts the return value
and uses it as needed.

If you’re new to XML-RPC, the information above should be sufficient to explain the basic
concepts and ensure that you can follow the material that comes next; however, I’d
recommend that you read the official XML-RPC specification, available at
http://www.xmlrpc.com/spec.

Value Added

The XML_RPC package is a PEAR package that provides an API for PHP developers to encode
and decode XML-RPC requests and responses, and to build XML-RPC clients and servers. It’s
currently maintained by Stig Bakken and Daniel Convissor, and is freely available from
http://pear.php.net/package/XML_RPC.

The XML_RPC package consists of a number of different objects, each serving a specific
purpose. The most fundamental object, however, is the XML_RPC_Value()
object, which is used to represent XML-RPC data values. This object supports all the
simple and complex types outlined in the XML-RPC specification. To see how it works,
install the package and then copy, paste and run the following script:


<?php

// include class

include ('XML/RPC.php');

// string

$value = new XML_RPC_Value("gargantuan gobbling geese", "string");

echo $value->serialize();

?>


When you view the output of the script, you should see something like this:

The output above is the XML-RPC representation of the string “gargantuan gobbling geese”.
It has been created by instantiating an object of the XML_RPC_Value() class,
and passing the class constructor two arguments: the value to be encoded, and the type of
data it is to be encoded as.

The XML-RPC specification supports a number of different data types, both simple and
complex. The following example demonstrates the simple (or scalar) types:


<?php

// include class

include ('XML/RPC.php');

// string

$value = new XML_RPC_Value("gargantuan gobbling geese", "string");

// int

$value = new XML_RPC_Value("2003", "int");

// float

$value = new XML_RPC_Value("12.67", "double");

// boolean

$value = new XML_RPC_Value("1", "boolean");

// datetime

$value = new XML_RPC_Value("20050102T22:12:00", "dateTime.iso8601");

// base64

$value = new XML_RPC_Value("1", "base64");

?>

The XML-RPC representation of each value can be obtained by calling the object’s
serialize() method, while the value itself can be retrieved with the
scalarval() method. The following listing illustrates:


<?php

// include class

include ('XML/RPC.php');

// set float value

$value = new XML_RPC_Value("12.67", "double");

// prints "<value><double>12.67</double></value>"

echo $value->serialize();

// prints "12.67"

echo $value->scalarval();

?>

A Simple Request

Now, while all this futzing about with values is very amusing, it begs the question: what
use are these XML_RPC_Value() objects anyway?

To answer that question, you need to meet two other objects in the XML_RPC pantheon: the
XML_RPC_Message() object, and the XML_RPC_Response() object.
The XML_RPC_Message() object represents a procedure invocation by the
client. When initializing this object, the object constructor must be passed two
arguments: the name of the remote procedure, and the arguments to it (as an array of

XML_RPC_Value() objects). Take a look at this next script, which illustrates
by creating a message packet invoking the procedure setYear(2005):


<?php

// include class

include ('XML/RPC.php');

// create value

$value = new XML_RPC_Value("2005", "int");

// create message packet

$msg = new XML_RPC_Message("setYear", array($value));

// encode into XML-RPC-compliant format

echo $msg->serialize();

?>

Here’s the output:

Typically, the XML_RPC_Message() object is used at the client end of the
connection, to create and encode an XML-RPC request. The corresponding server response is
represented by a second object, the XML_RPC_Response() object, which is
illustrated in the listing below:


<?php

// include class

include ('XML/RPC.php');

// create value

$value = new XML_RPC_Value("-1", "int");

// create response packet

$resp = new XML_RPC_Response($value);

// encode into XML-RPC-compliant format

echo $resp->serialize();

?>

In this case, the object constructor is passed the return value of the procedure, again
as an XML_RPC_Value() object. Here’s the output:

The answer to the question posed at the top of this page should now be self-evident. As
the two examples above illustrate, XML_RPC_Value() objects are a crucial
part of the client-server transaction, because procedure arguments and return values are
encoded as XML_RPC_Value() objects by the client and server respectively.
Encapsulating data (and data types) in XML help the agents at either end of the
connection to transfer data back and forth without affecting its integrity.

Service With A Smile

With the basics out of the way, let’s now turn our attention to the meat of the XML_RPC
package – the Server and Client objects. Consider the following code, which demonstrates
the process of creating an XML-RPC server:


<?php

// include server class

include ('XML/RPC/Server.php');

// create server

// set up dispatch map

$server = new XML_RPC_Server(array("whoAmI" => array("function" => "phpWhoAmI")));

?>

With the PEAR XML_RPC class, creating an XML-RPC server is pretty simple – all you need
to do is initialize an object of the XML_RPC_Server() class, and pass the
object constructor what geeks call a dispatch map. Essentially, a dispatch map
maps the server’s public XML-RPC procedures to private PHP functions; every time an
XML-RPC procedure is called by a client, the server consults the dispatch map to see
which PHP function it should invoke to handle the call.

In the example above, the XML-RPC server exposes a single procedure –
whoAmI() – which is internally mapped to the function

phpWhoAmI(). This PHP function is responsible for parsing the XML-RPC
request packet, extracting the procedure arguments from it, performing its calculations,
and encoding the return value into a response packet suitable for transmission to the
server. Let’s take a closer look, to see how all these tasks are accomplished:


<?php

// include server class

include ('XML/RPC/Server.php');

// create server

// set up dispatch map

$server = new XML_RPC_Server(array("whoAmI" => array("function" => "phpWhoAmI")));

// function to parse arguments and return them to caller in different format

function phpWhoAmI($params) {

    
$name = $params->getParam(0);

    
$species = $params->getParam(1);

    $age = $params->getParam(2);

    $response = "I am " . $name->scalarval() . ", a " . $age->scalarval() . "-year old " . $species->scalarval();

    // return response to client

    
return new XML_RPC_Response(new XML_RPC_Value($response, "string"));

}

?>

From the above, it is clear that three arguments are required by the
phpWhoAmI() function – a name, a species type, and an age. These three
arguments are extracted as XML_RPC_Value() objects from the request packet
via the getParam() method, and their values are retrieved via each object’s
scalarval() method. Once retrieved, the arguments are interpolated into a
longer string, which is then repackaged as an XML_RPC_Response() object, and
returned to the caller.

Save the above script in your toplevel web-accessible directory (usually htdocs) as
myserver.php before continuing.

Meeting Bugs

So that takes care of the server – now how about the client? Well, there’s a
corresponding XML_RPC_Client() object, which takes care of sending an
XML-RPC request to the server and decoding the response. Take a look:


<?php

// include base functions

include ('XML/RPC.php');

// initialize client

$client = new XML_RPC_Client("/myserver.php", "localhost", 80);

// turn on debugging

// $client->setDebug(1);

// generate XML-RPC message packet

// contains RPC method name and arguments

$msg = new XML_RPC_Message("whoAmI", array(

                new
XML_RPC_Value("Bugs Bunny", "string"),

                new XML_RPC_Value("rabbit", "string"),

                new
XML_RPC_Value("18", "int")));

// send request

$response = $client->send($msg);

// read response

if (!$response->faultCode()) {

    
// no error

    // print response

    
$value = $response->value();

    print
$value->scalarval() . "\n";

    }

else {

    // error

    // print error message and code

    
print "Error code: " . $response->faultCode() . " Message: " . $response->faultString() . "\n";

}

?>

When initializing an XML_RPC_Client() object, the object constructor must be
passed three parameters: the path to the server script, the host name of the server, and
the port number. These values are used to POST the XML-RPC request packet to
the appropriate host.

Once the object has been initialized, an XML_RPC_Message() object is
created, with the remote procedure name as the first argument and an array of parameters
to be passed to it as the second. The XML_RPC_Client() object’s
send() method is then used to transmit the message packet to the server, and
a response object is generated from the server’s response. If an error occurred, the
response object will contain a fault, which can be retrieved via the faultCode()
and faultString() methods. If no error occurred, the response object will
contain the return value of the remote procedure, which can be retrieved – again as an
XML_RPC_Value() object – via its value() method.

Now, when you run the client above, from either an XML-RPC-enabled command line client or
server, the following XML-RPC message will be generated and transmitted to the server:


<?xml version="1.0"?>

<methodCall>

 <methodName>whoAmI</methodName>

 <params>

  <param>

   <value><string>Bugs Bunny</string></value>

  </param>

  <param>

   <value><string>rabbit</string></value>

  </param>

  <param>

   <value><int>18</int></value>

  </param>

 </params>

</methodCall>

The server will respond with this message, which will be decoded and used by the
client – switch on debug to see it for yourself:


<?xml version="1.0"?>

<methodResponse>

 <params>

  <param>

   <value><string>I am Bugs Bunny, a 18-year old rabbit</string></value>

  </param>

 </params>

</methodResponse>

As the client and server above demonstrate, the procedure to construct the two ends of an
XML-RPC connection is pretty straightforward – the only thing that really changes is the
construction of the procedure call and the values passed back and forth through the
message and response packets. It’s therefore important to have a clear understanding of
the XML-RPC data types, and of the internal structure of the message packets.

Not My Type

A few paragraphs back, I introduced you to the simple data types supported by XML-RPC.
However, those aren’t the only kids on the block – the XML-RPC specification also
describes two complex types, arrays and structs, which are conceptually similar
to PHP’s indexed and associative arrays respectively.

To see how an XML-RPC array works, consider the following script, which constructs such
an array using XML_RPC_Value() objects:


<?php

// include class

include ('XML/RPC.php');

// array

$value = new XML_RPC_Value(array(

            new
XML_RPC_Value("huey", "string"),

            new XML_RPC_Value("dewey", "string"),

            new
XML_RPC_Value("louie", "string"),

            ),
"array");

?>

Here’s what the XML-encoded value looks like:

This is how you would go about constructing a struct:


<?php

// include class

include ('XML/RPC.php');

// create struct value

$value = new XML_RPC_Value(array(

            "desc" => new XML_RPC_Value("power drill", "string"),

            
"price" => new XML_RPC_Value("12.99", "double")

            ), "struct");

?>

And here’s the XML version:

Arrays and structs can be used to pass related values to and from a remote procedure.
Consider the example of an XML-RPC server which exposes a getAverage()

method and expects an array of numbers as input argument. Here’s the client, which
invokes the procedure with an array as argument,


<?php

// include base functions

include ('XML/RPC.php');

// initialize client

$client = new XML_RPC_Client("/myserver2.php", "localhost", 80);

// turn on debugging

// $client->setDebug(1);

// generate XML-RPC message packet

// contains RPC method name and arguments

$numberSet = new XML_RPC_Value(array(

                new
XML_RPC_Value("12", "int"),

                new XML_RPC_Value("13", "int"),

                new
XML_RPC_Value("14", "int")

                ),
"array");

$msg = new XML_RPC_Message("getAverage", array($numberSet));

// send request

$response = $client->send($msg);

// read response

if (!$response->faultCode()) {

    
// no error

    // print response

    
$value = $response->value();

    print
"Average is " . $value->scalarval() . "\n";

    }

else {

    // error

    // print error message and code

    
print "Error code: " . $response->faultCode() . " Message: " . $response->faultString() . "\n";

}

?>

and here’s the server, myserver2.php, which parses the input array and performs
calculations on it:


<?php

// include server class

include ('XML/RPC/Server.php');

// create server

// set up dispatch map

$server = new XML_RPC_Server(array("getAverage" => array("function" => "phpCalcAverage")));

// function to calculate average

function phpCalcAverage($params) {

    
// initialize sum

    
$sum = 0;

    

    
// get first argument (array)

    
$arr = $params->getParam(0);

    // iterate and calculate sum of array elements

    
for ($x=0; $x<$arr->arraysize(); $x++) {

        
$elem = $arr->arraymem($x);

        $sum += $elem->scalarval();

    }

    

    
// calculate average

    
$avg = $sum / $arr->arraysize();

    // return response to client

    
return new XML_RPC_Response(new XML_RPC_Value($avg, "double"));

}

?>

Pay special attention to the internals of the phpCalcAverage() function –
this function uses the arraysize() and arraymem() methods of
the array object to calculate the total number of elements in the array and to retrieve
individual elements. These methods are unique to the complex data types in the XML_RPC
package (structs come with a structmem() method).

Fault-y Towers

In the XML-RPC world, errors are referred to as “faults”. These faults are generated by
the XML-RPC server in the event that an error occurs in processing the XML-RPC request.
Every fault has two attributes: a numeric fault code, and a human-readable fault string.
Here’s an example:


<?xml version="1.0" ?>

<methodResponse>

 <fault>

  <value>

   <struct>

    <member>

     <name>faultCode</name>

     <value><int>19</int></value>

    </member>

    <member>

     <name>faultString</name>

     <value><string>Could not find file</string></value>

    </member>

   </struct>

  </value>

 </fault>

</methodResponse>

With the PEAR XML_RPC class, faults can be generated using an
XML_RPC_Response() object, as in the example below:


<?php

// include class

include ('XML/RPC.php');

// create response packet containing fault

$resp = new XML_RPC_Response(0, 19, "Could not find file");

?>

Faults can be retrieved using the faultCode() and faultString()
methods of the XML_RPC_Response() object – you’ve already seen this in
previous examples.

When deciding what numeric codes to assign to your faults, it is wise to follow the
XML_RPC recommendation that “fault codes for your servers should start at the value
indicated by the global $XML_RPC_erruser + 1
“.

Access Granted

Now that you have some inkling of how XML-RPC works, let’s put everything you’ve learned
into a real-world example. The next example shows you how to build an authentication
server, which exposes a simple isValidLogin() procedure. This procedure
accepts a username and password, consults a MySQL database to see if they match, and
returns a “yay” or “nay” to the caller. By exposing this procedure through XML-RPC, it
becomes possible for remote clients to check the validity of user credentials, without
needing access to the user database themselves. This kind of system is particularly
suited to distributed-computing environments, which have multiple clients running on
different platforms and operating systems.

First, a sample of the user database:

Here, each username is unique, and each password is encrypted with the SHA1()
function available in MySQL 4.0.2 or better (Note: PHP also has this function).

Next, the server, myserver3.php, which exposes the isValidLogin()
procedure:


<?php

// include server class

include ('XML/RPC/Server.php');

// create server

// set up dispatch map

$server = new XML_RPC_Server(array("isValidLogin" => array("function" => "auth")));

// authentication function

// connects to MySQL database and verifies user credentials

function auth($params) {

    
// import starting value for user fault codes

    
global $XML_RPC_erruser;

    // get first argument (struct)

    
$struct = $params->getParam(0);

    // get values of struct elements

    
$value = $struct->structmem('username');

    
$username = $value->scalarval();

    

    $value = $struct->structmem('password');

    
$password = $value->scalarval();

    

    
// open connection to MySQL server

    // if connection not possible, return fault

    if (!$connection = @mysql_connect("localhost", "user", "pass")) {

        return new
XML_RPC_Response(0, $XML_RPC_erruser+1, "Unable to connect to database server");

    }

    

    // select database for use

    // if database cannot be selected, generate fault

    
if (!mysql_select_db("db1717")) {

        return new
XML_RPC_Response(0, $XML_RPC_erruser+2, "Unable to select database");

    }

    

    // create and execute query

    // if query cannot be executed, generate fault

    
$query = "SELECT * FROM users WHERE username = '$username' AND password = SHA1('$password')";

    if (!
$result = mysql_query($query)) {

        return new XML_RPC_Response(0, $XML_RPC_erruser+3, "Unable to execute query");

    }

    

    
// check if a record was returned

    // if yes, then username/password is correct

    // if no, then username/password is wrong

    // return appropriate Boolean value

    if (mysql_num_rows($result) == 1) {

        return new
XML_RPC_Response(new XML_RPC_Value(1, "boolean"));

        }

    else {

        return new XML_RPC_Response(new XML_RPC_Value(0, "boolean"));

    }

}

?>

It’s clear from the above that the isValidLogin() procedure accepts a hash
of two elements, the supplied user name and the corresponding plain-text password, and
checks these values against the encrypted values in the database. Depending on the result
of the test, a Boolean true or false value is returned to the caller.

Finally, the client, which presents a form asking for the user name and password, and
submits the same to the server using XML-RPC. The return value of the procedure call is
then used to display an appropriate message to the user.


<html>

<head></head>

<body>

<?php

// form not submitted

if (!isset($_POST['submit'])) {

?>

    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">

    Username: <input type="text" name="name">

    <br />

    Password: <input type="password" name="pass">

    <br />

    <input type="submit" name="submit">

    </form>

<?php

}

// form submitted

else {

    
// include base functions

    
include ('XML/RPC.php');

    

    
// initialize client

    
$client = new XML_RPC_Client("/myserver3.php", "localhost", 80);

    

    // turn on debugging

    // $client->setDebug(1);

    

    // build XML-RPC struct containing username and password

    
$struct = new XML_RPC_Value(array(

                
"username" => new XML_RPC_Value($_POST['name'], "string"),

                "password" => new XML_RPC_Value($_POST['pass'], "string")

                ),
"struct");

    

    
// generate XML-RPC message packet

    // contains RPC method name and arguments

    $msg = new XML_RPC_Message("isValidLogin", array($struct));

    

    
// send request

    
$response = $client->send($msg);

    

    // read response

    
if (!$response->faultCode()) {

        
// no error

        // check response and print message

        
$value = $response->value();

        if ($value->scalarval() == 1) {

            echo "Access granted";

            }

        else {

            echo
"Access denied";

        }

    }

    else {

        // error

        // print error message and code

        
print "Error code: " . $response->faultCode() . " Message: " . $response->faultString() . "<br />";

    }

}

?>



</body>

</html>

The cool thing? No matter what platform or operating system your client is using, so long
as it has XML-RPC support, it can use this server for authentication. This makes it
possible to build a centralized user database that many different agents can access,
independent of local constraints.

And that’s about all I have for you. Over the course of this tutorial, I introduced you
to one of the coolest packages in PEAR, the XML_RPC package. As you’ve seen, this package
provides a simple API to create XML-RPC clients and servers, and encode and decode
message packets and data values. Play with it a little, and read more about what it can
do at http://pear.php.net/manual/en/package.webservices.xml-rpc.php.


Note: Examples are illustrative only, and are not intended for use in a production
environment. Melonfire and Zend provide no warranties or support for the source code
described in this article.

About the Author

Vikram Vaswani is the founder and CEO of Melonfire, a production house and consultancy based
in Bombay, India. He has written several tutorials for zend.com, including a complete
PHP beginners’ series, part of which is due to
be published in book format by McGraw-Hill in January 2005.


Copyright Melonfire, 2004 (http://www.melonfire.com). All rights reserved.