Pubblichiamo un servizio tramite JSON-RPC con Zend Framework

Logo JsonIn 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
$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>

Mentre il contenuto di article.json potrebbe essere qualcosa di simile a:

{
  "title": "Test",
  "date": "13/01/2011",
  "content": "<p>Testo di prova</p>"
}

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
// 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();

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.

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'];
}

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:

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()}";
}

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.