2013-07-28 21:44:51 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace JsonRPC;
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
use RuntimeException;
|
2014-09-19 18:54:48 +02:00
|
|
|
use BadFunctionCallException;
|
2014-12-24 03:28:26 +01:00
|
|
|
use InvalidArgumentException;
|
2014-09-19 18:54:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* JsonRPC client class
|
|
|
|
*
|
|
|
|
* @package JsonRPC
|
|
|
|
* @author Frederic Guillot
|
|
|
|
* @license Unlicense http://unlicense.org/
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
class Client
|
|
|
|
{
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
|
|
|
* URL of the server
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var string
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
private $url;
|
2014-09-19 18:54:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* HTTP client timeout
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var integer
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
private $timeout;
|
2014-09-19 18:54:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Username for authentication
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var string
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
private $username;
|
2014-09-19 18:54:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Password for authentication
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var string
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
private $password;
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
/**
|
|
|
|
* True for a batch request
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $is_batch = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Batch payload
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
public $batch = array();
|
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
|
|
|
* Enable debug output to the php error log
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @var boolean
|
|
|
|
*/
|
|
|
|
public $debug = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default HTTP headers to send to the server
|
|
|
|
*
|
|
|
|
* @access private
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
private $headers = array(
|
|
|
|
'Connection: close',
|
|
|
|
'Content-Type: application/json',
|
|
|
|
'Accept: application/json'
|
|
|
|
);
|
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $url Server URL
|
|
|
|
* @param integer $timeout Server URL
|
|
|
|
* @param array $headers Custom HTTP headers
|
|
|
|
*/
|
|
|
|
public function __construct($url, $timeout = 5, $headers = array())
|
2013-07-28 21:44:51 +02:00
|
|
|
{
|
|
|
|
$this->url = $url;
|
|
|
|
$this->timeout = $timeout;
|
|
|
|
$this->headers = array_merge($this->headers, $headers);
|
|
|
|
}
|
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
|
|
|
* Automatic mapping of procedures
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $method Procedure name
|
|
|
|
* @param array $params Procedure arguments
|
|
|
|
* @return mixed
|
|
|
|
*/
|
2014-12-24 03:28:26 +01:00
|
|
|
public function __call($method, array $params)
|
2014-02-08 20:13:14 +01:00
|
|
|
{
|
2014-12-24 03:28:26 +01:00
|
|
|
// Allow to pass an array and use named arguments
|
|
|
|
if (count($params) === 1 && is_array($params[0])) {
|
|
|
|
$params = $params[0];
|
|
|
|
}
|
|
|
|
|
2014-02-08 20:13:14 +01:00
|
|
|
return $this->execute($method, $params);
|
|
|
|
}
|
2014-09-19 18:54:48 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set authentication parameters
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $username Username
|
|
|
|
* @param string $password Password
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
public function authentication($username, $password)
|
|
|
|
{
|
|
|
|
$this->username = $username;
|
|
|
|
$this->password = $password;
|
|
|
|
}
|
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
2014-12-24 03:28:26 +01:00
|
|
|
* Start a batch request
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return Client
|
|
|
|
*/
|
|
|
|
public function batch()
|
|
|
|
{
|
|
|
|
$this->is_batch = true;
|
|
|
|
$this->batch = array();
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Send a batch request
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function send()
|
|
|
|
{
|
|
|
|
$this->is_batch = false;
|
|
|
|
|
|
|
|
return $this->parseResponse(
|
|
|
|
$this->doRequest($this->batch)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a procedure
|
2014-09-19 18:54:48 +02:00
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $procedure Procedure name
|
|
|
|
* @param array $params Procedure arguments
|
|
|
|
* @return mixed
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
public function execute($procedure, array $params = array())
|
|
|
|
{
|
2014-12-24 03:28:26 +01:00
|
|
|
if ($this->is_batch) {
|
|
|
|
$this->batch[] = $this->prepareRequest($procedure, $params);
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->parseResponse(
|
|
|
|
$this->doRequest($this->prepareRequest($procedure, $params))
|
|
|
|
);
|
|
|
|
}
|
2013-07-28 21:44:51 +02:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
/**
|
|
|
|
* Prepare the payload
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $procedure Procedure name
|
|
|
|
* @param array $params Procedure arguments
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function prepareRequest($procedure, array $params = array())
|
|
|
|
{
|
2013-07-28 21:44:51 +02:00
|
|
|
$payload = array(
|
|
|
|
'jsonrpc' => '2.0',
|
|
|
|
'method' => $procedure,
|
2014-12-24 03:28:26 +01:00
|
|
|
'id' => mt_rand()
|
2013-07-28 21:44:51 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (! empty($params)) {
|
|
|
|
$payload['params'] = $params;
|
|
|
|
}
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
return $payload;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse the response and return the procedure result
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param array $payload
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function parseResponse(array $payload)
|
|
|
|
{
|
|
|
|
if ($this->isBatchResponse($payload)) {
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
|
|
|
|
foreach ($payload as $response) {
|
|
|
|
$results[] = $this->getResult($response);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->getResult($payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return true if we have a batch response
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param array $payload
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
private function isBatchResponse(array $payload)
|
|
|
|
{
|
|
|
|
return array_keys($payload) === range(0, count($payload) - 1);
|
|
|
|
}
|
2013-07-28 21:44:51 +02:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
/**
|
|
|
|
* Get a RPC call result
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param array $payload
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getResult(array $payload)
|
|
|
|
{
|
|
|
|
if (isset($payload['error']['code'])) {
|
|
|
|
$this->handleRpcErrors($payload['error']['code']);
|
2013-07-28 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
return isset($payload['result']) ? $payload['result'] : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Throw an exception according the RPC error
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param integer $code
|
|
|
|
*/
|
|
|
|
public function handleRpcErrors($code)
|
|
|
|
{
|
|
|
|
switch ($code) {
|
|
|
|
case -32601:
|
|
|
|
throw new BadFunctionCallException('Procedure not found');
|
|
|
|
case -32602:
|
|
|
|
throw new InvalidArgumentException('Invalid arguments');
|
|
|
|
default:
|
|
|
|
throw new RuntimeException('Invalid request/response');
|
|
|
|
}
|
2013-07-28 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
/**
|
|
|
|
* Do the HTTP request
|
|
|
|
*
|
|
|
|
* @access public
|
|
|
|
* @param string $payload Data to send
|
|
|
|
*/
|
2013-07-28 21:44:51 +02:00
|
|
|
public function doRequest($payload)
|
|
|
|
{
|
|
|
|
$ch = curl_init();
|
|
|
|
|
|
|
|
curl_setopt($ch, CURLOPT_URL, $this->url);
|
|
|
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
|
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'JSON-RPC PHP Client');
|
|
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
|
|
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
|
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
|
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
|
|
|
|
|
|
|
if ($this->username && $this->password) {
|
|
|
|
curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password);
|
|
|
|
}
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
$http_body = curl_exec($ch);
|
|
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
|
|
|
|
if ($http_code === 401 || $http_code === 403) {
|
|
|
|
throw new RuntimeException('Access denied');
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = json_decode($http_body, true);
|
2013-07-28 21:44:51 +02:00
|
|
|
|
2014-09-19 18:54:48 +02:00
|
|
|
if ($this->debug) {
|
|
|
|
error_log('==> Request: '.PHP_EOL.json_encode($payload, JSON_PRETTY_PRINT));
|
|
|
|
error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT));
|
|
|
|
}
|
|
|
|
|
2013-07-28 21:44:51 +02:00
|
|
|
curl_close($ch);
|
|
|
|
|
|
|
|
return is_array($response) ? $response : array();
|
|
|
|
}
|
|
|
|
}
|