Last active
January 31, 2017 00:10
-
-
Save beenhere4hours/83499f9fc1520cc3c9b2 to your computer and use it in GitHub Desktop.
PHP JSON RPC Peer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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