Files
phpMAE/classes/JsonRPCServer.php
T

460 lines
7.7 KiB
PHP

<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\PhpMAE;
use Psr\Http\Message\ResponseInterface;
use JsonRpc\Base\Rpc;
use JsonRpc\Base\Request;
use JsonRpc\Base\Response;
/**
* This is a modified version of JsonRPC\Server with custom enhancements for phpMAE.
*/
class JsonRPCServer
{
private $handler;
private $transport = null;
private $logger = null;
private $assoc = false;
private $requests = array();
private $responses = array();
private $error = null;
private $handlerError = null;
private $refClass = null;
public function __construct($methodHandler, $transport = null)
{
ini_set('display_errors', '0');
$this->handler = $methodHandler;
$this->transport = $transport;
if (!$this->transport)
{
$this->transport = new JsonRPCTransport;
}
}
public function receive($input = '')
{
$this->init();
try
{
$input = $input ?: $this->transport->receive();
$json = $this->process($input);
$this->transport->reply($json);
}
catch (\Exception $e)
{
$this->logException($e);
exit;
}
}
public function setTransport($transport)
{
$this->transport = $transport;
}
public function setLogger($logger)
{
$this->logger = $logger;
}
public function setObjectsAsArrays()
{
$this->assoc = true;
}
private function process($json)
{
if (!$struct = Rpc::decode($json, $batch))
{
$code = is_null($struct) ? Rpc::ERR_PARSE : Rpc::ERR_REQUEST;
$data = new Response();
$data->createStdError($code);
return $data->toJson();
}
$this->getRequests($struct);
$this->processRequests();
if (count($this->responses) == 1 && is_object($this->responses[0]))
return $this->responses[0];
$data = implode(',', $this->responses);
return $batch && $data ? '[' . $data . ']' : $data;
}
private function getRequests($struct)
{
if (is_array($struct))
{
foreach ($struct as $item)
{
$this->requests[] = new Request($item);
}
}
else
{
$this->requests[] = new Request($struct);
}
}
private function processRequests()
{
foreach ($this->requests as $request)
{
# check if we got an error parsing the request, otherwise process it
if ($request->fault)
{
$this->error = array(
'code' => Rpc::ERR_REQUEST,
'data' => $request->fault
);
# we always response to request errors
$this->addResponse($request, null);
continue;
}
$result = $this->processRequest($request->method, $request->params);
if (!$request->notification)
{
$this->addResponse($request, $result);
}
}
}
private function processRequest($method, $params)
{
$this->error = null;
if (!$callback = $this->getCallback($method))
{
$this->error = Rpc::ERR_METHOD;
return;
}
if (!$this->checkMethod($method, $params))
{
$this->error = Rpc::ERR_PARAMS;
return;
}
if ($this->assoc)
{
$this->castObjectsToArrays($params);
}
try
{
$result = call_user_func_array($callback, $params);
}
catch (\Exception $e)
{
$this->logException($e);
$this->error = Rpc::ERR_INTERNAL;
return;
}
if ($this->error = $this->getHandlerError())
{
$this->clearHandlerError();
}
return $result;
}
private function addResponse($request, $result)
{
if (is_object($result) && is_a($result, ResponseInterface::class)) {
$this->responses[] = $result;
return;
}
$ar = array(
'id' => $request->id
);
if ($this->error)
{
$ar['error'] = $this->error;
}
else
{
$ar['result'] = $result;
}
$response = new Response();
if (!$response->create($ar))
{
$this->logError($response->fault);
$response->createStdError(Rpc::ERR_INTERNAL, $request->id);
}
$this->responses[] = $response->toJson();
}
private function getCallback($method)
{
$callback = array($this->handler, $method);
if (is_callable($callback))
{
return $callback;
}
}
private function checkMethod($method, &$params)
{
try
{
if (!$this->refClass)
{
# we have already checked that handler is callable
$this->refClass = new \ReflectionClass($this->handler);
try
{
$prop = $this->refClass->getProperty('error');
if ($prop->isPublic())
{
$this->handlerError = $prop;
}
}
catch (\Exception $e){}
}
try
{
$refMethod = $this->refClass->getMethod($method);
}
catch (\Exception $e)
{
# we know we are callable, so the class must be implementing __call or __callStatic
$params = $this->getParams($params);
return true;
}
$res = true;
if (is_object($params))
{
$named = (array) $params;
$params = array();
$refParams = $refMethod->getParameters();
foreach ($refParams as $arg)
{
$argName = $arg->getName();
if (array_key_exists($argName, $named))
{
$params[] = $named[$argName];
unset($named[$argName]);
}
elseif (!$arg->isOptional())
{
$res = false;
break;
}
}
if ($extra = array_values($named))
{
$params = array_merge($params, $extra);
}
}
else
{
$params = $this->getParams($params);
$reqArgs = $refMethod->getNumberOfRequiredParameters();
$res = count($params) >= $reqArgs;
}
return $res;
}
catch (\Exception $e) {}
}
private function getParams($params)
{
if (is_object($params))
{
$params = array_values((array) $params);
}
elseif (is_null($params))
{
$params = array();
}
return $params;
}
private function castObjectsToArrays(&$params)
{
foreach ($params as &$param)
{
if (is_object($param))
{
$param = (array) $param;
}
}
}
private function getHandlerError()
{
if ($this->handlerError)
{
if ($this->handlerError->isStatic())
{
return $this->handlerError->getValue();
}
else
{
return $this->handlerError->getValue($this->handler);
}
}
}
private function clearHandlerError()
{
if ($this->handlerError)
{
if ($this->handlerError->isStatic())
{
return $this->handlerError->setValue(null);
}
else
{
return $this->handlerError->setValue($this->handler, null);
}
}
}
private function logException(\Exception $e)
{
$message = 'Exception: '. $e->getMessage();
$message .= ' in ' . $e->getFile() . ' on line ' . $e->getLine();
$this->logError($message);
}
private function logError($message)
{
try
{
if ($this->logger)
{
$callback = array($this->logger, 'addRecord');
$params = array(
500,
$message
);
$result = call_user_func_array($callback, $params);
}
else
{
error_log($message);
}
}
catch (\Exception $e)
{
error_log($e->__toString());
}
}
private function init()
{
$this->requests = array();
$this->responses = array();
$this->error = null;
$this->handlerError = null;
$this->refClass = null;
}
}