I l @ ve RuBoard |
A PHP module for RPC has been available since version 4.1.0 of the language (released in December 2001). Created by Dan Libby of Epinions.com, this RPC extension (which goes by the distressingly unpronounceable name of XMLRPC-EPI-PHP) provides standard encoding, decoding, and server administration functions for XML-RPC and SOAP requests and responses.This extension is also available as a standalone C library at the project's official web site: http://xmlrpc-epi. sourceforge .net/. If you're using a stock PHP binary, it's quite likely that you'll need to recompile PHP to add support for this library to your PHP build (detailed instructions for accomplishing this are available in this book's Appendix A, "Recompiling PHP to Add XML Support"). This chapter will focus exclusively on the XMLRPC-EPI-PHP extension. However, it should be noted that this RPC library is not the only game in town ”both the XML-RPC web site (http://www.xmlrpc.org/) and SOAPWare (http://www.soapware.org/) list other PHP implementations of these protocols. Links to these alternative implementations are also available on this book's web site, and some of them are discussed in Chapter 8, "Open Source XML/PHP Alternatives."
Encoding and Decoding RPC RequestsPHP's RPC extension offers two functions designed specifically to encode RPC requests into XML.The first (and most commonly used) is the xmlrpc_encode_request() function that accepts, as function arguments, the name of the procedure to be called on the remote server and the parameters to be passed to it. Listing 6.8 demonstrates how it works. Listing 6.8 Encoding a Remote Procedure Call with PHP<?php // $numbers is an array of arguments to be passed to the remote procedure $numbers = array(34, 78, 2, 674); // encode procedure call echo xmlrpc_encode_request("getRange", $numbers); ?> The output of this code snippet is displayed in Listing 6.9. Listing 6.9 An XML-RPC Request<?xml version='1.0' encoding="iso-8859-1" ?> <methodCall> <methodName>getRange</methodName> <params> <param> <value> <int>34</int> </value> </param> <param> <value> <int>78</int> </value> </param> <param> <value> <int>2</int> </value> </param> <param> <value> <int>674</int> </value> </param> </params> </methodCall> The second argument passed to xmlrpc_encode_request() ”a variable containing arguments for the remote procedure ”may be of any type supported by PHP, and it will automatically be encoded into a corresponding XML-RPC data type. Listing 6.10 demonstrates by encoding a PHP array of mixed type with xmlrpc_encode_request . Listing 6.10 Encoding Different Data Types with PHP<?php echo xmlrpc_encode_request("some_remote_method", array("name" => "John Doe", 34, "abracadabra")); ?> Listing 6.11 has the output. Listing 6.11 An XML-RPC Request Containing Different Data Types<?xml version='1.0' encoding="iso-8859-1" ?> <methodCall> <methodName>some_remote_method</methodName> <params> <param> <value> <struct> <member> <name>name</name> <value> <string>John Doe</string> </value> </member> <member> <name/> <value> <int>34</int> </value> </member> <member> <name/> <value> <string>abracadabra</string> </value> </member> </struct> </value> </param> </params> </methodCall> It's also possible to encode an RPC request as SOAP (rather than XML-RPC) data; this is accomplished by adding a third optional argument to the call to xmlrpc_encode_request() (see the sidebar entitled "What's in a Name?" for more output options). Listing 6.12 demonstrates how to encode a SOAP request. Listing 6.12 Encoding a SOAP Request<?php // $numbers is an array of arguments to be passed to the remote procedure $numbers = array(34, 78, 2, 674); // encode procedure call echo xmlrpc_encode_request("getRange", $numbers, array("version" => "soap 1.1")); ?> The output of this command is a SOAP envelope containing the remote procedure call and its arguments, as Listing 6.13 demonstrates. Listing 6.13 A SOAP Request<?xml version="1.0" encoding="iso-8859-1" ?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns: xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/ XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:si="http:// soapinterop.org/xsd" xmlns:ns6="http://testuri.org" SOAP-ENV:encodingStyle="http:// schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <getRange> <xsd:int>34</xsd:int> <xsd:int>78</xsd:int> <xsd:int>2</xsd:int> <xsd:int>674</xsd:int> </getRange> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
It's also conceivable that you might need to generate XML for just a single procedure argument, rather than a complete RPC request ”if, for example, you're incrementally building an RPC packet from a database or flat file. In this case, you can use the xmlrpc_encode() function, which returns an XML string containing the data-typed representation of the structure passed to xmlrpc_encode() . Listing 6.14 demonstrates encoding a single parameter. Listing 6.14 Encoding a Single Parameter<?php echo xmlrpc_encode(98.6); ?> Listing 6.15 shows the output. Listing 6.15 The XML-RPC Result of Encoding a Single Parameter<?xml version="1.0" encoding="utf-8" ?> <params> <param> <value> <double>98.600000</double> </value> </param> </params>
This works with arrays, too ”as Listings 6.16 and 6.17 demonstrate . Listing 6.16 Encoding an Array<?php $params = array("temperature" => "98.6", "convert_to" => "celsius"); // encode parameter echo xmlrpc_encode($params); ?> Listing 6.17 The XML-RPC Result of Encoding an Array<?xml version="1.0" encoding="utf-8" ?> <params> <param> <value> <struct> <member> <name>temperature</name> <value> <string>98.6</string> </value> </member> <member> <name>convert_to</name> <value> <string>celsius</string> </value> </member> </struct> </value> </param> </params> Once encoded, the RPC request needs merely to be transmitted to the RPC server. This transmission may take place using any supported protocol; in the case of the web, this is usually HTTP. For a full-fledged example, take a look at Listing 6.26.
When the server receives the encoded RPC request, it needs to decode it, execute the procedure, obtain a return value, encode it as an RPC response, and transmit it back to the requesting client. At the server end of the connection, PHP's RPC extension automatically handles request decoding and response encoding via the xmlrpc_server_call_method() function (discussed in the next section); at the client end, however, the decoding of the server response needs to be implemented manually. This is primarily accomplished by the xmlrpc_decode() function, a mirror of the xmlrpc_encode() function discussed in the preceding section. This function accepts an XML-RPC encoded response and decodes it into native PHP data types for subsequent use. Listing 6.18 demonstrates this. Listing 6.18 Decoding an XML-RPC Response<?php // sample response from server (XML-RPC) $response = <<< END <?xml version="1.0"?> <methodResponse> <params> <param> <value> <struct> <member> <name>start</name> <value> <int>2</int> </value> </member> <member> <name>end</name> <value> <int>674</int> </value> </member> </struct> </value> </param> </params> </methodResponse> END; // decode into native type and print $output = xmlrpc_decode($response); echo "Numbers provided fall in the range " . $output["start"] ." to " . $output["end"]; ?> This works just as well with a SOAP-encoded response, too (see Listing 6.19). Listing 6.19 Decoding a SOAP Response<?php // sample response from server (SOAP) $response = <<< END <?xml version="1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns: xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/ XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:si="http:// soapinterop.org/xsd" xmlns:ns6="http://testuri.org" SOAP-ENV:encodingStyle="http:// schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <getRangeResponse> <item> <start xsi:type="xsd:int">2</start> <end xsi:type="xsd:int">674</end> </item> </getRangeResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> END; // decode into native type and print $output = xmlrpc_decode($response); echo "Numbers provided fall in the range " . $output["start"] ." to " . $output["end"]; ?> The RPC extension also includes an xmlrpc_decode_request() function, which decodes an XML-encoded request string and returns the embedded remote procedure name and its arguments. It's unlikely that you'll ever need to use this function because (as you'll shortly see) the server can handle XML-encoded RPC requests without needing to decode them first, and the client will typically never need to decode a request (merely encode it). It's included with the library for the sake of completeness, and an example is included here for the same reason (see Listing 6.20). Listing 6.20 Decoding an XML-RPC Request<?php // encode a request $request = xmlrpc_encode_request("someMethod", array("red", "green", "blue")); // decode it with xmlrpc_decode() // $params will contain the name of the called procedure // $output is an array containing passed parameters $params = xmlrpc_decode_request($request, &$method); // prints "Called method was: someMethod(red, green, blue)" echo "Called method was: $method(" . join(", ", $params) . ")"; ?> Note that the variable containing the original remote procedure name must be passed to xmlrpc_decode_request() by reference. Creating and Using an RPC ServerBy itself, an RPC client is fairly useless; to be really valuable , it needs a server at the other end of the connection to process its requests and transmit information back to it. And so, PHP's RPC extension also provides a server API to support server instantiation and procedure registration and invocation. There are a number of different steps involved here. So, let's look at a simple example to understand how the process works (see Listing 6.21). Listing 6.21 A Simple PHP-based RPC Server<?php // $request contains an XML-encoded request $request = <<< END <?xml version='1.0' encoding="iso-8859-1" ?> <methodCall> <methodName>getRange</methodName> <params> <param> <value> <int>34</int> </value> </param> <param> <value> <int>78</int> </value> </param> <param> <value> <int>2</int> </value> </param> <param> <value> <int>674</int> </value> </param> </params> </methodCall> END; // create server $rpc_server = xmlrpc_server_create() or die("Could not create RPC server"); // register methods xmlrpc_server_register_method($rpc_server, "getRange", "phpGetRange") or die("Could not register method"); // call method $response = xmlrpc_server_call_method($rpc_server, $request, NULL); // print response echo $response; // clean up xmlrpc_server_destroy($rpc_server); // function to calculate highest // and lowest in a number series function phpGetRange ($method, $args, $add_args) { sort ($args); return array ('start' => $args[0], 'end' => end ($args)); } ?> Let's go through the process step-by-step:
The previous steps make up a fairly standard process, and you'll see them being used over and over again in the examples that follow.
IntrospectionPHP's RPC extension also supports introspection , a fairly cool concept that allows your RPC server to display a certain degree of self-awareness about the procedures registered with it. A primitive introspection API makes it possible for developers to document the procedures they create, and provide this documentation to connecting clients using a simple XML vocabulary.
An example might make this clearer. Consider Listing 6.22, which invokes, over RPC, the procedure system.listMethods() . Listing 6.22 Listing Remote Procedures with system.listMethods()<?php // a sample request $request = <<< END <?xml version="1.0"?> <methodCall> <methodName>system.listMethods</methodName> <params /> </methodCall> END; // create server $rpc_server = xmlrpc_server_create() or die("Could not create RPC server"); // register methods // execute request $response = xmlrpc_server_call_method($rpc_server, $request, NULL, array("version" => "xml")); // print response echo $response; // clean up xmlrpc_server_destroy($rpc_server); ?> Now, system.listMethods() is a built-in function that generates a list of methods currently registered with the RPC server and returns this list to the client. Listing 6.23 demonstrates the response to the procedure call. Listing 6.23 The Result of a Call to system.listMethods()<?xml version='1.0' encoding="iso-8859-1" ?> <methodResponse> <params> <param> <value> <array> <data> <value><string>system.listMethods</string></value> <value><string>system.methodHelp</string></value> <value><string>system.methodSignature</string></value> <value><string>system.describeMethods</string></value> <value><string>system.multiCall</string></value> <value><string>system.getCapabilities</string></value> <value><string>getRandomQuote</string></value> </data> </array> </value> </param> </params> </methodResponse> This is a very basic example of introspection ”the server is aware of all the methods currently registered with it and can provide this information to clients on demand. Of course, this information is not limited to a mere list of procedure names . The system.describeMethods() procedure provides more exhaustive information on registered procedures, including details such as expected arguments and argument types, expected return values, optional and required arguments, related methods, bugs , and version information; whereas the system.methodHelp() procedure provides information on the purpose of each registered procedure. There are a few other procedures as well (take a look at the next sidebar entitled "Talking Back" for a list). As an illustration, consider Listing 6.24, which demonstrates what system.methodHelp() has to say about the getRandomQuote() function. Listing 6.24 Obtaining an XML Description of the getRandomQuote() Procedure with system.methodHelp()<?xml version='1.0' encoding="iso-8859-1" ?> <methodResponse> <params> <param> <value><string>Retrieves a random quote by the specified author</string></value> </param> </params> </methodResponse>
Obviously, this information is not generated automatically; the developer needs to provide it to the RPC server while registering the corresponding procedure. For this purpose, PHP's RPC extension includes a function named xmlrpc_server_register_introspection_callback() , which names a callback function to handle introspection requests. Listing 6.25 has an example. Listing 6.25 Registering an Introspection Handler<?php xmlrpc_server_register_introspection_callback($rpc_server, "generateIntrospectionData"); ?> In this case, the callback function generateIntrospectionData() is responsible for generating documentation in the approved XML vocabulary when it intercepts an introspection request. Listing 6.26 has a more detailed example. Listing 6.26 A more Complete Introspection Handler<?php // create server $rpc_server = xmlrpc_server_create() or die("Could not create RPC server"); // register methods xmlrpc_server_register_method($rpc_server, "getRandomQuote", "phpGetRandomQuote") or die("Could not register method"); // function to run when introspection request comes through xmlrpc_server_register_introspection_callback($rpc_server, "generateIntrospectionData"); // generate an XML-RPC request for system.methodHelp // this would normally come from an RPC client $request = xmlrpc_encode_request("system.methodHelp", "getRandomQuote"); // call method $response = xmlrpc_server_call_method($rpc_server, $request, NULL, array("version" => "xml")); // print response echo $response; // clean up xmlrpc_server_destroy($rpc_server); // function to auto-generate documentation for procedures // (introspection) function generateIntrospectionData() { $data = <<< END <?xml version="1.0"?> <introspection version="1.0"> <methodList> <methodDescription name="getRandomQuote"> <author>Vikram Vaswani</author> <purpose>Retrieves a random quote by the specified author</purpose> <version>1.0</version> <signatures> <signature> <params> <value type="string" name="author">The name of the quote author</ value> </params> <returns> <value type="string" name="quote">A quote by the specified author</ value> </returns> </signature> </signatures> <errors> <item>Returns fault code 2 if no quotes by the specified author are available</item> </errors> <notes /> <bugs/> <todo/> </methodDescription> </methodList> </introspection> END; return $data; } function phpGetRandomQuote($method, $args, $add_args) { // code snipped out } ?> Note that the XML-encoded documentation generated by the callback function must conform to the rules laid down in the introspection specification, available from the extension's official web site at http://xmlrpc-epi.sourceforge.net/, and that the function must actually exist for the introspection data to be correctly returned. By and large, introspection is a good idea. It provides application developers with an efficient, transparent mechanism of obtaining API documentation, thereby making remote services more accessible and easier to use ”which, after all, is the holy grail of RPC. This book's web site includes links to a few web-based introspection clients that demonstrate this capability further. |
I l @ ve RuBoard |