Location: PHPKode > scripts > JSON_RPC > JSON_RPC.php
<?php
/**
 * $Id: $
 *
 * A PHP Client/Server implementation of JSON-RPC.
 *
 *
 * LICENSE:
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author      Jason Hines <hide@address.com>
 * @copyright   2005 Jason Hines
 * @license     http://www.opensource.org/licenses/bsd-license.php
 */

include("JSON.php");

/**
 * Client
 */
class JSON_RPC_Client {
    var $host;
    var $path;
    var $port;
    var $response = null;
    var $debug = false;
    var $error = false;
    var $username = null;
    var $password = null;

    function JSON_RPC_Client($url=null,$port=80) {
        $parts = parse_url($url);

        $this->host = $parts['host'];
        $this->path = $parts['path'];
        $this->port = $port;
    }
    function setBasicAuth($username,$password) {
        $this->username = $username;
        $this->password = $password;
    }
    /**
     * Send the client request and return response
     *
     * @param   string  method name
     * @param   mixed   anything ...
     * @return  object  JSON_RPC_Response
     */
    function query() {
        $params = func_get_args();
        $method = array_shift($params);

        $rpc_request = new JSON_RPC_Request(time());
        $rpc_request->setMethod($method);
        $rpc_request->setParams($params);

        $json = new Services_JSON;
        $encoded_request = $json->encode($rpc_request);

        if ($this->debug) {
            echo "Encoded Request: " . $encoded_request . "\n";
        }

        $socket = fsockopen($this->host, $this->port, $errno, $errstr, 30);
        if (!$socket) {
            $this->error = new JSON_RPC_Error(-32300, "HTTP transport error - Could not open socket. [{$errno}] {$errstr}");
            return false;
        }

        fwrite($socket, "POST {$this->path} HTTP/1.0\r\n");
        fwrite($socket, "Host: {$this->host}\r\n");
        fwrite($socket, "Content-type: application/x-javascript\r\n");
        fwrite($socket, "Content-length: " . strlen($encoded_request) . "\r\n");
        fwrite($socket, "User-Agent: JSON_RPC_Client\r\n");
        fwrite($socket, "Accept: */*\r\n");
        if (!is_null($this->username) && !is_null($this->password)) {
            fwrite($socket,"Authorization: Basic ".base64_encode($this->username.':'.$this->password)."\r\n");
        }
        fwrite($socket, "\r\n");
        fwrite($socket, $encoded_request."\r\n");
        fwrite($socket, "\r\n");

        $contents = "";
        $gotFirstLine = false;
        $gettingHeaders = true;
        while (!feof($socket)) {
            $line = fgets($socket, 4096);
            if (!$gotFirstLine) {
                // Check line for '200'
                if (strstr($line, '200') === false) {
                    $this->error = new JSON_RPC_Error(-32300, "HTTP transport error [Server responded: ".trim($line)."]");
                    return false;
                }
                $gotFirstLine = true;
            }
            if (trim($line) == '') {
                $gettingHeaders = false;
            }
            if (!$gettingHeaders) {
                $contents .= trim($line)."\n";
            }
        }
        fclose($socket);
        $contents = trim($contents);

        if ($this->debug) {
            echo "Encoded Response: " . $contents . "\n";
        }

        // Got returned response, decode it back
        $object = $json->decode($contents);

        $response = new JSON_RPC_Response($object->id);
        $response->setResult($object->result);
        $response->setError($object->error);

        if ($this->debug) {
            echo "Decoded Response: " . var_export($response,true) . "\n";
        }

        return $response;
    }
    /**
     *
     * @return  booleon    TRUE if we have an error
     */
    function isError() {
        return (is_object($this->error));
    }
    /**
     * 
     * @return  mixed       Error string, if any
     */
    function getError() {
        if ($this->isError()) {
            return $this->error->toString();
        }
    }
}

/**
 *
 */
class JSON_RPC_Error {
    var $code;
    var $message;
    function JSON_RPC_Error($code, $message) {
        $this->code = $code;
        $this->message = $message;
    }
    function toString() {
        return "JSON_RPC_Error: [{$this->code}] $this->message";
    }
}

/**
 *
 */
