Remote Procedure Calls

From CSE330 Wiki
Jump to: navigation, search

RPC

Remote Procedure Calls (RPC) is a general computing paradigm for invoking procedures on one computer from another. There are many forms of RPC technology, and we will focus on XML-RPC.

XML-RPC

XML-RPC is an RPC protocol that uses XML encoding of RPC requests and responses over HTTP.

Calling Remote Procedures

The calling procedure requests invokation of a function on a remote host using XML. A typical XML call looks something like this:

<?xml version="1.0"?>
<methodCall>
  <methodName>FunctionName</methodName>
  <params>
    <param>
        <value><DATATYPE>DATAVALUE</DATATYPE></value>
    </param>
  </params>
</methodCall>

The server invokes the function with the specified parameters then returns a response like this:

<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
        <value><DATATYPE>VALUE</DATATYPE></value>
    </param>
  </params>
</methodResponse>

Data Types

XML-RPC is generalized enough to transmit most any kind of data. The data types are specified like

 <DATATYPE>VALUE</DATATYPE> 

The types include boolean, dateTime,double,int,string. Binary data has to be encoded at the sender in BASE-64 and decoded at the end by the receiver. The encoded binary data is sent in a base64 tag. It also provides arrays and structs.

XML-RPC Implementations

In order to use XML-RPC, you need to have an XML-RPC library installed for your platform and language. There are several available libraries, depending on your needs.


XML-RPC for PHP(PHPXMLRPC)

PHPXMLRPC is one of the several XML-RPC libraries for PHP. It is accessible at http://phpxmlrpc.sourceforge.net/

In order to use this library, you need to download and unzip the package. A handful of .inc files are included in the distribution for PHP clients and servers. Include them in your PHP scripts to get access to the functionality in the library. For example, xmlrpc.inc is the client library and xmlrpcs.inc is the server library. It also provides several helper and wrapper functions that require other include files. You can look at the documentation to learn more about them. For this module, we will concentrate on client functionality.

Creating a client in PHP

xmlrpc_client is the XML-RPC client class. You can create a new client using

$client = new xmlrpc_client( server_url )

It also supports basic HTTP authentication

