Skip to content

Instantly share code, notes, and snippets.

@beenhere4hours
Last active January 31, 2017 00:10
Show Gist options
  • Save beenhere4hours/83499f9fc1520cc3c9b2 to your computer and use it in GitHub Desktop.
Save beenhere4hours/83499f9fc1520cc3c9b2 to your computer and use it in GitHub Desktop.
PHP JSON RPC Peer
<?php namespace JSONRPC;
class jsonRPCClient {
/**
* Debug state
*
* @var boolean
*/
private $debug;
/**
* Host or endpoint for rpc call
*
* @var string
*/
private $url;
/**
* The request id used in validation and error objects
*
* @var mixed can be integer or string per spec
*/
private $id;
/**
* Array of json error codes, their http counterpart, and descriptions
*
* @var array
*/
private $json_rpc_error_code = array(
// json specific
'INVALID_JSONRPC_REQUEST' => array(
'jsonrpcCode' => -32600,
'httpCode' => 400,
'message' => 'Invalid json-rpc request',
'description' => 'The json passed does not conform to JSON-RPC.'),
'INVALID_ID_FORMAT' => array(
'jsonrpcCode' => -32000,
'httpCode' => 400,
'message' => 'Invalid id format',
'description' => 'The id passed does not conform to JSON-RPC.'),
'INVALID_METHOD_FORMAT' => array(
'jsonrpcCode' => -32601,
'httpCode' => 400,
'message' => 'Invalid method format',
'description' => 'The method name is not in a valid format.'),
'INVALID_PARAMETERS' => array(
'jsonrpcCode' => -32602,
'httpCode' => 400,
'message' => 'Invalid parameters',
'description' => 'The parameter field is missing or not an array.'),
// internal errors
'INTERNAL_SERVER_ERROR' => array(
'jsonrpcCode' => -32603,
'httpCode' => 500,
'message' => 'Internal Server Error',
'description' => 'An error occurred while processing the request.'),
'GATEWAY_TIMEOUT' => array(
'jsonrpcCode' => -32100,
'httpCode' => 504,
'message' => 'Gateway Timeout',
'description' => 'Possible network connectivity issue. Request has taken too long to complete.'),
'PARSE_ERROR' => array(
'jsonrpcCode' => -32700,
'httpCode' => 400,
'message' => 'Parse error',
'description' => 'An error occurred on the server while parsing the JSON text.')
);
/**
* Takes in host endpoint and debug parameters
*
* @param string $url
* @param boolean $debug
*/
public function __construct($url, $debug = FALSE) {
if (!extension_loaded('curl')) {
throw new LogicException('The curl extension is needed to use the jsonRPCClient');
}
// server URL
$this->url = $url;
// debug state
$this->debug = $debug;
}
/**
* Performs a jsonRCP request via curl. Returns results as an array.
*
* @param string $method
* @param array $params
* @param mixed $id if passed it is checked otherwise it is generated
* @return array
*/
public function execute($method = NULL, array $params = array(), $id = NULL) {
error_log('[jsonRPCClient execute] E');
$this->id = $id;
// check for any obvious errors before making request
$initialRequestError = $this->preRequestValidation($method, $params);
// passed first check call mid-tier
if ($initialRequestError === NULL) {
$response = $this->doRequest($this->getJSONBody($method, $params));
// failed first check get error object
} else {
$response = $this->getErrorObject($initialRequestError);
}
error_log('[jsonRPCClient execute] X');
return $response;
}
/**
* Performs curl request and validates the response
*
* @param array $json_body
* @return array
*/
private function doRequest($json_body) {
error_log('[jsonRPCClient doRequest] E');
// initialize handle
$ch = curl_init();
// set options
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // TODO what is the correct timeout time?
curl_setopt($ch, CURLOPT_USERAGENT, 'JSON-RPC PHP Client');
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Connection: close',
'Content-Type: application/json',
'Accept: application/json'
));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json_body));
// make request and disarm the bomb in the response
$result = $this->removeByteOrderMark(curl_exec($ch));
// validate result
$postRequestError = $this->postRequestValidation($result);
// passed post request check
if ($postRequestError === NULL) {
$response = json_decode($result, TRUE);
// failed request check get error object
} else {
$response = $this->getErrorObject($postRequestError);
}
// close handle
curl_close($ch);
if ($this->debug) {
error_log('[jsonRPCClient doRequest] request: ' . PHP_EOL . json_encode($json_body, JSON_PRETTY_PRINT));
error_log('[jsonRPCClient doRequest] response: ' . PHP_EOL . json_encode($response, JSON_PRETTY_PRINT));
}
error_log('[jsonRPCClient doRequest] X');
return $response;
}
/**
* Light weight initial validation prior to hitting mid-tier
*
* @param string $method
* @return mixed returns String if there is an error NULL if not
*/
private function preRequestValidation($method) {
$errorCode = NULL;
if (empty($method) || !is_string($method)) {
$errorCode = 'INVALID_METHOD_FORMAT';
} elseif (!is_string($this->id) || !is_numeric($this->id) || $this->id !== NULL) {
$errorCode = 'INVALID_ID_FORMAT';
}
return $errorCode;
}
/**
* Validate the response for curl errors and json decode errors
*
* @param object $result curl response object
* @return mixed returns String if there is an error NULL if not
*/
private function postRequestValidation($result) {
$errorCode = NULL;
// handle curl request errors
if ($result === FALSE) {
// TODO how to handle timeouts
$errorCode = 'INTERNAL_SERVER_ERROR';
// successful curl request
} else {
json_decode($result, TRUE);
// validate the json response
if (json_last_error() !== JSON_ERROR_NONE) {
$errorCode = 'PARSE_ERROR';
}
}
return $errorCode;
}
/**
* Remove binary characters from beginning of message.
*
* @param mixed $result response to be scrubbed
* @return mixed result with UTF-8 encoding and byte order mark removed
*/
private function removeByteOrderMark($result) {
// Based on BOM "http://en.wikipedia.org/wiki/Byte_order_mark" convert encoding
$cleanResult = mb_convert_encoding($result, 'UTF-8', mb_detect_encoding($result));
$BOM = pack( 'CCC', 239, 187, 191 );
// remove the bomb
while(0 === strpos($cleanResult, $BOM)) {
$cleanResult = substr($cleanResult, 3);
}
return $cleanResult;
}
/**
* Creates a response object with error object
*
* @param string $errorCode
* @return array
*/
private function getErrorObject($errorCode) {
return array(
'jsonrpc' => '2.0',
'id' => $this->id,
'error' => (object) array(
'code' => $this->json_rpc_error_code[$errorCode]['jsonrpcCode'],
'message' => $this->json_rpc_error_code[$errorCode]['message']
));
}
/**
* Creates an array to use for json rpc
*
* @param mixed $method
* @param array $params cast as object type on return
* @param mixed $id
* @return array
*/
private function getJSONBody($method, $params) {
return array(
'jsonrpc' => '2.0',
'method' => $method,
'params' => (object) $params,
'id' => $this->id
);
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment