In ambito di calcolo distribuito e sempre più orientato ai servizi web non si può non nominare qualche tecnica di chiamate a procedure remote (RPC: Remote Procedure Call).
RPC si riferisce all’attivazione di una procedura su una macchina remota a seguito di una richiesta effettuata attraverso la rete. Gli standard più diffusi al momento sono sicuramente SOAP e XML-RPC, ma questi stanno, nell’ultimo periodo, perdendo terreno rispetto al più giovane JSON-RPC (Vedi il neanche tanto recente ritiro delle API SOAP da parte di google).
Come suggerisce il nome, JSON-RPC è una tecnica di RPC basata sul formato di interscambio dati JSON, tanto utilizzato nei moderni siti internet con javascript e le chiamate AJAX.
L’Applicazione di Esempio
Per mantenere le cose il più semplici possibile, realizzeremo una pagina web che contiene un solo articolo, con titolo, data e testo, letto da un file (niente database).
Tramite JSON-RPC sarà possibile leggere e scrivere da remoto il contenuto di questo file, aggiornando così la pagina web.
L’esempio conterrà in sostanza 3 file, oltre allo Zend Framework che utilizzeremo per implementare Client e Server JSON-RPC:
- index.php: Visualizza la pagina con l’articolo
- json-rpc.php: Script che implementa il server JSON-RPC
- article.json: Il nostro storage che conterrà i dati dell’articolo
La Homepage
La homepage sarà semplicissima, legge il contenuto del file article.json e lo visualizza:
[php]
<?php
$article = json_decode(file_get_contents("article.json"), true);
?>
<!DOCTYPE html>
<html>
<head><title>Test JSON-RPC</title></head>
<body>
<h1><?=$article[‘title’]?></h1>
<h5><?=$article[‘date’]?></h5>
<p><?=$article[‘content’]?></p>
</body>
</html>
[/php]
Mentre il contenuto di article.json potrebbe essere qualcosa di simile a:
[javascript]
{
"title": "Test",
"date": "13/01/2011",
"content": "<p>Testo di prova</p>"
}
[/javascript]
Il Server JSON-RPC
Grazie alla potenza della classe Zend_Json_Server, implementare un server JSON-RPC è semplicissimo.
E’ sufficiente definire la classe PHP con i metodi pubblici da rendere disponibili da remoto e passarla come parametro a Zend_Json_Server, che ne leggerà i metodi e creerà automaticamente la definizione del servizio.
Quello che ci resta da fare è gestire le richieste. Le chiamate JSON-RPC devono avvenire necessariamente in POST, per cui se la richiesta è in GET, forniremo la definizione del servizio:
[php]
<?php
// Inizializzo l’ambiente caricando lo zend framework
error_reporting(E_ALL);
require_once "Zend/Loader.php";
Zend_Loader::loadClass("Zend_Loader_Autoloader");
Zend_Loader_Autoloader::getInstance();
// Definisco la classe che legge e scrive il file con l’articolo
class ArticleUpdater {
protected static $storage = ‘article.json’;
public function set($title, $date, $content) {
file_put_contents(self::$storage, json_encode(array(
‘title’ => $title,
‘date’ => $date,
‘content’ => $content
)));
}
public function get() {
return json_decode(file_get_contents(self::$storage), true);
}
}
// Server JSON
$server = new Zend_Json_Server();
$server->setClass(‘ArticleUpdater’);
if ($_SERVER[‘REQUEST_METHOD’] == ‘GET’) {
$server->setTarget(‘/json-rpc.php’)
->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2);
header(‘Content-Type: application/json’);
echo $server->getServiceMap();
return;
}
$server->handle();
[/php]
Ovviamente perché questo script funzioni è necessario aver installato lo Zend Framework in una cartella nell’include_path o nella stessa cartella dell’applicazione. Il pacchetto minimal è più che sufficiente.
Impostiamo inoltre i necessari permessi di scrittura al file article.json (666) per poter consentire al metodo set di modificarne il contenuto.
Client remoto (Consumer)
Purtroppo lo Zend Framework non ci fornisce l’implementazione Zend_Json_Client che ci aspetteremmo, ma realizzare un semplice client PHP tramite CURL non è troppo complicato.
Creiamo quindi una semplice funzione per “consumare” un servizio JSON-RPC. I parametri necessari sono l’url del server e il nome del metodo da chiamare. I parametri sono invece opzionali.
[php]
function json_rpc_call($url, $method, $params=null) {
$request = array(
‘method’ => $method,
‘id’ => 1
);
if (is_array($params))
$request[‘params’] = $params;
$request = json_encode($request);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
‘Content-Type: application/json’,
‘Content-Length: ‘ . strlen($request) . "\r\n",
$request
));
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
$output = json_decode($output, true);
if (!is_null($output[‘error’]))
throw new Exception($output[‘error’][‘message’]);
return $output[‘result’];
}
[/php]
La funzione restituisce il parametro “result” della risposta se tutto ha funzionato correttamente, altrimenti lancerà un’eccezione col messaggio d’errore restituito dal server.
Un test d’uso quindi potrebbe essere questo:
[php]
try {
var_dump(json_rpc_call("http://localhost/json-rpc.php", "get"));
var_dump(json_rpc_call("http://localhost/json-rpc.php", "set", array("Nuovo titolo", "17/01/2011", "Nuovo testo dell’articolo")));
var_dump(json_rpc_call("http://localhost/json-rpc.php", "get"));
} catch (Exception $e) {
echo "JSON-RPC call failed with message: {$e->getMessage()}";
}
[/php]
Ovviamente nulla ci vieta di chiamare le funzioni RPC direttamente da javascript o da qualsiasi altro linguaggio di programmazione. Ad esempio per MooTools, è disponibile una classe apposita sul Forge.
Il codice dell’articolo è disponibile per il download.