$client=(  http(s)://user:password@server_url:port/path)

For example, the following code creates a connection to the PHPXMLRPC demo server

<?
include("xmlrpc.inc");
$client = new xmlrpc_client("http://phpxmlrpc.sourceforge.net/server.php");
?>

PHPXMLRPC variables

The parameters(to or from remote procedures) are specified using the xmlrpcval function. This function can be called in different ways:

new xmlrpcval ( )
new xmlrpcval ( string )
new xmlrpcval ( VALUE, TYPE )
new xmlrpcval ( ARRAY, ARRAY_TYPE )

If xmlrpcval is called without a parameter, you need to set the variables by calling the addScalar, addArray or addStruct functions to initialize the variable. If xmlrpcval is called with a string, the variable is assumed to be of type string, and the value is specified in the string parameter. A third option is to pass the value and type of a variable. The fourth option is to create complex variables of arrays and structures.

$myInt = new xmlrpcval(1267, "int");
$myString = new xmlrpcval("Hello, World!", "string");
$myBool = new xmlrpcval(1, "boolean");
// note: this will serialize a php float value as xml-rpc string
$myString2 = new xmlrpcval(1.24, "string");
$myArray = new xmlrpcval(
 array(
   new xmlrpcval("Tom"),
   new xmlrpcval("Dick"),
   new xmlrpcval("Harry")
 ),
 "array");
// recursive struct
$myStruct = new xmlrpcval(
 array(
   "name" => new xmlrpcval("Tom", "string"),
   "age" => new xmlrpcval(34, "int"),
   "address" => new xmlrpcval(
     array(
       "street" => new xmlrpcval("Fifht Ave", "string"),
       "city" => new xmlrpcval("NY", "string")
     ), 
     "struct")
 ), 
 "struct");

Once you have your variables ready, you can send them to the remote server.

Preparing a message

In order to make an RPC, first you need to prepare a message. This is done through the xmlrpcmsg class.

$message = new xmlrpcmsg("REMOTE_FUNCTION", PARAMETER_ARRAY);

Remote function is the name of the function on the remote machine. Parameter array stores the parameters formed above. It can be skipped if the function does not take any parameters.

For example, the following PHP code sends a message to the demo server to call an existing function:

$myParams=array(new xmlrpcval(23, "int"));
$msg = new xmlrpcmsg("examples.getStateName", $myParams);

Calling the function and receiving a response

The actual RPC is done when the client's send function is called.

$client->return_type = 'phpvals';
$resp = $client->send($msg);
if($resp->faultCode())
  echo 'Error: ' . $resp->faultString();
else
  echo 'OK: got ' . $resp->value();

The client's return_type specifies either that the result should be stored in XML or as PHP variables. If there was an error during the call, the response's error code will be set. Otherwise, the value function returns the response value.

Python Server and PHP Client

Python and PHP both have several different libraries available to implement XML-RPC. First we will look at setting up a very simple Python XML-RPC server that returns the larger of two ints. We will do this using the SimpleXMLRPCServer library.

from SimpleXMLRPCServer import SimpleXMLRPCServer


def largerNumber(x, y):
    if x>y:
        return x
    else:
        return y

server = SimpleXMLRPCServer(("127.0.0.1", 8000))
print "Listening on port 8000..."
server.register_multicall_functions()
server.register_function(largerNumber, 'largerNumber')
server.serve_forever()

The Python server defines an RPC method called largerNumber which takes two inputs and returns the larger of the two. Next we create our XML-RPC server instructing it to bind (or listen) to the IP address 127.0.0.1. This is a special IP address called the loopback address or the localhost. This forces the server to only accept request sent to the ec2 instance from that instance itself. Typically your client and server would have unique IP addresses and the XML-RPC message would travel over the Internet.

For the PHP client, all we need to do is setup a new XML-RPC connection to the server. In this case the server is the same ec2 instance as the client so we use the loopback IP address. Next we pass in two ints for the RPC server to compare, $first and $second. We then create a XML RPC message with the function we wish to call and an array containing the arguments for the function. Finally, we send the message, get the response, convert it to a scalar value, and display it.

<?php
   include("xmlrpc.inc"); //for xmlrpc operations

  $client = new xmlrpc_client("http://127.0.0.1:8000:/"); //your host

 $first=1;
 $second=2;
 
 $msg = new xmlrpcmsg('largerNumber',
                         array(new xmlrpcval($first, 'int'),
                         new xmlrpcval($second, 'int')));

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

 $struct = $resp->value();
 $largerNumber = $struct->scalarval();
 print $largerNumber . "\n";  

?>

Perl Frontier Module and PHP client

Perl has a few different modules for XML-RPC. We will use the Frontier::* modules, focusing on Frontier::Daemon which provides XML-RPC server functionality.

You can initialize the daemon with the new function. The arguments include the port number to listen on and function names to serve to clients. For example:

use Frontier::Daemon;

$methods = { 'myservice.convert' => \&convert };

Frontier::Daemon->new(LocalPort => 4873,  methods => $methods) or die "Couldn't start HTTP server: $!";

This will set up the daemon at port 4873 and the function convert is called from the remote clients as myservice.convert. In the following example, the daemon receives a Base64 encoded image, decodes, saves, and modifies it, and then sends the modified image back in Base64. Note that it assumes just single user is using the system at any time. If multiple users access the same server the files could be overwritten.

#!/usr/bin/perl
use MIME::Base64;
use Frontier::Daemon;
use Frontier::RPC2;

$|=1;
print "server $$";
$methods = { 'myservice.convert' => \&convert };

#initialize daemon
Frontier::Daemon->new(LocalPort => 4873,  methods => $methods, ReuseAddr=>1) or die "Couldn't start HTTP server: $!";

sub convert
{
  ($name,$encoded) = @_; #client sends filename and file in base64
  print "Name:$name\n\n\n\n";
  $decoded = MIME::Base64::decode($encoded);
  open(OUT,">/tmp/$name"); #save the image
  print OUT $decoded;
  close(OUT);
  
  #put a label over the image
  system("convert -pointsize 40 -draw \"text 200,200 'cse330'\" /tmp/$name /tmp/a2.jpg");
  
  #read back the image
  open(IN,"</tmp/a2.jpg");
  binmode(IN);
  read IN,$de,1000000;
  close(OUT);
    
  #encode it
  $encoded = MIME::Base64::encode($de);
  #print $encoded;
  
  #send it back
  return {'success' => "true",'result' => $encoded};
}

On the client side, you may have an upload page:

<html>
<body>
  <form enctype="multipart/form-data" action="image-xmlrpc.php" method="POST">
  <input type="hidden" name="MAX_FILE_SIZE" value="100000" />
  Choose an image  file to upload: <input name="uploadedfile" type="file" /><br />
  <input type="submit" value="Upload File" />
  </form>
&lt/body>
</html>

Finally, a PHP based client that would send the uploaded image to the RPC server, and display the modified image.

<?php
  $uploadfile = $_FILES['uploadedfile']['tmp_name'];
  $handle = fopen($uploadfile,'r');
  $file_content = fread($handle,filesize($uploadfile)); //read uploaded file
  fclose($handle);
  $encoded = chunk_split(base64_encode($file_content)); //encode it
  include("xmlrpc.inc"); //for xmlrpc operations

  $client = new xmlrpc_client("http://127.0.0.1:4873/RPC2"); //your host
 
  $myParams = array(new xmlrpcval($_FILES['uploadedfile']['name'], "string"),
                    new xmlrpcval($encoded, "string"));
 
  $message = new xmlrpcmsg("myservice.convert", $myParams);
  $resp = $client->send($message);
  if ($resp->faultCode())
  {
    echo 'KO. Error: '.$resp->faultString();
  }
  else
  {
    $struct=$resp->value();
    $retvalue = $struct->structmem('success'); //value of the field success
    $resultval = $struct->structmem('result'); //result field
    $result = $resultval->scalarval(); //encoded image from the result

    $decoded = base64_decode($result); //decode the base64 data
    $handle = fopen("changed.jpg",'w'); //save it to a new file
    $file_content = fwrite($handle,$decoded);
    fclose($handle);

    echo "<img src='changed.jpg'/><br>"; //display the new image
  }
?>