A Few Examples

I l @ ve RuBoard

Over the preceding sections, you've seen quite a few examples of how the RPC library works and should now have a fairly clear understanding of the theory. This section puts it into practice with a few real-life demonstrations of how this technology can be used across the web.

Retrieving Meteorological Data with XML-RPC

I'll begin with something simple ”a basic RPC client-server implementation for information delivery over HTTP. I'll be using a MySQL database as the data source, XML-RPC as the expression language for the procedure calls and responses, and PHP for the actual mechanics.

Requirements

Let's assume the existence of a fictional government department that is charged with the task of obtaining meteorological information and charting it for statistical purposes. In a rare fit of generosity, this department has decided to make some of its meteorological readings available to the general public, in the hope of encouraging amateur meteorologists (and interested science students) to take an interest in the subject. Consequently, this department needs some way to broadcast its raw data (at the moment, sets of temperature readings for different cities) over the web, to be available to any connecting client.

Here's a snippet of the MySQL database table that contains the temperature data (you may assume that this table is automatically updated on a regular basis by an independent process).

 +------+-------+------+-------+   city  t1     t2    t3      +------+-------+------+-------+   BOM    27.6  26.9   26.1    LON    10.5  11.9   10.8    NYC   17.69  15.8  14.99   +------+-------+------+-------+ 

All that is required is an interface to this database so that users can connect to the system and obtain readings for any city (keyed against each city's unique three-letter city code).

RPC's client-server architecture provides a suitable framework for this kind of application. An RPC server can be used at one end of the connection to accept XML-encoded remote procedure calls (containing arguments such as the city code and the format in which data is required), and execute appropriate functions to retrieve the required data from the database. The results of invoking the procedure can then be sent back to the client for further processing or display.

Obviously, there are a number of different ways to implement this application (Perl, Java, WDDX, and even a plain- vanilla PHP script with MySQL hooks), so my usage of XML-RPC here is purely illustrative . In the long run, however, using XML-RPC might well turn out to be a good decision ”because implementations of the protocol are available for different platforms, it would be possible for the fictional government organization to create other, non-web-based clients (for example, a Windows COM-based application) without any changes required to its RPC server.

The Server

The server is fairly easy to implement (and, in fact, hardly changes across subsequent examples), so I'll demonstrate that first. Listing 6.27 has the code.

Listing 6.27 A Simple RPC Server
 <?php  // get the request  $request = $HTTP_RAW_POST_DATA;  // create server  $rpc_server = xmlrpc_server_create() or die("Could not create RPC server");  // register methods  xmlrpc_server_register_method($rpc_server, "getWeatherData", "phpWeather") or die("Could graphics/ccc.gif not register method");  // call method  $response = xmlrpc_server_call_method($rpc_server, $request, NULL, array("verbosity" => graphics/ccc.gif "no_white_space"));  // print response  echo $response;  // clean up  xmlrpc_server_destroy($rpc_server);  // function to return weather data  // $args include a city code and a parameter indicating whether  // all three readings (raw) or a composite calculation (avg)  // are required  function phpWeather($method, $args, $add_args)  {       // open connection to database        $connection = mysql_connect("localhost", "rpc_agent", "secret") or die ("Unable to graphics/ccc.gif connect!");          mysql_select_db("weather_data") or die ("Unable to select database!");        // get data        $city = mysql_escape_string($args[0]["city"]);        $query = "SELECT t1, t2, t3 FROM weather WHERE city = '$city'";        $result = mysql_query($query) or die ("Error in query: $query. " . mysql_error());        // if a result is returned        if (mysql_num_rows($result) > 0)        {             // iterate through resultset              list($t1, $t2, $t3) = mysql_fetch_row($result);              // process data depending on requested output format              if ($args[0]["format"] == "raw")              {                   // return raw data                    return array("format" => "raw", "data" => array($t1, $t2, $t3));              }             else if ($args[0]["format"] == "avg")              {                   // total and average readings                    // return average                    $total = $t1 + $t2 + $t3;                    // do this to avoid division by zero errors                    if ($total != 0)                    {                         $avg = $total/3;                    }                    else                    {                         $avg = 0;                    }                    return array("format" => "avg", "data" => $avg);              }        }        else        {             // return a fault              return array("faultCode" => 33, "faultString" => "No dataavailable for that graphics/ccc.gif city");        }        // close database connection        mysql_close($connection);  }  ?> 

Most of this should be familiar to you from the previous sections of this chapter. The procedure getWeatherData() , which maps internally to the PHP function phpWeather() , is set up to accept two arguments: the three-letter city code and the data format (raw readings or arithmetic mean of raw readings) required by the client. This procedure is registered with the RPC server via the xmlrpc_server_register_method() function, and it is invoked when the server receives a POST request containing the XML-encoded RPC request.

Going POST-al

In case you're wondering, the special PHP variable $HTTP_RAW_POST_DATA returns the raw data sent to the server via the POST method (or, if you're using a command-line script, the data entered via the standard input). This data can then be parsed and processed by your script.

