├── .gitignore ├── Client.php ├── composer.json ├── readme.md ├── Exception.php ├── traits ├── Request.php ├── Serializable.php └── Client.php └── Action.php /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | .idea 3 | /vendor -------------------------------------------------------------------------------- /Client.php: -------------------------------------------------------------------------------- 1 | url = $url; 17 | } 18 | 19 | public function __call($name, $arguments) 20 | { 21 | return $this->callServer($name, $arguments, $this->url); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nizsheanez/yii2-json-rpc", 3 | "description": "A lightweight JsonRpc Server and Client for PHP", 4 | "type": "yii2-extension", 5 | "license": "BSD-3-Clause", 6 | "keywords": ["yii", "yii2", "json", "rpc", "jsonrpc"], 7 | "minimum-stability": "dev", 8 | "authors": [ 9 | { 10 | "name": "Alex Sharov", 11 | "email": "www.pismeco@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4.0", 16 | "yiisoft/yii2": "*" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "nizsheanez\\jsonRpc\\": "" 21 | } 22 | }, 23 | "target-dir": "nizsheanez/jsonRpc" 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | JsonRpc Server and Client for Yii2 2 | 3 | 4 | ##Usage Server 5 | 6 | 1) Install with Composer 7 | 8 | ~~~php 9 | "require": { 10 | "nizsheanez/yii2-json-rpc": "1.*", 11 | }, 12 | 13 | php composer.phar update 14 | ~~~ 15 | 16 | 2) Add action to controller 17 | 18 | ~~~php 19 | public function actions() 20 | { 21 | return array( 22 | 'index' => array( 23 | 'class' => \nizsheanez\jsonRpc\Action::class, 24 | ), 25 | ); 26 | } 27 | 28 | public function sum($a, $b) { 29 | return $a + $b; 30 | } 31 | ~~~ 32 | 33 | 3) All methods of controller now available as JsonRpc methods, for example see method `sum`: 34 | 35 | ##Usage Client 36 | 37 | ~~~php 38 | $client = new \nizsheanez\JsonRpc\Client('http://url/of/webservice'); 39 | 40 | $response = $client->sum(2, 3); 41 | echo $response; 42 | ~~~ 43 | 44 | 4) Enjoy! 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Exception.php: -------------------------------------------------------------------------------- 1 | data = $data; 22 | parent::__construct($message, $code); 23 | } 24 | 25 | public function getErrorAsArray() 26 | { 27 | $result = [ 28 | 'code' => $this->getCode(), 29 | 'message' => $this->getMessage(), 30 | ]; 31 | if ($this->data !== null) { 32 | $result['data'] = $this->data; 33 | } 34 | return $result; 35 | } 36 | } -------------------------------------------------------------------------------- /traits/Request.php: -------------------------------------------------------------------------------- 1 | _requestMessage = $message; 13 | $this->_data = json_decode($message, true); 14 | } 15 | 16 | public function getParams($method) 17 | { 18 | $params = []; 19 | $args = isset($this->_data['params']) ? $this->_data['params'] : []; 20 | foreach ($method->getParameters() as $param) { 21 | /* @var $param ReflectionParameter */ 22 | if (isset($args[$param->getName()])) { 23 | $params[] = $args[$param->getName()]; 24 | } elseif ($param->isDefaultValueAvailable()) { 25 | $params[] = $param->getDefaultValue(); 26 | } else { 27 | $params[] = null; 28 | } 29 | } 30 | 31 | return $params; 32 | } 33 | 34 | public function getMethod() 35 | { 36 | return isset($this->_data['method']) ? $this->_data['method'] : null; 37 | } 38 | 39 | public function getRequestId() 40 | { 41 | return isset($this->_data['id']) ? $this->_data['id'] : $this->newId(); 42 | } 43 | } -------------------------------------------------------------------------------- /traits/Serializable.php: -------------------------------------------------------------------------------- 1 | _requestMessage, true); 15 | $answer = [ 16 | 'jsonrpc' => '2.0', 17 | 'id' => isset($request['id']) ? $request['id'] : $this->newId(), 18 | ]; 19 | if ($this->exception) { 20 | if ($this->exception instanceof Exception) { 21 | $answer['error'] = $this->exception->getErrorAsArray(); 22 | } else { 23 | $answer['error'] = [ 24 | 'code' => Exception::INTERNAL_ERROR, 25 | 'message' => $this->exception 26 | ]; 27 | } 28 | } 29 | $answer['result'] = $this->result; 30 | if (self::isValidJsonRpc($answer)) { 31 | $answer['error'] = [ 32 | 'code' => Exception::INTERNAL_ERROR, 33 | 'message' => 'Internal error' 34 | ]; 35 | } 36 | 37 | return json_encode($answer); 38 | } 39 | 40 | public function isSuccessResponse() 41 | { 42 | return !$this->exception; 43 | } 44 | 45 | public function newId() 46 | { 47 | return md5(microtime()); 48 | } 49 | 50 | public static function isValidJsonRpc($response) 51 | { 52 | $version = isset($response['jsonrpc']) && $response['jsonrpc'] == '2.0'; 53 | $method = isset($response['method']); 54 | $data = isset($response['result']) || isset($response['error']); 55 | $additional = true; 56 | if (isset($response['error'])) { 57 | $additional = isset($response['error']['code'], $response['error']['message']); 58 | } 59 | 60 | return $version && $method && $data && $additional; 61 | } 62 | 63 | public static function checkContentType() 64 | { 65 | return !empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/json-rpc'; 66 | } 67 | } -------------------------------------------------------------------------------- /traits/Client.php: -------------------------------------------------------------------------------- 1 | newId(); 11 | $id = 1; 12 | $request = [ 13 | 'jsonrpc' => '2.0', 14 | 'method' => $method, 15 | 'params' => $params, 16 | 'id' => $id 17 | ]; 18 | 19 | $ctx = $this->getHttpStreamContext($request); 20 | $jsonResponse = file_get_contents($url, false, $ctx); 21 | 22 | if ($jsonResponse === '') { 23 | throw new Exception('fopen failed', Exception::INTERNAL_ERROR); 24 | } 25 | 26 | $response = json_decode($jsonResponse); 27 | 28 | if ($response === null) { 29 | throw new Exception('JSON cannot be decoded', Exception::INTERNAL_ERROR); 30 | } 31 | 32 | if ($response->id != $id) { 33 | throw new Exception('Mismatched JSON-RPC IDs', Exception::INTERNAL_ERROR); 34 | } 35 | 36 | if (property_exists($response, 'error')) { 37 | throw new Exception($response->error->message, $response->error->code); 38 | } else if (property_exists($response, 'result')) { 39 | return $response->result; 40 | } else { 41 | throw new Exception('Invalid JSON-RPC response', Exception::INTERNAL_ERROR); 42 | } 43 | } 44 | 45 | public function getHttpStreamContext($request) 46 | { 47 | $jsonRequest = json_encode($request); 48 | 49 | $ctx = stream_context_create([ 50 | 'http' => [ 51 | 'method' => 'POST', 52 | 'header' => "Content-Type: application/json-rpc\r\n", 53 | 'content' => $jsonRequest 54 | ] 55 | ]); 56 | 57 | return $ctx; 58 | } 59 | 60 | public static function isValidRequest($request) 61 | { 62 | $version = isset($request['jsonrpc']) && $request['jsonrpc'] == '2.0'; 63 | $method = isset($request['method']); 64 | $id = isset($request['id']); 65 | return $version && $method && $id; 66 | } 67 | 68 | public function newId() 69 | { 70 | return md5(microtime()); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /Action.php: -------------------------------------------------------------------------------- 1 | failIfNotAJsonRpcRequest(); 21 | Yii::beginProfile('service.request'); 22 | $output = null; 23 | try { 24 | $this->setRequestMessage(file_get_contents('php://input')); 25 | $this->result = $this->tryToRunMethod(); 26 | } catch (Exception $e) { 27 | Yii::error($e, 'service.error'); 28 | $this->exception = new Exception($e->getMessage(), Exception::INTERNAL_ERROR); 29 | } 30 | Yii::endProfile('service.request'); 31 | 32 | Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; 33 | 34 | return $this->toJson(); 35 | } 36 | 37 | /** 38 | * @return string|callable|ReflectionMethod 39 | */ 40 | protected function getHandler() 41 | { 42 | $class = new ReflectionClass($this->controller); 43 | if (!$class->hasMethod($this->getMethod())) { 44 | throw new Exception("Method not found", Exception::METHOD_NOT_FOUND); 45 | } 46 | $method = $class->getMethod($this->getMethod()); 47 | 48 | return $method; 49 | } 50 | 51 | /** 52 | * @param string|callable|\ReflectionMethod $method 53 | * @param array $params 54 | * 55 | * @return mixed 56 | */ 57 | protected function runMethod($method, $params) 58 | { 59 | return $method->invokeArgs($this->controller, $params); 60 | } 61 | 62 | /** 63 | * @return mixed 64 | * @throws Exception 65 | */ 66 | protected function tryToRunMethod() 67 | { 68 | $method = $this->getHandler(); 69 | Yii::beginProfile('service.request.action'); 70 | $output = $this->runMethod($method, $this->getParams($method)); 71 | Yii::endProfile('service.request.action'); 72 | Yii::info($method, 'service.output'); 73 | Yii::info($output, 'service.output'); 74 | 75 | return $output; 76 | } 77 | 78 | /** 79 | * @throws HttpException 80 | */ 81 | protected function failIfNotAJsonRpcRequest() 82 | { 83 | if (!Yii::$app->request->isPost || !$this->checkContentType()) { 84 | throw new HttpException(404, "Page not found"); 85 | } 86 | } 87 | } 88 | --------------------------------------------------------------------------------