Home > OOP, PHP > Creating your own RPC-interface in PHP

Creating your own RPC-interface in PHP

April 17th, 2009

Last week I got a request to create some kind of RPC-server-client in PHP. The assignment was to create a flexible solution to allow remote updating several properties in a database. I’ve created interfaces like this before, but specifically for AJAX-calls to PHP-objects, not really for server-to-server-calls. This particular assignment also involved a PHP-client-object for other developers to implement in their website. The RPC-interface actually doesn’t really care if it’s used for real server-to-server calls or AJAX-calls. However; in this article I’ll focus on server-to-server communication, if designed well it shouldn’t take too much effort to make it AJAX-compatible.


[download code]

This is how these RPC-calls are done:

RPC-interface structure

Since I like XML a lot, I wanted to use XML as transport mechanism for doing requests to the RPC-interface. Because I use XML I was also able to group RPC-calls into one single request. A simple method of authenticating these RPC-calls is something to consider if you’re not running a public service, but that’s beyond the scope of this article

A typical call would look like this:

<command>
	<method name=”remote_method” object=”some_object”>
		<arg key=”some_argument”>value_of_some_argument</arg>
		<arg key=”another_argument”>value_of_another_argument</arg>
	</method>
	<method name=”another_remote_method” object=”another_object”>
		<arg key=”some_argument”>value_of_some_argument</arg>
		<arg key=”another_argument”>value_of_another_argument</arg>
	</method>
</command>

Now, what does this XML actually mean?

First of all; the methods need to be nested into a single node: <command>, the XML would not be valid otherwise. Each method-node has an name-attribute and object-attribute, the object-attribute defines the remote PHP-object which contains the method you’d like to call, the method is defined in the name-attribute.

Each method-node contains one or more arg-nodes, these nodes are passed to the method as a hashed-array and used as arguments for the method. Attribute “key” will be the key or name in the arguments-array, the node-value will become the value.

The example above will eventually call 2 methods,

  1. the first method, “remote_method” is part of object “some_object”, and receives 2 arguments;
    • “some_argument” with value “value_of_some_argument”
    • “another_argument” with value “value_of_another_argument”
  2. the second method, “another_remote_method” is part of object “another_object”, and also receives 2 arguments;
    • “some_argument” with value “value_of_some_argument”
    • “another_argument” with value “value_of_another_argument”

Now, let get some coding done, let’s focus on the RPC-server first, download this code-snippet and unpack it in a fresh webroot (it contains all the files described in this article, also the RPC-client).

First take a look at /include/classes:

  • tools.php : contains static “helper” methods, I hate procedural spaghetti-code and I always put methods which are not part of a larger object-model into several helper-classes. In this particular example a single helper-class is enough. All methods in this class are static and can be called without first creating an object.
  • abstract_object.php : this is important and can be confusing if you’re not used to an object oriented approach. This class contains methods which are inherited into child-classes, but you cannot create an object-instance of this class, because it’s abstract. Why is it abstract? It’s arguable to just let it be a normal class in this particular example, however; using abstract classes in larger projects really keeps your code clean and separates the implementation of your object-design from the structure you’re using.
  • some_object.php : inherited from abstract_class.php; example implementation.
  • another_object.php : inherited from abstract_class.php; another example implementation.

The actual object available for the RPC-interface are some_object and another_object. Now let’s take a look at rpc.php, which is in the root. The only really interesting part is from line 56



	// gather 'method' nodes and execute them;
	$methods = $dom->getElementsByTagName('method');

	for ($i=0;$i<$methods->length;$i++){
		$methodNode = $methods->item($i);
		$method = $methodNode->getAttribute('name');
		$obj = $methodNode->getAttribute('object');
		$obj = new $obj();
		$arguments = tools::getArgumentsAsArray($methodNode);
		$obj->$method($arguments);
		$messages .= $obj->getMessages();
	}

	echo tools::wrapMessages($messages);

So, what’s really happening here?

The first statement; $methods = $dom->getElementsByTagName(‘method’); will retrieve all method-nodes from the request. PHP-DOM (or any other DOM-implementation for that matter) will always return the nodes in a nodeset, even if it’s only one method-node, but not if there aren’t any nodes found.
This nodeset is used to loop upon, the classic for-next statement on the next line.

In each loop the method-node is examined; the attribute-values for object and method are determined ($method = $methodNode->getAttribute(‘name’) and $obj = $methodNode->getAttribute(‘object’)) and using a static function from the tools-class the arguments are converted into a PHP-array ($arguments = tools::getArgumentsAsArray($methodNode) ).

Now we’re ready gathering all the values needed to really create the object ( $obj = new $obj() ) and execute the requested method ( $obj->$method($arguments) ).

The method should not return anything, instead it’s return-values are stored into a protected variable (defined in abstract_object). To get these values, use $obj->getMessages(). This method doesn’t seem to be available in the class of $obj, it’s defined in abstract_object.

After the loop has finished the $message-string should be wrapped into a node to make sure the XML is valid, this is done with the tools’ class wrapMessages-method.