Internally, the phpWeather() function is pretty simple. It uses the arguments provided to it to connect to a MySQL database, retrieve the data from it, and then return it to the caller as an array containing either the average or the raw data. This returned array is then encoded into XML and sent back to the client as a POST response.

The Client

The RPC client needs to do a few important things:

  • Accept user input as to the city and output format required.

  • Encode this user input into an XML-RPC request, and POST it to the server.

  • Receive the XML-encoded POST response and decode it.

  • Display the received data as an HTML page.

Listing 6.28 has the complete code.

Listing 6.28 A Simple XML-RPC Client
 <html>  <head>  <basefont face="Arial">  </head>  <body>  <?php  if(!$_POST['submit'])  { ?>        <form action="<? echo $_SERVER['PHP_SELF']; ?>" method="POST">        <b>City code:</b>        <br>         input type="text" name="city" size="4" maxlength="3">        <p>          <b>Data format:</b>        <br>        <input type="Radio" name="format" value="avg" checked>Average only        <br>        <input type="radio" name="format" value="raw">Raw data        <p>        <input type="submit" name="submit" value="Go!">        </form>  <?php  }  else  {       // where is the RPC server?        $server = "weather.domain.com";        $url = "/rpc/server.php";        $port = 80;        // RPC arguments        $city = strtoupper($_POST['city']);        $params = array("city" => $city, "format" => $_POST['format']);        // encode XML-RPC request        $request = xmlrpc_encode_request("getWeatherData", $params, array("verbosity" => graphics/ccc.gif "no_white_space"));        // open socket        $fp = fsockopen($server, $port);        if(!$fp)        {             echo "Could not open socket";        }        else        {             // create POST data string              $post_data = "POST $url HTTP/1.0\r\nUser-Agent: PHP-RPC graphics/ccc.gif Client\r\nContent-Type: text/xml\r\nContent-Length: " .strlen($request) . "\r\n\r\n" . graphics/ccc.gif $request;              // send POST data              fwrite($fp, $post_data);              // read the response              $post_response = fread($fp, 14096);              // close the socket              fclose($fp);        }        // strip out HTTP headers in response        // look for <?xml and get everything from there to the end of the response        $response = substr($post_response, strpos($post_response, "<?xml"));        // decode response        $output = xmlrpc_decode($response);        // more stringent error checks would be good here        if(is_array($output))        {             // check to see if a fault was returned              if (!isset($output[0]["faultCode"]))              {                   // no? this means valid data was returned                    // format and display                    if ($output["format"] == "avg")                    {                         echo "Average temperature reading for city $city is " . graphics/ccc.gif $output["data"] . " (based on three readings)";                    }                    else if ($output["format"] == "raw")                    {                         echo "Last three temperature readings for city $city (8-hour graphics/ccc.gif intervals) are:<br><ul>";                          echo "<li>" . $output["data"][0];                          echo "<li>" . $output["data"][1];                          echo "<li>" . $output["data"][2];                          echo "</ul>";                    }              }              else              {                   // a fault occurred                    // format and display fault information                    echo "The following fault occurred:<br>";                    echo $output[0]["faultString"] . " (fault code " . graphics/ccc.gif $output[0]["faultCode"] . ")";              }        }        else        {             // no array returned              echo "Unrecognized response from server";        }  }  ?>  </body>  </html> 

This entire script is split into two main sections. The first section merely displays an HTML form for user input; this form contains a text field for the city code and a pair of radio buttons to choose the data format required.

After the form is submitted, the script calls itself again; this time around, however, the $submit variable will exist, so the second half of the script will be executed. At this stage, the first order of business is to create an array to hold the remote procedure arguments, and create an XML-encoded RPC request with xmlrpc_encode_request() .

Next , a socket is opened to the remote RPC server (actually a web server with PHP's RPC extension compiled in), and the XML data is sent to it as a POST request (note the various HTTP headers that must accompany the data packet). After the server has received the request, decoded it, and executed the remote procedure, the response is sent back (through the same socket connection) to the client as a POST response.

This POST response contains a bunch of HTTP headers, in addition to the XMLRPC response string. These need to be stripped out so that the remaining XML can be decoded into a native PHP data type with xmlrpc_decode() . Finally, depending on the data format requested (this format is included in the RPC response), the temperature reading(s) are displayed with an appropriate message.

Figure 6.3 and Figure 6.4 demonstrate what the client looks like, both before and after sending an RPC request.

Figure 6.3. The XML-RPC client from Listing 6.28, prior to requesting weather data.

graphics/06fig03.gif

Figure 6.4. The XML-RPC client from Listing 6.28, with the weather data requested.

graphics/06fig04.gif

The HTTP Files

If you want to learn more about the HTTP protocol and the POST method, you should take a look at the HTTP specification on the W3C's Protocols web page, at http://www.w3.org/Protocols/.

Now that was fairly complicated, especially the part involving sockets and HTTP headers ”which is why it makes sense to package those bits of code into functions and then separate them from the main body of your script. And, if you look at the official web site for PHP's RPC extension, you'll see that the library author, Dan Libby, has done just this: He's packaged a number of network transmission functions as separate utilities that can be included and used in your RPC client.

Listing 6.29 demonstrates an alternative implementation of the script in Listing 6.28, using these packaged functions. As you will see, this listing is far simpler to read and understand.

Listing 6.29 An Alternative XML-RPC Client
 <html>  <head>  <basefont face="Arial">  </head>  <body>    <?php  if($_POST['submit'])  { ?>        <form action="<? echo $_SERVER['PHP_SELF']; ?>" method="POST">        <b>City code:</b>        <br>        <input type="text" name="city" size="4" maxlength="3">        <p>        <b>Data format:</b>        <br>        <input type="Radio" name="format" value="avg" checked>Average only        <br>        <input type="radio" name="format" value="raw">Raw data        <p>        <input type="submit" name="submit" value="Go!">        </form>  <?php  }  else  {       include("utils.php");        // where is the RPC server?        $server = "weather.domain.com";        $url = "/rpc/server.php";        $port = 80;        // RPC arguments        $city = strtoupper($_POST['city']);        $params = array("city" => $city, "format" => $_POST['format']);        // encode, transmit request and receive, decode response        $output = xu_rpc_http_concise(array("host" => $server, "uri" => $url,"port" => graphics/ccc.gif $port, "method" => "getWeatherData", "args" => $params));        if(is_array($output))        {             // check for faults              if (!xu_is_fault($output))              {                   // no fault!                    // data has been returned                    // format and display                    if ($output["format"] == "avg")                    {                         echo "Average temperature reading for city $city is " graphics/ccc.gif .$output["data"] . " (based on three readings)";                    }                    else if ($output["format"] == "raw")                    {                         echo "Last three temperature readings for city $city (8-hour graphics/ccc.gif intervals) are:<br><ul>";                          echo "<li>" . $output["data"][0];                          echo "<li>" . $output["data"][1];                          echo "<li>" . $output["data"][2];                          echo "</ul>";                    }              }              else              {                   // fault! format and display                    echo "The following fault occurred:<br>";                    echo $output[0]["faultString"] . " (fault code " . graphics/ccc.gif $output[0]["faultCode"] . ")";              }        }        else        {             // no array returned              echo "Unrecognized response from server";        }  }  ?>  </body>  </html> 

In this case, everything related to HTTP header transmission and response translation has been extracted into the function xu_rpc_http_concise() , which accepts a number of arguments identifying the RPC server, remote procedure name and arguments, and response format. This function is defined in the file "utils.php" , which you can download from the official site (http://xmlrpc-epi. sourceforge .net/). If you examine its internals, you'll see that it performs almost all the tasks so lovingly detailed in Listing 6.28, albeit with more stringent error checks.

The same holds true for the xu_is_fault() function, which merely checks the server response for a fault identifier, and returns true if it finds one.

Building an RPC-Based Mail Service

The previous example examined remote database access using the RPC framework. However, this is just one application of many; RPC makes it possible to expose and execute some very useful services over a network. The following example demonstrates one such service: an RPC-accessible mail service that checks a POP3 server for email and returns the number of messages in the specified mailbox.

Requirements

The requirements here are fairly simple: a server capable of accepting RPC requests and making POP3 (or IMAP) connections, and a client capable of encoding and decoding XML-encoded RPC data. Both client and server use SOAP as the encoding mechanism.

Note that it's not necessary that both client and server be implemented using PHP (although that's the way I've done it here); either client or server (or both) can be implemented in any SOAP-supported platform.

The Server

Listing 6.30 has the code for the server.

Listing 6.30 A SOAP Server
 <?php  include("utils.php");  $request = $HTTP_RAW_POST_DATA;  // create server  $rpc_server = xmlrpc_server_create() or die("Could not create RPC server");  // register methods  xmlrpc_server_register_method($rpc_server, "getTotalPOP3Messages", graphics/ccc.gif "getTotalPOP3Messages") or die("Could not register method");  // call method  $response = xmlrpc_server_call_method($rpc_server, $request, NULL,array("verbosity" => graphics/ccc.gif "no_white_space", "version" => "soap 1.1"));  // print response  echo $response;  // clean up  xmlrpc_server_destroy($rpc_server);  // function to return number of messages  function getTotalPOP3Messages($method, $args, $add_args)  {       // check to see if the server supports POP3        // you may need to recompile your PHP build        // to enable this support        if (function_exists('imap_open'))        {             // open connection to mail server              $inbox = imap_open ("{". $args[0]["pop_host"] . "/pop3:110}", graphics/ccc.gif $args[0]["pop_user"], $args[0]["pop_pass"]);              if ($inbox)              {                   // get number of messages                    $total = imap_num_msg($inbox);                    imap_close($inbox);                    return $total;              }              else              {                   return xu_fault_code(2, "Could not connect to POP3 server");              }        }        else        {             return xu_fault_code(1, "POP3 support not available on RPC server");  }  }  ?> 

Again, the server code here is not very different from that seen in Listing 6.27. The primary difference here lies in the remote procedure ”the getTotalPOP3Messages() function accepts a username, password, and mail host as procedure arguments, and attempts a connection to the specified mail host using PHP's POP3 functions. If the connection is successful, the function returns the number of messages found; if not, it generates an appropriate SOAP-Fault using one of the additional utility functions include()-d in "utils.php" .

SOAP encoding is implemented by the simple expedient of specifying SOAP as the encoding mechanism to the xmlrpc_server_call_method() function. Because PHP's RPC extension supports both SOAP and XML-RPC (see the earlier sidebar entitled "Teething Trouble" for a few caveats related to this), switching between the two is a fairly simple matter, and one that is quickly accomplished.

The Client

In its internals, this client also resembles the previous ones. Take a look at Listing 6.31.

Listing 6.31 A SOAP Client
 <html>  <head>  <basefont face="Arial">  </head>  <body>  <?php  if(!$_POST['submit'])    {       // display form  ?>        <table border="0" cellspacing="5" cellpadding="5">        <form action="<? echo $_SERVER['PHP_SELF']; ?>" method="POST">        <tr>        <td><b>Username:</b></td>        <td><input type="text" name="pop_user"></td>        </tr>        <tr>        <td><b>Password:</b></td>        <td><input type="password" name="pop_pass"></td>        </tr>        <tr>        <td><b>POP Server:</b></td>        <td><input type="text" name="pop_host"></td>        </tr>        <tr>        <td colspan="2" align="center"><input type="submit" name="submit" value="Get Total graphics/ccc.gif Messages!"></td>        </tr>        </form>        </table>  <?php  }  else  {       // include useful network connection functions        include("utils.php");        // where is the RPC server        $server = "mail.service";        $url = "/rpc/server.php";        $port = 80;        // RPC arguments        $params = array("pop_user" => $_POST['pop_user'], "pop_pass" => $_POST['pop_pass'], graphics/ccc.gif "pop_host" => $_POST['pop_host']);        // encode, transmit request and receive, decode response        $result = xu_rpc_http_concise(array("host" => $server, "uri" => $url, "port" => graphics/ccc.gif $port, "method" => "getTotalPOP3Messages", "args" => $params, "output" => array("version" graphics/ccc.gif => "soap 1.1")));        // check for faults        if(is_array($result) && $result["faultcode"])        {             echo "The following error occurred: " . $result["faultstring"] . " (error graphics/ccc.gif code " . $result["faultcode"] . ")";        }        // if none present, display message count        else        {             echo "$result messages in mailbox";        }  }  ?>  </body>  </html> 

Again, the first half of the script sets up an HTML form for the user to enter mail account details; these details are then encoded into a SOAP envelope and sent to the SOAP server using the provided xu_rpc_http_concise() function. The decoded return value is then analyzed , and an appropriate message is displayed, depending on whether a fault was recorded or not.

Figure 6.5 and Figure 6.6 demonstrate what the client looks like, both before and after sending an RPC request.

Figure 6.5. The SOAP client from Listing 6.31, prior to retrieving mailbox information.

graphics/06fig05.gif

Figure 6.6. The SOAP client from Listing 6.31, with the mailbox information requested.

graphics/06fig06.gif

Executing Shell Commands via RPC

Finally, an example that demonstrates the power inherent in RPC, and illustrates what a double-edged sword that power can be. The next (and final) example creates an RPC server that can accept any system or shell command from a client and execute this command on the server.

Requirements

Again, fairly simple: a client who accepts a command through user input, encodes it, and transmits it to a server that executes this command and returns the resulting output to the client. Both client and server communicate using XML-RPC, with HTTP as the network transport layer.

The Server

Listing 6.32 has the complete code for the server. This is probably one of the simplest servers created in this section: It merely exposes one procedure ” execR() ”that in turn simply uses PHP's backtick operator command (equivalent to the exec () command you read about in Chapter 2, "PHP and the Simple API for XML (SAX)") to execute the command received and return the output to the calling script.

Listing 6.32 An XML-RPC Server
 <?php  $request = $HTTP_RAW_POST_DATA;  // create server  $rpc_server = xmlrpc_server_create() or die("Could not create RPC server");  // register methods  xmlrpc_server_register_method($rpc_server, "execR", "execR") or die("Could not register graphics/ccc.gif method");  // call method  $response = xmlrpc_server_call_method($rpc_server, $request, NULL);  // print response  echo $response;  // clean up  xmlrpc_server_destroy($rpc_server);  // execute command  function execR($method, $args, $add_args)  {       $output = `$args[0]`;        return $output;  }  ?> 
The Client

As you might guess, the client is equally simple ”a form consisting of one text box, into which the user can enter the command to be executed on the remote server. This data is then encoded and sent to the server as an XML-RPC request with the command output coming back as an XML-RPC response.

Listing 6.33 has the complete code listing.

Listing 6.33 An XML-RPC Client
 <html>  <head>  <basefont face="Arial">  </head>    <body>  <?php  if(!$_POST['submit'])  { ?>        <form action="<? echo $_SERVER['PHP_SELF']; ?>" method="POST">        <b>Remote command:</b>        <br>        <input type="text" name="command">        <input type="submit" name="submit" value="Go!">        </form>  <?php  }  else  {       include("utils.php");        // where is the RPC server?        $server = "remote.server";        $url = "/rpc/server.php";        $port = 80;        // encode, transmit request and receive, decode response         output = xu_rpc_http_concise(array("host" => $server, "uri" => $url, "port" => graphics/ccc.gif $port, "method" => "execR", "args" => $_POST['command']));        echo $output;  }  ?>  </body>  </html> 

Figure 6.7 and Figure 6.8 demonstrate what the client looks like, both before and after sending an RPC request.

Figure 6.7. The XML-RPC client from Listing 6.33, prior to executing a remote command.

graphics/06fig07.gif

Figure 6.8. The XML-RPC client from Listing 6.33, after receiving the output of the uname -a command from the XML-RPC server.

graphics/06fig08.gif

Red Alert

It cannot be stressed enough that an RPC server, such as the one discussed in Listing 6.32, should never, ever be implemented in a production environment. By allowing any client to execute any command on a remote system, you're immediately opening up a security hole of gaping proportions ”one that can be used by malicious users to seriously damage your network or system.

The example is included here only for illustrative purposes, to demonstrate the power that RPC provides; it's fitting to remember that this power can be easily turned against you if used improperly.

I l @ ve RuBoard


XML and PHP
XML and PHP
ISBN: 0735712271
EAN: 2147483647
Year: 2002
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net