Merge pull request #4113 from dokuwiki/jsonrpc-conformity

JSONRPC Standards
This commit is contained in:
Andreas Gohr 2023-11-29 16:09:34 +01:00 committed by GitHub
commit f0319d45ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 129 additions and 20 deletions

View File

@ -9,6 +9,9 @@ class JsonRpcServer
{
protected $remote;
/** @var float The XML-RPC Version. 0 is our own simplified variant */
protected $version = 0;
/**
* JsonRpcServer constructor.
*/
@ -46,16 +49,113 @@ class JsonRpcServer
throw new RemoteException("JSON-RPC server only accepts application/json requests.", -32606);
}
$call = $INPUT->server->str('PATH_INFO');
$call = trim($call, '/');
try {
$args = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
$body = file_get_contents('php://input');
if($body !== '') {
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
} else {
$data = [];
}
} catch (\Exception $e) {
$args = [];
http_status(400);
throw new RemoteException("JSON-RPC server only accepts valid JSON.", -32700);
}
if (!is_array($args)) $args = [];
return $this->call($call, $args);
return $this->createResponse($data);
}
/**
* This executes the method and returns the result
*
* This should handle all JSON-RPC versions and our simplified version
*
* @link https://en.wikipedia.org/wiki/JSON-RPC
* @link https://www.jsonrpc.org/specification
* @param array $data
* @return array
* @throws RemoteException
*/
protected function createResponse($data)
{
global $INPUT;
$return = [];
if (isset($data['method'])) {
// this is a standard conform request (at least version 1.0)
$method = $data['method'];
$params = $data['params'] ?? [];
$this->version = 1;
// always return the same ID
if (isset($data['id'])) $return['id'] = $data['id'];
// version 2.0 request
if (isset($data['jsonrpc'])) {
$return['jsonrpc'] = $data['jsonrpc'];
$this->version = (float)$data['jsonrpc'];
}
// version 1.1 request
if (isset($data['version'])) {
$return['version'] = $data['version'];
$this->version = (float)$data['version'];
}
} else {
// this is a simplified request
$method = $INPUT->server->str('PATH_INFO');
$method = trim($method, '/');
$params = $data;
$this->version = 0;
}
// excute the method
$return['result'] = $this->call($method, $params);
$this->addErrorData($return); // handles non-error info
return $return;
}
/**
* Create an error response
*
* @param \Exception $exception
* @return array
*/
public function returnError($exception)
{
$return = [];
$this->addErrorData($return, $exception);
return $return;
}
/**
* Depending on the requested version, add error data to the response
*
* @param array $response
* @param \Exception|null $e
* @return void
*/
protected function addErrorData(&$response, $e = null)
{
if ($e !== null) {
// error occured, add to response
$response['error'] = [
'code' => $e->getCode(),
'message' => $e->getMessage()
];
} else {
// no error, act according to version
if ($this->version > 0 && $this->version < 2) {
// version 1.* wants null
$response['error'] = null;
} elseif ($this->version < 1) {
// simplified version wants success
$response['error'] = [
'code' => 0,
'message' => 'success'
];
}
// version 2 wants no error at all
}
}
/**
@ -68,6 +168,13 @@ class JsonRpcServer
*/
public function call($methodname, $args)
{
if (!array_is_list($args)) {
throw new RemoteException(
"server error. arguments need to passed as list. named arguments not supported",
-32602
);
}
try {
$result = $this->remote->call($methodname, $args);
return $result;

View File

@ -119,3 +119,17 @@ if (!function_exists('str_ends_with')) {
return empty($needle) || substr($haystack, -strlen($needle)) === $needle;
}
}
/**
* polyfill for PHP < 8.1
* @see https://www.php.net/manual/en/function.array-is-list
*/
if (!function_exists('array_is_list')) {
function array_is_list(array $arr)
{
if ($arr === []) {
return true;
}
return array_keys($arr) === range(0, count($arr) - 1);
}
}

View File

@ -11,21 +11,9 @@ header('Content-Type: application/json');
$server = new JsonRpcServer();
try {
$result = [
'error' => [
'code' => 0,
'message' => 'success'
],
'data' => $server->serve(),
];
$result = $server->serve();
} catch (\Exception $e) {
$result = [
'error' => [
'code' => $e->getCode(),
'message' => $e->getMessage()
],
'data' => null,
];
$result = $server->returnError($e);
}
echo json_encode($result, JSON_THROW_ON_ERROR);