Ok, the RPC-interface is ready, let’s test it! Enter the following URL into a browser (assuming you’re running PHP on localhost, in a folder called rpc:

http://localhost/rpc.php?req=<command><method name="remote_method" object="some_object"> <arg key="some_argument">value_of_some_argument</arg><arg key="another_argument">value_of_another_argument</arg> </method><method name="another_remote_method" object="another_object"> <arg key="some_argument">value_of_some_argument</arg><arg key="another_argument">value_of_another_argument</arg> </method></command>

You’ve just called a 2 functions from your RPC-interface! The output should look like this:

<servermessages>
    <message object="some_object" method="remote_method" code="200">remote_method was executed
        succesfully with arguments : value_of_some_argument, value_of_another_argument </message>
    <message object="another_object" method="another_remote_method" code="200">another_remote_method
        was executed succesfully with arguments : value_of_some_argument, value_of_another_argument
    </message>
</servermessages>

Now let’s move on to the RPC-client: take a look at rpcClient.php in the root. It’s quite large, and I will not go into all the details (the comments in the file should be enough). Most of the functions in rpcClient take care of constructing the XML needed to execute the RPC-call, absolutely no rocket-science.  However; I do want to highlight the actual “executing” of the RPC-call, take a look at the private function sendToHost:


 private function sendToHost($data) {
	$postdata = http_build_query(
	    array(
	        'req' => $data
	    )
	);
	$opts = array('http' =>
	    array(
	        'method'  => 'POST',
	        'header'  => 'Content-type: application/x-www-form-urlencoded',
	        'content' => $postdata
	    )
	);
	$context  = stream_context_create($opts);
	$this->result = file_get_contents($this->rpcURI, false, $context);
}

This method uses file_get_contents, http_build_query and stream_context_create to POST the constructed XML to the RPC-server and stores the reply in a local variable ($result). I’ve tried using fopen like this:

function sendToHost($host,$path,$data) {
	$fp  = @fsockopen($host,80);
	$buf = '';
	if ($fp) {
		@fputs($fp, "POST $path HTTP/1.0\n");
		@fputs($fp, "Host: $host\n");
		@fputs($fp, "Content-type: application/x-www-form-urlencoded\n");
		@fputs($fp, "Content-length: " . strlen($data) . "\n");
		@fputs($fp, "Connection: close\n\n");
		@fputs($fp, $data);
		while (!feof($fp)) {
		  $buf .= fgets($fp,128);
		}
		fclose($fp);
	}
	return $buf;
}

This method failed. I kept getting weird responses from my RPC-server (command not understood etc.). And honestly;  I like the clean call using file_get_contents, http_build_query and stream_context_create much better.

Ok; we have a nice object-structure, a RPC-server and a RPC-Client. What’s left is a real world implementation of the client. Take a look at example.php:

 include('rpcClient.php');

 $hsClient = new rpcClient();
 $hsClient->createCall('some_object', 'remote_method', array('some_argument'=>'some_value', 'another_argument'=>'another_value'));
 $hsClient->createCall('another_object', 'another_remote_method', array('some_argument'=>'some_value', 'another_argument'=>'another_value'));
 $hsClient->execute();
 $str = $hsClient->getXMLResponse();
 echo "<pre>";
 echo $str;
 echo "</pre>"; 

If you’d leave out the debugging-statement at the end you end up using 4 lines of code to create, execute and retrieve a RPC-call (ok; 5 lines if you consider I’ve made 2 RPC calls). First you create an instance of the RPC-client, secondly you create a call using createCall and last but not least; you execute the call and intepret the response. If you’d enter the URL to example.php into your browser you’ll get:

remote_method was executed succesfully with arguments : some_value, another_value
another_remote_method was executed succesfully with arguments : some_value, another_value

That’s it! You have a RPC-server, RPC-client and a working implementation! You should take care of authenticating your RPC-calls, and design a nice Object-structure to implement ‘behind’  the RPC-server, this all depends on the kind of logic you need. I’ve used this method of RPC-ing to allow 3rd-parties to update certain values in a shop-database, and with a little effort you could make your RPC-server handle AJAX-requests and return JSON to clients. But that something for the next article.

[download code]

Share this post:
  • Twitter
  • Facebook
  • Digg
  • del.icio.us
  • FriendFeed
  • Technorati
  • Google Bookmarks
  • PDF
  • Print
  • NuJIJ
  • Ping.fm
  • StumbleUpon
  • Symbaloo
  • Hyves

buTTon OOP, PHP , , , , ,

  1. October 18th, 2009 at 11:57 | #1

    With respect to your function that tried using fsockopen, I have found that some servers are more persnickety than others in what they will accept as far as headers. Primarily, \r\n is the expected newline pattern, not just \n, so the server might not be translating the commands properly, but just as important is knowing exactly which headers to send. Generally, I’ll start with a set of headers recently sent by my browser (intercepted with something like Tamper-Data or Live HTTP Headers for Firefox) and whittle those down to the smallest set that the target machine will accept.

    At any rate, your article pleased me, and may I say “Good Show, sir”

  1. April 18th, 2009 at 05:24 | #1

Onze tip:SCDB.info – de meest actuele flitspalen in Europa voor uw GPS!