[IMPROVED] jsonRPC Error Handling with CURL

* [ADDED] Use curl instead of fopen
* [ADDED] Error handling for various connection issues
* [MOVED] jsonRPC library into lib folder
* [UPDATED] Pools page for proper RPC errors with caching enabled

It's using the base RPC class but modified to support CURL. Simplified
some code since we won't need those features. Should make maintaining
that code a whole lot easier.

Fixes #1343 once merged.
This commit is contained in:
Sebastian Grewe 2014-01-15 09:37:58 +01:00
parent 290ac36729
commit aa27e8dfde
4 changed files with 146 additions and 204 deletions

View File

@ -229,24 +229,7 @@ class Bitcoin {
}
}
/**
* Exception class for BitcoinClient
*
* @author Mike Gogulski
* http://www.gogulski.com/ http://www.nostate.com/
*/
class BitcoinClientException extends ErrorException {
// Redefine the exception so message isn't optional
public function __construct($message, $code = 0, $severity = E_USER_NOTICE, Exception $previous = null) {
parent::__construct($message, $code, $severity, $previous);
}
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
require_once(INCLUDE_DIR . "/jsonRPCClient.php");
require_once(INCLUDE_DIR . "/lib/jsonRPCClient.php");
/**
* Bitcoin client class for access to a Bitcoin server via JSON-RPC-HTTP[S]
@ -283,21 +266,21 @@ class BitcoinClient extends jsonRPCClient {
* @access public
* @throws BitcoinClientException
*/
public function __construct($scheme, $username, $password, $address = "localhost", $port = 8332, $certificate_path = '', $debug_level = 0) {
public function __construct($scheme, $username, $password, $address = "localhost", $port = 8332, $certificate_path = '', $debug = true) {
$scheme = strtolower($scheme);
if ($scheme != "http" && $scheme != "https")
throw new BitcoinClientException("Scheme must be http or https");
throw new Exception("Scheme must be http or https");
if (empty($username))
throw new BitcoinClientException("Username must be non-blank");
throw new Exception("Username must be non-blank");
if (empty($password))
throw new BitcoinClientException("Password must be non-blank");
throw new Exception("Password must be non-blank");
$port = (string) $port;
if (!$port || empty($port) || !is_numeric($port) || $port < 1 || $port > 65535 || floatval($port) != intval($port))
throw new BitcoinClientException("Port must be an integer and between 1 and 65535");
throw new Exception("Port must be an integer and between 1 and 65535");
if (!empty($certificate_path) && !is_readable($certificate_path))
throw new BitcoinClientException("Certificate file " . $certificate_path . " is not readable");
throw new Exception("Certificate file " . $certificate_path . " is not readable");
$uri = $scheme . "://" . $username . ":" . $password . "@" . $address . "/";
parent::__construct($uri);
parent::__construct($uri, $debug);
}
/**

View File

@ -1,167 +0,0 @@
<?php
/*
COPYRIGHT
Copyright 2007 Sergio Vaccaro <sergio@inservibile.org>
This file is part of JSON-RPC PHP.
JSON-RPC PHP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
JSON-RPC PHP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JSON-RPC PHP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* The object of this class are generic jsonRPC 1.0 clients
* http://json-rpc.org/wiki/specification
*
* @author sergio <jsonrpcphp@inservibile.org>
*/
class jsonRPCClient {
/**
* Debug state
*
* @var boolean
*/
private $debug;
/**
* The server URL
*
* @var string
*/
private $url;
/**
* The request id
*
* @var integer
*/
private $id;
/**
* If true, notifications are performed instead of requests
*
* @var boolean
*/
private $notification = false;
/**
* Takes the connection parameters
*
* @param string $url
* @param boolean $debug
*/
public function __construct($url,$debug = false) {
// server URL
$this->url = $url;
// proxy
empty($proxy) ? $this->proxy = '' : $this->proxy = $proxy;
// debug state
empty($debug) ? $this->debug = false : $this->debug = true;
// message id
$this->id = 1;
}
/**
* Sets the notification state of the object. In this state, notifications are performed, instead of requests.
*
* @param boolean $notification
*/
public function setRPCNotification($notification) {
empty($notification) ?
$this->notification = false
:
$this->notification = true;
}
/**
* Performs a jsonRCP request and gets the results as an array
*
* @param string $method
* @param array $params
* @return array
*/
public function __call($method,$params) {
// check
if (!is_scalar($method)) {
throw new Exception('Method name has no scalar value');
}
// check
if (is_array($params)) {
// no keys
$params = array_values($params);
} else {
throw new Exception('Params must be given as array');
}
// sets notification or request task
if ($this->notification) {
$currentId = NULL;
} else {
$currentId = $this->id;
}
// prepares the request
$request = array(
'method' => $method,
'params' => $params,
'id' => $currentId
);
$request = json_encode($request);
$this->debug && $this->debug.='***** Request *****'."\n".$request."\n".'***** End Of request *****'."\n\n";
// performs the HTTP POST
$opts = array ('http' => array (
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $request
));
$context = stream_context_create($opts);
if ($fp = @fopen($this->url, 'r', false, $context)) {
$response = '';
while($row = fgets($fp)) {
$response.= trim($row)."\n";
}
$this->debug && $this->debug.='***** Server response *****'."\n".$response.'***** End of server response *****'."\n";
$response = json_decode($response,true);
} else {
$error = error_get_last();
$error = explode(']: ', $error['message']);
throw new Exception('Unable to connect: ' . $error[1]);
}
// debug output
if ($this->debug) {
echo nl2br($debug);
}
// final checks and return
if (!$this->notification) {
// check
if ($response['id'] != $currentId) {
throw new Exception('Incorrect response id (request id: '.$currentId.', response id: '.$response['id'].')');
}
if (!is_null($response['error'])) {
throw new Exception('Request error: '.$response['error']);
}
return $response['result'];
} else {
return true;
}
}
}
?>

View File

@ -0,0 +1,125 @@
<?php
/*
COPYRIGHT
Copyright 2007 Sergio Vaccaro <sergio@inservibile.org>
This file is part of JSON-RPC PHP.
JSON-RPC PHP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
JSON-RPC PHP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JSON-RPC PHP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* The object of this class are generic jsonRPC 1.0 clients
* http://json-rpc.org/wiki/specification
*
* @author sergio <jsonrpcphp@inservibile.org>
*/
class jsonRPCClient {
/**
* Debug state
*
* @var boolean
*/
private $debug;
private $debug_output;
/**
* The server URL
*
* @var string
*/
private $url;
/**
* The request id
*
* @var integer
*/
private $id;
/**
* Takes the connection parameters
*
* @param string $url
* @param boolean $debug
*/
public function __construct($url, $debug = false) {
$this->url = $url;
$this->debug = $debug;
$this->debug_output = '';
$this->id = rand(1, 100);
}
/**
* Fetch debug information
* @param none
* @return array Debug data
**/
public function getDebugData() {
return $this->debug_output;
}
/**
* Performs a jsonRCP request and gets the results as an array
*
* @param string $method
* @param array $params
* @return array
*/
public function __call($method, $params) {
if (!is_scalar($method)) throw new Exception('Method name has no scalar value');
if (is_array($params)) {
// no keys
$params = array_values($params);
} else {
throw new Exception('Params must be given as array');
}
// prepares the request
$request = array(
'method' => $method,
'params' => $params,
'id' => $this->id
);
$request = json_encode($request);
if ($this->debug) $this->debug_output[] = 'Request: '.$request;
// performs the HTTP POST
// extract information from URL for proper authentication
$url = parse_url($this->url);
$ch = curl_init($url['scheme'].'://'.$url['host'].':'.$url['port'].$url['path']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_setopt($ch, CURLOPT_USERPWD, $url['user'] . ":" . $url['pass']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
$response = json_decode(curl_exec($ch), true);
$resultStatus = curl_getinfo($ch);
if ($resultStatus['http_code'] != '200') {
if ($resultStatus['http_code'] == '401') throw new Exception('RPC call did not return 200: Authentication failed');
throw new Exception('RPC call did not return 200: HTTP error: ' . $resultStatus['http_code']);
}
if (curl_errno($ch)) throw new Exception('RPC call failed: ' . curl_error($ch));
if ($this->debug) $this->debug_output[] = 'Response: ' . $response;
curl_close($ch);
// final checks and return
if (!is_null($response['error']) || is_null($response)) throw new Exception('Response error or empty: ' . @$response['error']);
if ($response['id'] != $this->id) throw new Exception('Incorrect response id (request id: ' . $this->id . ', response id: ' . $response['id'] . ')');
return $response['result'];
}
}

View File

@ -3,20 +3,21 @@
// Make sure we are called from index.php
if (!defined('SECURITY')) die('Hacking attempt');
// Fetch data from wallet, always run this check
if ($bitcoin->can_connect() === true){
$dDifficulty = $bitcoin->getdifficulty();
$dNetworkHashrate = $bitcoin->getnetworkhashps();
$iBlock = $bitcoin->getblockcount();
is_int($iBlock) && $iBlock > 0 ? $sBlockHash = $bitcoin->getblockhash($iBlock) : $sBlockHash = '';
} else {
$dDifficulty = 1;
$dNetworkHashrate = 1;
$iBlock = 0;
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
}
if (!$smarty->isCached('master.tpl', $smarty_cache_key)) {
$debug->append('No cached version available, fetching from backend', 3);
// Fetch data from wallet
if ($bitcoin->can_connect() === true){
$dDifficulty = $bitcoin->getdifficulty();
$dNetworkHashrate = $bitcoin->getnetworkhashps();
$iBlock = $bitcoin->getblockcount();
is_int($iBlock) && $iBlock > 0 ? $sBlockHash = $bitcoin->getblockhash($iBlock) : $sBlockHash = '';
} else {
$dDifficulty = 1;
$dNetworkHashrate = 1;
$iBlock = 0;
$_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service: ' . $bitcoin->can_connect(), 'TYPE' => 'errormsg');
}
// Top share contributors
$aContributorsShares = $statistics->getTopContributors('shares', 15);