PHP-RPC update 3

I figured, the best way to come up with a usable PHP-RPC spec, is to put it in practice, and see where the problems appear. This is to prevent putting 'academical correctness' before usefulness.

So, I created a server implementation, and so far it works well. It's under a bsd license, so feel free to give it a shot. Right now I put everything in the 'Sabre' package, hopefully at one point this will be Zend, PEAR2 or Solar, but I'll look at that when I can put the 1.0 stamp on it. This implementation is only tested with PHP 5.2 and 5.1.

download link

The server class:

<?php

  // creating the server object
  require_once 'Sabre/PHPRPC/Server.php';
  $server = new Sabre_PHPRPC_Server();

  // handling method calls
  // Method contains a method name.. this could for example be 'blog.getPosts'
  // argumentNotation is 1 if its a simple array, it is 2 if the parameters are specified as a struct
  function invokeMethod($method,array $arguments,$argumentNotation) {

     return 'Hello World! You called the following method: ' . $method;

  }

  $server->setInvokeCallback('invokeMethod');

  // after this point, everything goes automatic.
  $server->exec();

?>

A sample client:

<?php

  $url = 'http://www.example.org/phprpc.php';

  $data = file_get_contents($url . '?method=system.testingMethod');
  $data = unserialize($data);

  echo $data['result']; // will output "Hello World! You called the following method: system.testingMethod

?>

Here's the updated proposal. Changes have been highlighted.

The proposal (0.2)

Goals

  • Client should be very easy to implement. Server is allowed to be a bit more complex.
  • No duplication of the HTTP protocol. For example, HTTP already provides encryption, redirecting and authentication.
  • PHP 4/5/6 compatiblity.
  • Client and server implementations should be built from the idea 'be strict in what you produce, be liberal in what you accept'

The request

Requests are made using either GET or POST. Both should be accepted. GET is more appropriate for fetching information, whereas POST is used for posting new data. POST has the advantage that it doesn't have any limits in the size of the request and an encoding can be supplied. GET has the advantage that information can be fetched using a one-liner.

When there is no encoding specified, UTF-8 is assumed. Data supplied using POST should be encoded as application/x-www-form-urlencoded (this is how a browser submits data by default).

The method thats called should always be supplied as the 'method' variable. The method can contain periods (.) to seperate namespaces like XML-RPC. Arguments can be specified in two ways, and the API documentation should specify what the appropriate way is. The first way is using named arguments, a GET example would be:

http://www.example.org/services/phprpc?method=getUsers&maxItems=20

The method here is getUsers, the named argument is maxItems and its value is 20.

The second way is using a list of arguments, which might be more appropriate in some cases where you want to directly map services and methods from a class on the server to the api. This is also how XML-RPC works.

http://www.example.org/services/phprpc?method=getUsers&arguments[0]=20&arguments[1]=1

The first argument is 20, the second is 1.

Smart clients should autodetect if the user is trying to use named arguments or a sequence by checking out the type of the keys in the array.

Smart servers should use reflection to automatically map named arguments to the actual arguments in a list.

Clients SHOULD supply the version of PHP they are running. This can be either a complete version number, or just the major version (e.g.: 4, 5, 6). Clients should supply this as the phpVersion parameter. If the versionnumber is not supplied, the current stable PHP version is assumed, which is at the time of writing 5.

Clients MAY also supply the version of the PHP-RPC protocol as the 'version' parameter. Currently this is 0.2.

Clients MAY supply a returnClasses parameter. The value for returnClasses is either 0 or 1 and this can tell the server if the client is aware of typed objects that might be sent from the server.

The server

The server MUST allow requests both GET and POST requests. The server MUST treat any incoming text without encoding as UTF-8.

The server SHOULD allow both named arguments and indexed arguments for methods where this is possible.

If the client sent phpVersion the server MUST convert the returned serialized string so it can be read by the server. If the phpVersion is 4 or 5 the server MUST convert all unicode-strings (type U) to binary strings (type s). If the phpVersion is 4 the server MUST convert all private and protected properties to public properties.

Servers SHOULD also convert all typed objects to either STDClass'es or arrays when the client supplied returnClasses is set to 0, if this is appropriate.

I'm fairly sure I will remove the following paragraph. If PHP gets an HTTP 500 on a file_get_contents, it will throw an error, which removes the possibility to easily grab the error message.

When the method-call was successful the server should send HTTP code 200. When an error occurred the server should send an appropriate HTTP error code. (for example 400 for missing arguments, 500 for unexpected exceptions, 401 if the user should authenticate itself first and 403 is the method was not allowed to be called).

The return data is always in PHP's serialize data format. The Content-Type header should always be 'application/x-php-serialized'

The server will always return an error with the following properties:

result
The actual return data.. (or an array with information about an exception, in which case it should have at least the 'message' property.)
status
HTTP status code for the method call. (200 = success, 500 = internal server error, 400 = bad request, etc etc.) Custom error codes have to start at 600.
version
optional: PHP-RPC protocol version. Currently this is 0.2
server
optional: Name of the server. Can be any string.