From 68011b2b35e7c3ce4be2fc02a4ed6e03d10aabed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Fri, 11 Mar 2022 13:21:45 +0100 Subject: [PATCH 1/2] v2.0 --- .gitignore | 4 + Makefile | 32 +++ README.md | 50 +++++ composer.json | 18 ++ src/Textalk/JsonRpc/ApplicationError.php | 7 + src/Textalk/JsonRpc/BatchRequest.php | 30 +++ src/Textalk/JsonRpc/Exception.php | 38 ++++ src/Textalk/JsonRpc/Helper.php | 16 ++ src/Textalk/JsonRpc/InternalError.php | 14 ++ src/Textalk/JsonRpc/InvalidParamsError.php | 14 ++ src/Textalk/JsonRpc/InvalidRequestError.php | 14 ++ src/Textalk/JsonRpc/InvalidVersionError.php | 14 ++ src/Textalk/JsonRpc/MethodNotFoundError.php | 18 ++ src/Textalk/JsonRpc/ParseError.php | 15 ++ src/Textalk/JsonRpc/ResponseError.php | 26 +++ src/Textalk/JsonRpc/Server.php | 155 ++++++++++++++ src/Textalk/JsonRpc/StdInServer.php | 12 ++ src/Textalk/JsonRpc/WebClient.php | 214 ++++++++++++++++++++ src/Textalk/JsonRpc/WebClientNotify.php | 18 ++ src/Textalk/JsonRpc/WebServer.php | 17 ++ 20 files changed, 726 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/Textalk/JsonRpc/ApplicationError.php create mode 100644 src/Textalk/JsonRpc/BatchRequest.php create mode 100644 src/Textalk/JsonRpc/Exception.php create mode 100644 src/Textalk/JsonRpc/Helper.php create mode 100644 src/Textalk/JsonRpc/InternalError.php create mode 100644 src/Textalk/JsonRpc/InvalidParamsError.php create mode 100644 src/Textalk/JsonRpc/InvalidRequestError.php create mode 100644 src/Textalk/JsonRpc/InvalidVersionError.php create mode 100644 src/Textalk/JsonRpc/MethodNotFoundError.php create mode 100644 src/Textalk/JsonRpc/ParseError.php create mode 100644 src/Textalk/JsonRpc/ResponseError.php create mode 100644 src/Textalk/JsonRpc/Server.php create mode 100644 src/Textalk/JsonRpc/StdInServer.php create mode 100644 src/Textalk/JsonRpc/WebClient.php create mode 100644 src/Textalk/JsonRpc/WebClientNotify.php create mode 100644 src/Textalk/JsonRpc/WebServer.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38b4dce --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +composer.lock +composer.phar +vendor/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..501af6c --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +# Default +all: deps-install + + +# DEPENDENCY MANAGEMENT + +# Updates dependencies according to lock file +deps-install: composer.phar + ./composer.phar --no-interaction install + +# Updates dependencies according to json file +deps-update: composer.phar + ./composer.phar self-update + ./composer.phar --no-interaction update + + +# TESTS AND REPORTS + +# Code standard check +cs-check: composer.lock + ./vendor/bin/phpcs --standard=PSR1,PSR12 --encoding=UTF-8 --report=full --colors src + + +# INITIAL INSTALL + +# Ensures composer is installed +composer.phar: + curl -sS https://getcomposer.org/installer | php + +# Ensures composer is installed and dependencies loaded +composer.lock: composer.phar + ./composer.phar --no-interaction install \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9967f0d --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Textalk JSON-RPC 2.0 + +xJsonRPC-PHP is a JSON-RPC library for PHP featuring a client (TODO) and a server. Currently it follows the 2.0 spec of JSON-RPC, including batch calls + +## Server + +### Usage + +There's currently 3 server implementations: Server, WebServer and StdInServer + +#### Server + +Base class, accepts a JSON string as argument to handle() that is processed as a JSON-RPC request + +#### WebServer + +Will attempt to read out php://input to get the JSON-RPC request, handle() should be called (without arguments) to make it start processing + +#### StdInServer + +Does the same as WebServer except it uses php://stdin (command line, etc.) instead of php://input + + +### Implementing methods + + +To implement methods you subclass one of the above servers and add methods. +These methods must be public in order to be allowed for server use. + +Example +```php + use \Textalk\JsonRpc\Server; + + class ExampleServer extends Server + { + public function echo($echo) + { + return $echo; + } + } + + $server = ExampleServer(); + $response = $server->handle(); +``` + +And then you can call the echo method with your preferred JSON-RPC client library by connecting to whatever url you have set-up that calls ``$server->handle()`` + +## Client + +TODO diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..018cdc5 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "textalk/jsonrpc", + "type": "library", + "description": " JSON-RPC 2.0 client/server library for PHP", + "keywords": ["jsonrpc"], + "license": "ISC", + "autoload": { + "psr-4": { + "": "src/" + } + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^3.5" + } +} diff --git a/src/Textalk/JsonRpc/ApplicationError.php b/src/Textalk/JsonRpc/ApplicationError.php new file mode 100644 index 0000000..fa55a9c --- /dev/null +++ b/src/Textalk/JsonRpc/ApplicationError.php @@ -0,0 +1,7 @@ +client = $client; + $this->calls = []; + } + + public function __call($method, $args) + { + $reqid = count($this->calls); + $this->calls[] = $this->client->assembleRequest($method, $args, $reqid); + + return $reqid; + } + + public function __invoke() + { + $this->result = $this->client->sendRequest($this->calls); + return $this->result; + } +} diff --git a/src/Textalk/JsonRpc/Exception.php b/src/Textalk/JsonRpc/Exception.php new file mode 100644 index 0000000..7a19beb --- /dev/null +++ b/src/Textalk/JsonRpc/Exception.php @@ -0,0 +1,38 @@ +data = $data; + parent::__construct($message, $code); + } + + public function getDict() + { + $data = [ + 'code' => $this->getCode(), + 'message' => $this->getMessage() + ]; + if ($this->data) { + $data['data'] = $this->data; + } + return $data; + } + + public function getData() + { + return $this->data; + } +} diff --git a/src/Textalk/JsonRpc/Helper.php b/src/Textalk/JsonRpc/Helper.php new file mode 100644 index 0000000..07d17e2 --- /dev/null +++ b/src/Textalk/JsonRpc/Helper.php @@ -0,0 +1,16 @@ + '2.0']); + } +} diff --git a/src/Textalk/JsonRpc/MethodNotFoundError.php b/src/Textalk/JsonRpc/MethodNotFoundError.php new file mode 100644 index 0000000..68288c7 --- /dev/null +++ b/src/Textalk/JsonRpc/MethodNotFoundError.php @@ -0,0 +1,18 @@ +response = $response; + } + + /** + * To debug what couldn't be parsed. + * @return The response that couldn't be parsed + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/src/Textalk/JsonRpc/Server.php b/src/Textalk/JsonRpc/Server.php new file mode 100644 index 0000000..51ddf42 --- /dev/null +++ b/src/Textalk/JsonRpc/Server.php @@ -0,0 +1,155 @@ +handleRequest($data); + return $this->encodeJson($result); + } + + protected function handleRequest($data) + { + try { + $request = $this->parseJson($data); + } catch (ParseError $e) { + return $this->createErrorResponse($e, null); + } + return $this->delegateRequest($request); + } + + protected function parseJson($json) + { + $result = json_decode($json, true); + if ($result === null) { + throw new ParseError(); + } + return $result; + } + + protected function encodeJson($result) + { + return json_encode($result); + } + + protected function createSuccessResponse($result, $reqid) + { + return [ + 'jsonrpc' => '2.0', + 'id' => $reqid, + 'result' => $result + ]; + } + + protected function createErrorResponse(Exception $error, $reqid) + { + $response = [ + 'jsonrpc' => '2.0', + 'id' => $reqid, + ]; + if ($error instanceof Exception) { + $response['error'] = $error->getDict(); + } else { + $response['error'] = [ + 'code' => 0, + 'message' => $error->getMessage() + ]; + } + return $response; + } + + protected function delegateRequest($request) + { + if (Helper::isAssoc($request)) { + return $this->parseRequest($request); + } else { + return $this->parseBatchRequest($request); + } + } + + protected function parseBatchRequest($requests) + { + $result = []; + foreach ($requests as $request) { + $request_result = $this->parseRequest($request); + + // Don't collect notification responses + if ($request_result !== null) { + $result[] = $request_result; + } + } + + return $result; + } + + protected function parseRequest($request) + { + try { + $this->validateRequest($request); + } catch (Exception $e) { + return $this->createErrorResponse($e, null); + } + + try { + $result = $this->runRequest($request); + if (isset($request['id'])) { + return $this->createSuccessResponse($result, $request['id']); + } else { + return null; // No return data for notification requests + } + } catch (Exception $e) { + return $this->createErrorResponse($e, $request['id']); + } + } + + protected function validateRequest($request) + { + if (!array_key_exists('method', $request)) { + throw new InvalidRequestError('Missing method'); + } + + if (!array_key_exists('jsonrpc', $request) || $request['jsonrpc'] != '2.0') { + throw new InvalidVersionError(); + } + + if (!is_string($request['method'])) { + throw new InvalidRequestError('Method is not string but ' . gettype($request['method'])); + } + + if (array_key_exists('params', $request) && !is_array($request['params'])) { + throw new InvalidRequestError('Params is not array but ' . gettype($request['params'])); + } + + if ( + isset($request['id']) && !is_numeric($request['id']) && !is_string($request['id']) + && !is_null($request['id']) + ) { + throw new InvalidRequestError('id isn\'t string, int or NULL but ' . gettype($request['id'])); + } + + return true; + } + + protected function runRequest($request) + { + $reqid = $request['id']; + $method = trim($request['method']); + $params = array_key_exists('params', $request) ? $request['params'] : []; + + try { + $caller = new \ReflectionMethod($this, $method); + } catch (\ReflectionException $e) { + throw new MethodNotFoundError($method); + } + // Only public methods defined in extending class are allowed to be call + if (!$caller->isPublic() || $caller->getDeclaringClass()->name == 'Textalk\JsonRpc\Server') { + throw new MethodNotFoundError($method); + } + return $caller->invokeArgs($this, $params); + } +} diff --git a/src/Textalk/JsonRpc/StdInServer.php b/src/Textalk/JsonRpc/StdInServer.php new file mode 100644 index 0000000..589254d --- /dev/null +++ b/src/Textalk/JsonRpc/StdInServer.php @@ -0,0 +1,12 @@ +endpoint = $endpoint; + $this->notify = new WebClientNotify($this); + + if ($flags & self::NO_VERIFY_SSL) { + $this->verify_ssl = false; + } + } + + public function __call($method, $args) + { + $request = $this->assembleRequest($method, $args); + return $this->sendRequest($request); + } + + public function createBatchRequest() + { + return new BatchRequest($this); + } + + public function notify($method, $args) + { + $request = $this->assembleRequest($method, $args, null, true); + return $this->encodeJson($request); + } + + public function assembleRequest($method, $args, $reqid = 1, $notification = false) + { + return [ + 'method' => $method, + 'params' => $args, + 'id' => $notification ? null : $reqid, + 'jsonrpc' => '2.0' + ]; + } + + public function sendRequest(array $request) + { + $request_json = $this->encodeJson($request); + + $curl = curl_init($this->endpoint); + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $request_json); + + if ($this->verify_ssl === false) { + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + } + + if ($this->debug) { + trigger_error("Jsonrpc/WebClient sending request: $request_json"); + } + $response = curl_exec($curl); + + return $this->parse($response); + } + + public function setDebug($debug = true) + { + $this->debug = $debug; + } + + protected function parse($data) + { + $response = $this->parseJson($data); + return $this->delegateResponse($response); + } + + protected function handleSuccessResponse($response, $reqid) + { + return [ + 'result' => $response, + 'reqid' => $reqid + ]; + } + + protected function handleErrorResponse(Exception $error, $reqid) + { + return [ + 'result' => $error, + 'reqid' => $reqid + ]; + } + + protected function delegateResponse($response) + { + if (Helper::isAssoc($response)) { + $result = $this->handleResponse($response); + $result = $result['result']; + + if ($result instanceof Exception) { + throw $result; + } else { + return $result; + } + } else { + return $this->handleBatchResponse($response); + } + } + + protected function handleBatchResponse($responses) + { + $results = []; + foreach ($responses as $response) { + $result = $this->handleResponse($response); + $results[$result['reqid']] = $result['result']; + } + + return $results; + } + + protected function handleResponse($response) + { + try { + $this->validateResponse($response); + } catch (Exception $exception) { + return $this->handleErrorResponse($exception, null); + } + + try { + $result = $this->parseResponse($response); + return $this->handleSuccessResponse($result, $response['id']); + } catch (Exception $exception) { + return $this->handleErrorResponse($exception, $response['id']); + } + } + + protected function parseResponse($response) + { + if (array_key_exists('error', $response)) { + $error = $response['error']; + switch ($error['code']) { + case -32700: + throw new ParseError(); + break; + case -32600: + throw new InvalidRequestError($error['message']); + break; + case -32601: + throw new MethodNotFoundError(); + break; + case -32602: + throw new InvalidParamsError(); + break; + case -32603: + throw new InternalError(); + break; + case 0: + //throw new InvalidVersionError(); + break; + default: + $data = array_key_exists('data', $error) ? $error['data'] : null; + throw new ApplicationError($error['code'], $error['message'], $data); + break; + } + } + + return $response['result']; + } + + protected function validateResponse($response) + { + if (!array_key_exists('id', $response)) { + throw new InvalidRequestError('Missing id'); + } + + //if(!array_key_exists('jsonrpc', $response) || $response['jsonrpc'] != '2.0') + // throw new InvalidVersionError(); + + if (!array_key_exists('result', $response) && !array_key_exists('error', $response)) { + throw new InvalidRequestError('No error or result in response'); + } + + if (!is_numeric($response['id']) && !is_string($response['id']) && !is_null($response['id'])) { + throw new InvalidRequestError('id isn\'t string or int but ' . gettype($request['id'])); + } + + return true; + } + + protected function parseJson($json) + { + $result = json_decode($json, true); + if ($result === null) { + throw new ParseResponseError($json); + } + + return $result; + } + + protected function encodeJson($result) + { + return json_encode($result); + } +} diff --git a/src/Textalk/JsonRpc/WebClientNotify.php b/src/Textalk/JsonRpc/WebClientNotify.php new file mode 100644 index 0000000..2e31c56 --- /dev/null +++ b/src/Textalk/JsonRpc/WebClientNotify.php @@ -0,0 +1,18 @@ +parent = $parent; + } + + public function __call($method, $args) + { + return $this->parent->notify($method, $args); + } +} diff --git a/src/Textalk/JsonRpc/WebServer.php b/src/Textalk/JsonRpc/WebServer.php new file mode 100644 index 0000000..29f6600 --- /dev/null +++ b/src/Textalk/JsonRpc/WebServer.php @@ -0,0 +1,17 @@ +handleRequest($data); + + header('HTTP/1.0 200'); // Signifying that the communication went well. + + return $this->encodeJson($result); + } +} From 3530ef4080995a654a523cb607b4525666254f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Fri, 11 Mar 2022 13:31:01 +0100 Subject: [PATCH 2/2] Docs --- README.md | 20 +--- README.rst | 57 ----------- autoload.php | 38 -------- client.php | 253 ------------------------------------------------- exceptions.php | 131 ------------------------- helpers.php | 13 --- server.php | 178 ---------------------------------- 7 files changed, 4 insertions(+), 686 deletions(-) delete mode 100644 README.rst delete mode 100755 autoload.php delete mode 100755 client.php delete mode 100755 exceptions.php delete mode 100644 helpers.php delete mode 100755 server.php diff --git a/README.md b/README.md index 9967f0d..f8d41e2 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,12 @@ xJsonRPC-PHP is a JSON-RPC library for PHP featuring a client (TODO) and a serve There's currently 3 server implementations: Server, WebServer and StdInServer -#### Server - -Base class, accepts a JSON string as argument to handle() that is processed as a JSON-RPC request - -#### WebServer - -Will attempt to read out php://input to get the JSON-RPC request, handle() should be called (without arguments) to make it start processing - -#### StdInServer - -Does the same as WebServer except it uses php://stdin (command line, etc.) instead of php://input - +* `Server` Base class, accepts a JSON string as argument to handle() that is processed as a JSON-RPC request +* `WebServer` Will attempt to read out php://input to get the JSON-RPC request, handle() should be called (without arguments) to make it start processing +* `StdInServer` Does the same as WebServer except it uses php://stdin (command line, etc.) instead of php://input ### Implementing methods - To implement methods you subclass one of the above servers and add methods. These methods must be public in order to be allowed for server use. @@ -40,11 +30,9 @@ Example } $server = ExampleServer(); - $response = $server->handle(); + $response = $server->handle('{"id": 1, "method": "echo", "jsonrpc": "2.0", "params": ["hello world"]}'); ``` -And then you can call the echo method with your preferred JSON-RPC client library by connecting to whatever url you have set-up that calls ``$server->handle()`` - ## Client TODO diff --git a/README.rst b/README.rst deleted file mode 100644 index 1fe7889..0000000 --- a/README.rst +++ /dev/null @@ -1,57 +0,0 @@ -Description -=========== - -xJsonRPC-PHP is a JSON-RPC library for PHP featuring a client(TODO) and a server. Currently it follows the 2.0 spec of JSON-RPC, including batch calls - -Usage -===== - -Server ------- - -There's currently 3 server implementations: Jsonrpc20Server, WebJsonrpc20Server and StdinJsonrpc20Server - -Jsonrpc20Server -............... - -Base class, accepts a JSON string as argument to handle() that is processed as a JSON-RPC request - -WebJsonrpc20Server -.................. - -Will attempt to read out php://input to get the JSON-RPC request, handle() should be called (Without arguments) to make it start processing - -StdinJsonrpc20Server -.................... - -Does the same as WebJsonrpc20Server except it uses php://stdin (Command line, etc.) instead of php://input - -Implementing methods -.................... - -To implement methods you subclass one of the above servers and add methods prepended with jsonrpc20_ any method with that prefix can be called through JSON-RPC (Leaving out the jsonrpc20_ prefix of course) - -Example -....... - -:: - - require_once('xjsonrpc-php/server.php'); - - class ExampleServer extends WebJsonrpc20Server - { - public function jsonrpc20_echo($echo) - { - return $echo; - } - } - - $server = ExampleServer(); - $server->handle() - -And then you can call the echo method with your preferred JSON-RPC client library by connecting to whatever url you have set-up that calls ``$server->handle()`` - -Client ------- - -TODO diff --git a/autoload.php b/autoload.php deleted file mode 100755 index e302d5f..0000000 --- a/autoload.php +++ /dev/null @@ -1,38 +0,0 @@ - $basedir . '/client.php', - 'Jsonrpc20BatchRequest' => $basedir . '/client.php', - 'Jsonrpc20WebClient' => $basedir . '/client.php', - 'Jsonrpc20Server' => $basedir . '/server.php', - 'StdinJsonrpc20Server' => $basedir . '/server.php', - 'WebJsonrpc20Server' => $basedir . '/server.php', - 'JsonrpcException' => $basedir . '/exceptions.php', - 'JsonrpcParseError' => $basedir . '/exceptions.php', - 'JsonrpcParseResponseError' => $basedir . '/exceptions.php', - 'JsonrpcInvalidRequestError' => $basedir . '/exceptions.php', - 'JsonrpcInvalidVersionError' => $basedir . '/exceptions.php', - 'JsonrpcMethodNotFoundError' => $basedir . '/exceptions.php', - 'JsonrpcInvalidParamsError' => $basedir . '/exceptions.php', - 'JsonrpcInternalError' => $basedir . '/exceptions.php', - 'JsonrpcApplicationError' => $basedir . '/exceptions.php', - ); - } - - if (isset($classfiles[$class])) { - require_once($classfiles[$class]); - return; - } -} - -spl_autoload_register('xJsonRpc_autoload'); diff --git a/client.php b/client.php deleted file mode 100755 index d17a922..0000000 --- a/client.php +++ /dev/null @@ -1,253 +0,0 @@ -parent = $parent; - } - - public function __call($method, $args) - { - return $this->parent->notify($method, $args); - } -} - -class Jsonrpc20BatchRequest -{ - protected $client; - protected $calls; - protected $result; - - public function __construct(Jsonrpc20WebClient $client) - { - $this->client = $client; - $this->calls = array(); - } - - public function __call($method, $args) - { - $reqid = count($this->calls); - $this->calls[] = $this->client->assemble_request($method, $args, $reqid); - - return $reqid; - } - - public function __invoke() - { - $this->result = $this->client->send_request($this->calls); - return $this->result; - } -} - -/** - * @TODO Error handling - */ -class Jsonrpc20WebClient -{ - // Bitfield flags to constructor - const NO_VERIFY_SSL = 1; - - protected $endpoint; - protected $debug = false; - protected $verify_ssl = true; - public $notify; - - public function __construct($endpoint, $flags = 0) - { - $this->endpoint = $endpoint; - $this->notify = new Jsonrpc20WebClientNotify($this); - - if ($flags & self::NO_VERIFY_SSL) $this->verify_ssl = false; - } - - public function __call($method, $args) - { - $request = $this->assemble_request($method, $args); - return $this->send_request($request); - } - - public function create_batch_request() - { - return new Jsonrpc20BatchRequest($this); - } - - public function notify($method, $args) - { - $request = $this->assemble_request($method, $args, null, true); - return $this->json_encode($request); - } - - public function assemble_request($method, $args, $reqid = 1, $notification = false) - { - return array( - 'method' => $method, - 'params' => $args, - 'id' => $notification ? null : $reqid, - 'jsonrpc' => '2.0' - ); - } - - public function send_request(array $request) - { - $request_json = $this->_encode_json($request); - - $curl = curl_init($this->endpoint); - - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_POSTFIELDS, $request_json); - - if ($this->verify_ssl === false) { - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); - } - - if ($this->debug) trigger_error("Jsonrpc20WebClient sending request: $request_json"); - $response = curl_exec($curl); - - return $this->_parse($response); - } - - public function set_debug($debug = true) - { - $this->debug = $debug; - } - - protected function _parse($data) - { - $response = $this->_parse_json($data); - return $this->_delegate_response($response); - } - - protected function _handle_success_response($response, $reqid) - { - return array('result' => $response, - 'reqid' => $reqid); - } - - protected function _handle_error_response(Exception $error, $reqid) - { - return array('result' => $error, - 'reqid' => $reqid); - } - - protected function _delegate_response($response) - { - if(is_assoc($response)) - { - $result = $this->_handle_response($response); - $result = $result['result']; - - if($result instanceof Exception) - throw $result; - else - return $result; - } - else - return $this->_handle_batch_response($response); - } - - protected function _handle_batch_response($responses) - { - $results = array(); - foreach($responses as $response) - { - $result = $this->_handle_response($response); - $results[$result['reqid']] = $result['result']; - } - - return $results; - } - - protected function _handle_response($response) - { - try - { - $this->_validate_response($response); - } - catch(JsonrpcException $exception) - { - return $this->_handle_error_response($exception, NULL); - } - - try - { - $result = $this->_parse_response($response); - return $this->_handle_success_response($result, $response['id']); - } - catch(JsonrpcException $exception) - { - return $this->_handle_error_response($exception, $response['id']); - } - } - - protected function _parse_response($response) - { - if(array_key_exists('error', $response)) - { - $error = $response['error']; - switch($error['code']) - { - case -32700: - throw new JsonrpcParseError(); - break; - case -32600: - throw new JsonrpcInvalidRequestError($error['message']); - break; - case -32601: - throw new JsonrpcMethodNotFoundError(); - break; - case -32602: - throw new JsonrpcInvalidParamsError(); - break; - case -32603: - throw new JsonrpcInternalError(); - break; - case 0: - //throw new JsonrpcInvalidVersionError(); - break; - default: - $data = array_key_exists('data', $error) ? $error['data'] : null; - throw new JsonrpcApplicationError($error['code'], $error['message'], $data); - break; - } - } - - return $response['result']; - } - - protected function _validate_response($response) - { - if(!array_key_exists("id", $response)) - throw new JsonrpcInvalidRequestError("Missing id"); - - //if(!array_key_exists("jsonrpc", $response) || $response["jsonrpc"] != "2.0") - // throw new JsonrpcInvalidVersionError(); - - if(!array_key_exists('result', $response) && !array_key_exists('error', $response)) - throw new JsonrpcInvalidRequestError("No error or result in response"); - - if(!is_numeric($response["id"]) && !is_string($response["id"]) && !is_null($response['id'])) - throw new JsonrpcInvalidRequestError("id isn't string or int but " . gettype($request["id"])); - - return true; - } - - protected function _parse_json($json) - { - $result = json_decode($json, true); - if($result === NULL) - throw new JsonrpcParseResponseError($json); - - return $result; - } - protected function _encode_json($result) - { - return json_encode($result); - } -} -?> diff --git a/exceptions.php b/exceptions.php deleted file mode 100755 index 2cd755f..0000000 --- a/exceptions.php +++ /dev/null @@ -1,131 +0,0 @@ -data = $data; - parent::__construct($message, $code); - } - - public function getDict() - { - $data = array( - "code" => $this->getCode(), - "message" => $this->getMessage() - ); - if($this->data) - $data["data"] = $this->data; - - return $data; - } - - public function getData() - { - return $this->data; - } -} - -/** - * Invalid JSON was received by the server. - * An error occurred on the server while parsing the JSON text - */ -class JsonrpcParseError extends JsonrpcException -{ - public function __construct() - { - parent::__construct(self::PARSE_ERROR, "Parse error"); - } -} - -/** - * Invalid JSON was received FROM the server. - */ -class JsonrpcParseResponseError extends JsonrpcException -{ - private $response; - - public function __construct($response) - { - parent::__construct(0, "Response parse error"); - $this->response = $response; - } - - /** - * To debug what couldn't be parsed. - * @return The response that couldn't be parsed - */ - public function getResponse() - { - return $this->response; - } -} - -/** - * The JSON sent is not a valid Request object - */ -class JsonrpcInvalidRequestError extends JsonrpcException -{ - public function __construct($message) - { - parent::__construct(self::INVALID_REQUEST, "Invalid request: " . $message); - } -} - -/** - * The JSON-RPC call is using an incompatible version - */ -class JsonrpcInvalidVersionError extends JsonrpcException -{ - public function __construct() - { - parent::__construct(0, "Incompatible version", array('expectedVersion' => "2.0")); - } -} - -/** - * The method does not exist / is not available. - */ -class JsonrpcMethodNotFoundError extends JsonrpcException -{ - public function __construct($method = null) - { - if (empty($method)) parent::__construct(self::METHOD_NOT_FOUND, "Method not found"); - else parent::__construct(self::METHOD_NOT_FOUND, "Method not found: $method"); - } -} - -/** - * Invalid method parameter(s). - */ -class JsonrpcInvalidParamsError extends JsonrpcException -{ - public function __construct($data = null) - { - parent::__construct(self::INVALID_PARAMS, "Invalid params", $data); - } -} - -/** - * Internal JSON-RPC error. - */ -class JsonrpcInternalError extends JsonrpcException -{ - public function __construct() - { - parent::__construct(self::INTERNAL_ERROR, "Internal error"); - } -} - -class JsonrpcApplicationError extends JsonrpcException -{ -} -?> diff --git a/helpers.php b/helpers.php deleted file mode 100644 index 34265fe..0000000 --- a/helpers.php +++ /dev/null @@ -1,13 +0,0 @@ - $value) - { - if(!is_numeric($key)) - return true; - } - return false; - } -} -?> diff --git a/server.php b/server.php deleted file mode 100755 index 7d79813..0000000 --- a/server.php +++ /dev/null @@ -1,178 +0,0 @@ -_handle($data); - return $this->_encode_json($result); - } - - protected function _handle($data) - { - try - { - $request = $this->_parse_json($data); - } - catch(JsonrpcParseError $e) - { - return $this->_create_error_response($e, NULL); - } - return $this->_delegate_request($request); - } - - protected function _parse_json($json) - { - $result = json_decode($json, true); - if($result === NULL) - throw new JsonrpcParseError(); - - return $result; - } - protected function _encode_json($result) - { - return json_encode($result); - } - - protected function _create_success_response($result, $reqid) - { - return array( - "jsonrpc" => "2.0", - "id" => $reqid, - "result" => $result - ); - } - - protected function _create_error_response(Exception $error, $reqid) - { - $response = array( - "jsonrpc" => "2.0", - "id" => $reqid - ); - if($error instanceof JsonrpcException) - $response["error"] = $error->getDict(); - else - { - $response["error"] = array( - "code" => 0, - "message" => $error->getMessage() - ); - } - return $response; - } - - protected function _delegate_request($request) - { - if(is_assoc($request)) - return $this->_parse_request($request); - else - return $this->_parse_batch_request($request); - } - - protected function _parse_batch_request($requests) - { - $result = array(); - foreach($requests as $request) - { - $request_result = $this->_parse_request($request); - - // Don't collect notification responses - if ($request_result !== null) $result[] = $request_result; - } - - return $result; - } - - protected function _parse_request($request) - { - try - { - $this->_validate_request($request); - } - catch(JsonrpcException $e) - { - return $this->_create_error_response($e, NULL); - } - - try - { - $result = $this->_run_request($request); - if (isset($request['id'])) - return $this->_create_success_response($result, $request["id"]); - else return null; // No return data for notification requests - } - catch(JsonrpcException $e) - { - if (!isset($request['id'])) return null; // No return data for notification requests - return $this->_create_error_response($e, $request["id"]); - } - } - - protected function _validate_request($request) - { - if(!array_key_exists("method", $request)) - throw new JsonrpcInvalidRequestError("Missing method"); - - if(!array_key_exists("jsonrpc", $request) || $request["jsonrpc"] != "2.0") - throw new JsonrpcInvalidVersionError(); - - if(!is_string($request["method"])) - throw new JsonrpcInvalidRequestError("Method is not string but " . gettype($request["method"])); - - if(array_key_exists("params", $request) && !is_array($request["params"])) - throw new JsonrpcInvalidRequestError("Params is not array but " . gettype($request["params"])); - - if(isset($request['id']) && !is_numeric($request["id"]) && !is_string($request["id"]) - && !is_null($request["id"])) - throw new JsonrpcInvalidRequestError("id isn't string, int or NULL but " . gettype($request["id"])); - - return true; - } - - protected function _run_request($request) - { - $reqid = $request["id"]; - $method = $request["method"]; - $params = array_key_exists("params", $request) ? $request["params"] : array(); - - $methodName = "jsonrpc20_" . $method; - if(!is_callable(array($this, $methodName))) - throw new JsonrpcMethodNotFoundError(); - - if(!empty($params) && is_assoc($params)) - $result = $this->$methodName($params); - else - $result = call_user_func_array(array($this, $methodName), $params); - - return $result; - } -} - -class StdinJsonrpc20Server extends Jsonrpc20Server -{ - public function handle($data) - { - $data = file_get_contents('php://stdin'); - return parent::handle($data); - } -} - -class WebJsonrpc20Server extends Jsonrpc20Server -{ - public function handle($data) - { - header('Content-type: application/json-rpc'); - $data = file_get_contents('php://input'); - $result = $this->_handle($data); - - header('HTTP/1.0 200'); // Signifying that the communication went well. - - return $this->_encode_json($result); - } -} -?>