class JSON_RPC_Request {
    var $id = null;
    var $method = null;
    var $params = null;
    function JSON_RPC_Request($id) {
        $this->id = $id;
    }
    function setMethod($method) {
        $this->method = $method;
    }
    function setParams($params=array()) {
        if (!is_array($params)) $params = array($params);
        $this->params = $params;
    }
}

/**
 *
 */
class JSON_RPC_Response {
    var $id = null;
    var $result = null;
    var $error = null;
    function JSON_RPC_Response($id) {
        $this->id = $id;
    }
    function setResult($result) {
        $this->result = $result;
    }
    function setError($error) {
        $this->error = $error;
    }
    function getResult() {
        return $this->result;
    }
    function getError() {
        if ($this->isError()) {
            return $this->error->message;
        }
    }
    function isError() {
        return !is_null($this->error);
    }
}

/**
 *
 */
class JSON_RPC_Server {
    var $callbacks = array();
    var $signatures;
    var $help;
    function JSON_RPC_Server() {
        $this->addCallback(
            'system.listMethods', 
            'this:listMethods', 
            array('array'), 
            'Returns an array of available methods on this server'
        );
    }

    /**
     *
     */
    function addCallback($method, $callback, $signature, $help) {
        $this->callbacks[$method] = $callback;
        $this->signatures[$method] = $signature;
        $this->help[$method] = $help;
    }
    function call($methodname, $params) {
        // Over-rides default call method, adds signature check
        if (!$this->hasMethod($methodname)) {
            return new JSON_RPC_Error(-32601, 'Requested method "'.$methodname.'" not specified.');
        }
        $method = $this->callbacks[$methodname];

        // TODO: check the params against the signature
        if (empty($params)) $params = array($params);

        // Do we have the expected param value types?
        $signatures = $this->signatures[$methodname];
        $return_type = array_shift($signatures);
        $expected_types = $signatures;
        for ($i=0;$i<count($expected_types);$i++) {
            $type = gettype($params[$i]);
            if (gettype($params[$i]) != $expected_types[$i]) {
                return new JSON_RPC_Error(-32601, "Argument type mismatch. Param {$i} is a {$type}, expected {$expected_types[$i]}.");
            }
        }

        // Are we dealing with a function or a method?
        if (substr($method, 0, 5) == 'this:') {
            // It's a class method - check it exists
            $method = substr($method, 5);
            if (!method_exists($this, $method)) {
                return new JSON_RPC_Error(-32601, "Requested class method '{$method}' does not exist.");
            }
            // Call the method
            $result = call_user_func_array(array($this,$method),$params);
        } else {
            // It's a function - does it exist?
            if (!function_exists($method)) {
                return new JSON_RPC_Error(-32601, "Requested function '{$method}' does not exist.");
            }
            // Call the function
            $result = call_user_func_array($method,$params);
        }

        if (gettype($result) != $return_type) {
            return new JSON_RPC_Error(-32612, "Return value type mismatch!  Value is type ".gettype($result).", expected {$return_type}.");
        }
        return $result;
    }
    /**
     *
     */
    function serve() {
        if (!isset($GLOBALS['HTTP_RAW_POST_DATA']) || $_SERVER['REQUEST_METHOD']!='POST') {
            die('JSON_RPC server accepts POST requests only.');
        }

        $json = new Services_JSON;
        $request = $json->decode($GLOBALS['HTTP_RAW_POST_DATA']);

        $response = new JSON_RPC_Response($request->id);

        $result = $this->call($request->method, $request->params);
        if (is_a($result, 'JSON_RPC_Error')) {
            $response->setError($result);
        } else {
            $response->setResult($result);
        }

        echo $json->encode($response);
        exit;
    }
    /**
     *
     */
    function listMethods() {
        $hints = array();
        foreach ($this->callbacks as $method=>$callback) {
            $return_sig = array_shift($this->signatures[$method]);
            $hints[] = array(
                'method' => $method,
                'signature' => $return_sig . " " . $method . "(".join(",",$this->signatures[$method]).")",
                'help' => $this->help[$method]
            );
        }
        return $hints;
    }
    function hasMethod($method) {
        return in_array($method, array_keys($this->callbacks));
    }
}

?> 
Return current item: JSON_RPC