|
Server : LiteSpeed System : Linux server900.shared.spaceship.host 4.18.0-553.89.1.lve.el8.x86_64 #1 SMP Wed Dec 10 13:58:50 UTC 2025 x86_64 User : kvzyqrttyz ( 5084) PHP Version : 8.1.34 Disable Function : NONE Directory : /opt/hc_php/unversioned/JsonRPC/ | |
|
Path: //opt/hc_php/unversioned/JsonRPC/Client.php
Size: 7.63 KB
Permissions: 0644
<?php
namespace JsonRPC;
use RuntimeException;
use BadFunctionCallException;
use InvalidArgumentException;
const TRANSPORT_TCP = 0;
const TRANSPORT_HTTP = 1;
// Retry config
const MAX_RETRIES = 5;
const RETRY_DELAY = 1; // seconds
class Client
{
/**
* RPC transport type
* @var int
*/
private $transport;
/**
* Path to the unix socket of the server
* @var string
*/
private $unix_socket_path;
/**
* HTTP client timeout
* @var int
*/
private $timeout;
/**
* Enable debug output to the php error log
* @var bool
*/
public $debug = false;
/**
* User that runs the script
* @var string
*/
private $user;
/**
* Default HTTP headers to send to the server
* @var string
*/
private $headers = "POST / HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nAccept: application/json\r\n";
/**
* Stream handle
*/
private $ch;
/**
* Constructor
*
* @param string $unix_socket_path Path to the unix socket
* @param int $timeout Timeout
* @param int $transport Transport type
*/
public function __construct($unix_socket_path, $timeout = 5, $transport = TRANSPORT_TCP)
{
$this->unix_socket_path = 'unix://'.$unix_socket_path;
$this->timeout = $timeout;
$this->transport = $transport;
$this->connect();
}
/**
* Destructor
*
* @access public
*/
public function __destruct()
{
if (is_resource($this->ch)) {
fclose($this->ch);
}
}
/**
* (Re)connect socket
*/
private function connect()
{
if (is_resource($this->ch)) {
@fclose($this->ch);
}
$this->ch = @stream_socket_client(
$this->unix_socket_path,
$errno,
$errstr,
$this->timeout
);
if (!$this->ch) {
throw new RuntimeException("$errstr ($errno)");
}
// Make reads honor timeout
stream_set_timeout($this->ch, $this->timeout);
}
/**
* Automatic mapping of methods
*
* @access public
* @param string $method Method name
* @param array $params Method arguments
* @return array
*/
public function __call($method, array $params)
{
return $this->execute($method, $params);
}
/**
* Execute a method
*/
public function execute($method, array $params = array())
{
return $this->parseResponse(
$this->doRequest($this->prepareRequest($method, $params))
);
}
/**
* Prepare the payload
*
* @access public
* @param string $method Method name
* @param array $params Method arguments
* @return array
*/
public function prepareRequest($method, array $params = array())
{
$payload = array(
'jsonrpc' => '2.0',
'method' => $method,
'id' => mt_rand()
);
if (!empty($params)) {
$payload['params'] = $params;
}
return $payload;
}
/**
* Parse the response and return the method result
*
* @access public
* @param array $payload
* @return mixed
*/
public function parseResponse(array $payload)
{
return $this->getResult($payload);
}
/**
* Get a RPC call result
*/
public function getResult(array $payload)
{
if (isset($payload['error']['code'])) {
$this->handleRpcErrors($payload['error']);
}
return isset($payload['result']) ? $payload['result'] : null;
}
/**
* Throw an exception according the RPC error
*/
public function handleRpcErrors($error)
{
switch ($error['code']) {
case -32601:
throw new BadFunctionCallException('Method not found: '.$error['message']);
case -32602:
throw new InvalidArgumentException('Invalid arguments: '.$error['message']);
default:
throw new RuntimeException('Invalid request/response: '.$error['message'], $error['code']);
}
}
/**
* Prepare http message
*/
private function prepareHttpMessage($headers, $body) {
return $headers . "Content-Length: ".strlen($body)."\r\n\r\n".$body;
}
/**
* Parse http message
*/
private function parseHttpMessage($text) {
$data = array();
$response = explode("\r\n", $text);
$header = preg_split("/\s+/", $response[0]);
$data["code"] = $header[1];
$data["body"] = "";
$is_body = false;
foreach (array_slice($response, 1) as $str) {
if ($is_body) {
$data["body"] .= $str;
continue;
}
$header = preg_split("/:\s+/", $str);
if ($header[0]) {
$data[$header[0]] = $header[1];
continue;
}
$is_body = true;
}
return $data;
}
/**
* Do the HTTP/TCP request with retry
*
* Retries up to MAX_RETRIES with RETRY_DELAY seconds between attempts,
* reconnecting the socket before each retry.
*/
public function doRequest($payload)
{
$json_payload = json_encode($payload);
$attempt = 1;
while (true) {
try {
$body = ($this->transport == TRANSPORT_TCP)
? $json_payload
: $this->prepareHttpMessage($this->headers, $json_payload);
if (@fwrite($this->ch, $body) === false) {
throw new RuntimeException('Socket write error');
}
$response = '';
while (($buffer = @fgets($this->ch)) !== false) {
$response .= $buffer;
if ($this->transport == TRANSPORT_TCP) {
// For raw TCP we expect a single JSON line
break;
}
}
// Check for timeout or empty read
$meta = is_resource($this->ch) ? stream_get_meta_data($this->ch) : array();
if (!$response || (!empty($meta['timed_out']) && $meta['timed_out'])) {
throw new RuntimeException('Socket read error');
}
$data = ($this->transport == TRANSPORT_TCP)
? $response
: $this->parseHttpMessage($response)["body"];
if ($this->debug) {
error_log('==> Request: '.PHP_EOL.json_encode($payload, JSON_PRETTY_PRINT));
error_log('==> Response: '.PHP_EOL.$response);
}
$result = json_decode($data, true);
return is_array($result) ? $result : array();
} catch (\Throwable $e) {
if ($attempt >= MAX_RETRIES) {
throw new RuntimeException(
"Failed after ".MAX_RETRIES." attempts: ".$e->getMessage(),
$e->getCode()
);
}
if ($this->debug) {
error_log(sprintf(
'[Attempt %d/%d] Error: %s. Retrying in %ds...',
$attempt, MAX_RETRIES, $e->getMessage(), RETRY_DELAY
));
}
// Wait then reconnect and retry
sleep(RETRY_DELAY);
$this->connect();
$attempt++;
continue;
}
}
}
}
?>