初始化代码
This commit is contained in:
72
vendor/topthink/think-swoole/src/rpc/Error.php
vendored
Normal file
72
vendor/topthink/think-swoole/src/rpc/Error.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc;
|
||||
|
||||
class Error implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $code = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = '';
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @param string $message
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return Error
|
||||
*/
|
||||
public static function make(int $code, string $message, $data = null): self
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
$instance->code = $code;
|
||||
$instance->message = $message;
|
||||
$instance->data = $data;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCode(): int
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'code' => $this->code,
|
||||
'message' => $this->message,
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
}
|
||||
124
vendor/topthink/think-swoole/src/rpc/JsonParser.php
vendored
Normal file
124
vendor/topthink/think-swoole/src/rpc/JsonParser.php
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc;
|
||||
|
||||
use Exception;
|
||||
use think\swoole\contract\rpc\ParserInterface;
|
||||
|
||||
class JsonParser implements ParserInterface
|
||||
{
|
||||
/**
|
||||
* Json-rpc version
|
||||
*/
|
||||
const VERSION = '2.0';
|
||||
|
||||
const DELIMITER = "@";
|
||||
|
||||
/**
|
||||
* @param Protocol $protocol
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encode(Protocol $protocol): string
|
||||
{
|
||||
$interface = $protocol->getInterface();
|
||||
$methodName = $protocol->getMethod();
|
||||
|
||||
$method = $interface . self::DELIMITER . $methodName;
|
||||
$data = [
|
||||
'jsonrpc' => self::VERSION,
|
||||
'method' => $method,
|
||||
'params' => $protocol->getParams(),
|
||||
'id' => '',
|
||||
];
|
||||
|
||||
$string = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public function decode(string $string): Protocol
|
||||
{
|
||||
$data = json_decode($string, true);
|
||||
|
||||
$error = json_last_error();
|
||||
if ($error != JSON_ERROR_NONE) {
|
||||
throw new Exception(
|
||||
sprintf('Data(%s) is not json format!', $string)
|
||||
);
|
||||
}
|
||||
|
||||
$method = $data['method'] ?? '';
|
||||
$params = $data['params'] ?? [];
|
||||
|
||||
if (empty($method)) {
|
||||
throw new Exception(
|
||||
sprintf('Method(%s) cant not be empty!', $string)
|
||||
);
|
||||
}
|
||||
|
||||
$methodAry = explode(self::DELIMITER, $method);
|
||||
if (count($methodAry) < 2) {
|
||||
throw new Exception(
|
||||
sprintf('Method(%s) is bad format!', $method)
|
||||
);
|
||||
}
|
||||
|
||||
[$interfaceClass, $methodName] = $methodAry;
|
||||
|
||||
if (empty($interfaceClass) || empty($methodName)) {
|
||||
throw new Exception(
|
||||
sprintf('Interface(%s) or Method(%s) can not be empty!', $interfaceClass, $method)
|
||||
);
|
||||
}
|
||||
|
||||
return Protocol::make($interfaceClass, $methodName, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function decodeResponse(string $string)
|
||||
{
|
||||
$data = json_decode($string, true);
|
||||
|
||||
if (array_key_exists('result', $data)) {
|
||||
return $data['result'];
|
||||
}
|
||||
|
||||
$code = $data['error']['code'] ?? 0;
|
||||
$message = $data['error']['message'] ?? '';
|
||||
$data = $data['error']['data'] ?? null;
|
||||
|
||||
return Error::make($code, $message, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $result
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function encodeResponse($result): string
|
||||
{
|
||||
$data = [
|
||||
'jsonrpc' => self::VERSION,
|
||||
];
|
||||
|
||||
if ($result instanceof Error) {
|
||||
$data['error'] = $result;
|
||||
} else {
|
||||
$data['result'] = $result;
|
||||
}
|
||||
|
||||
$string = json_encode($data);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
67
vendor/topthink/think-swoole/src/rpc/Protocol.php
vendored
Normal file
67
vendor/topthink/think-swoole/src/rpc/Protocol.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc;
|
||||
|
||||
class Protocol
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $interface = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $params = [];
|
||||
|
||||
/**
|
||||
* Replace constructor
|
||||
*
|
||||
* @param string $interface
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
*
|
||||
* @return Protocol
|
||||
*/
|
||||
public static function make(string $interface, string $method, array $params)
|
||||
{
|
||||
$instance = new static();
|
||||
|
||||
$instance->interface = $interface;
|
||||
$instance->method = $method;
|
||||
$instance->params = $params;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInterface(): string
|
||||
{
|
||||
return $this->interface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
}
|
||||
87
vendor/topthink/think-swoole/src/rpc/client/Client.php
vendored
Normal file
87
vendor/topthink/think-swoole/src/rpc/client/Client.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc\client;
|
||||
|
||||
use think\swoole\contract\rpc\ParserInterface;
|
||||
use think\swoole\exception\RpcClientException;
|
||||
|
||||
/**
|
||||
* Class Client
|
||||
* @package think\swoole\rpc\client
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
protected $host;
|
||||
protected $port;
|
||||
protected $timeout;
|
||||
protected $options;
|
||||
|
||||
/** @var \Swoole\Coroutine\Client */
|
||||
protected $handler;
|
||||
|
||||
public function __construct($host, $port, $timeout = 0.5, $options = [])
|
||||
{
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->timeout = $timeout;
|
||||
$this->options = $options;
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
public function sendAndRecv(string $data, bool $reconnect = false)
|
||||
{
|
||||
if ($reconnect) {
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->send($data)) {
|
||||
throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode);
|
||||
}
|
||||
|
||||
$result = $this->handler->recv();
|
||||
|
||||
if ($result === false || empty($result)) {
|
||||
throw new RpcClientException(swoole_strerror($this->handler->errCode), $this->handler->errCode);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (RpcClientException $e) {
|
||||
if ($reconnect) {
|
||||
throw $e;
|
||||
}
|
||||
return $this->sendAndRecv($data, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function send($data)
|
||||
{
|
||||
return $this->handler->send($data . ParserInterface::EOF);
|
||||
}
|
||||
|
||||
protected function connect()
|
||||
{
|
||||
$client = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
|
||||
|
||||
$client->set([
|
||||
'open_eof_check' => true,
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => ParserInterface::EOF,
|
||||
]);
|
||||
|
||||
if (!$client->connect($this->host, $this->port, $this->timeout)) {
|
||||
throw new RpcClientException(
|
||||
sprintf('Connect failed host=%s port=%d', $this->host, $this->port)
|
||||
);
|
||||
}
|
||||
|
||||
$this->handler = $client;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->handler) {
|
||||
$this->handler->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
vendor/topthink/think-swoole/src/rpc/client/Connection.php
vendored
Normal file
15
vendor/topthink/think-swoole/src/rpc/client/Connection.php
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc\client;
|
||||
|
||||
use think\swoole\concerns\InteractsWithPoolConnector;
|
||||
|
||||
/**
|
||||
* Class Connection
|
||||
* @package think\swoole\rpc\client
|
||||
* @mixin Client
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
use InteractsWithPoolConnector;
|
||||
}
|
||||
57
vendor/topthink/think-swoole/src/rpc/client/Pool.php
vendored
Normal file
57
vendor/topthink/think-swoole/src/rpc/client/Pool.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc\client;
|
||||
|
||||
use Swoole\Coroutine\Channel;
|
||||
use think\helper\Arr;
|
||||
use think\swoole\concerns\InteractsWithPool;
|
||||
|
||||
class Pool
|
||||
{
|
||||
use InteractsWithPool;
|
||||
|
||||
protected $clients;
|
||||
|
||||
public function __construct($clients)
|
||||
{
|
||||
$this->clients = $clients;
|
||||
}
|
||||
|
||||
protected function getPoolMaxActive($name)
|
||||
{
|
||||
return $this->getClientConfig($name, 'max_active', 3);
|
||||
}
|
||||
|
||||
protected function getPoolMaxWaitTime($name)
|
||||
{
|
||||
return $this->getClientConfig($name, 'max_wait_time', 3);
|
||||
}
|
||||
|
||||
public function getClientConfig($client, $name, $default = null)
|
||||
{
|
||||
return Arr::get($this->clients, $client . "." . $name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return Connection
|
||||
*/
|
||||
public function connect($name)
|
||||
{
|
||||
return $this->getPoolConnection($name);
|
||||
}
|
||||
|
||||
protected function buildPoolConnection($client, Channel $pool)
|
||||
{
|
||||
return new Connection($client, $pool);
|
||||
}
|
||||
|
||||
protected function createPoolConnection(string $name)
|
||||
{
|
||||
$host = $this->getClientConfig($name, 'host', '127.0.0.1');
|
||||
$port = $this->getClientConfig($name, 'port', 9000);
|
||||
$timeout = $this->getClientConfig($name, 'timeout', 0.5);
|
||||
|
||||
return new Client($host, $port, $timeout);
|
||||
}
|
||||
}
|
||||
102
vendor/topthink/think-swoole/src/rpc/client/Proxy.php
vendored
Normal file
102
vendor/topthink/think-swoole/src/rpc/client/Proxy.php
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc\client;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Nette\PhpGenerator\Factory;
|
||||
use Nette\PhpGenerator\PhpFile;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use think\swoole\contract\rpc\ParserInterface;
|
||||
use think\swoole\exception\RpcResponseException;
|
||||
use think\swoole\rpc\Error;
|
||||
use think\swoole\rpc\JsonParser;
|
||||
use think\swoole\rpc\Protocol;
|
||||
|
||||
class Proxy
|
||||
{
|
||||
protected $client;
|
||||
protected $interface;
|
||||
|
||||
/** @var Pool */
|
||||
protected $pool;
|
||||
|
||||
/** @var ParserInterface */
|
||||
protected $parser;
|
||||
|
||||
public function __construct(Pool $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
|
||||
$parserClass = $this->pool->getClientConfig($this->client, 'parser', JsonParser::class);
|
||||
$this->parser = new $parserClass;
|
||||
}
|
||||
|
||||
protected function proxyCall($method, $params)
|
||||
{
|
||||
$protocol = Protocol::make($this->interface, $method, $params);
|
||||
$data = $this->parser->encode($protocol);
|
||||
|
||||
$client = $this->pool->connect($this->client);
|
||||
|
||||
$response = $client->sendAndRecv($data);
|
||||
|
||||
$client->release();
|
||||
|
||||
$result = $this->parser->decodeResponse($response);
|
||||
|
||||
if ($result instanceof Error) {
|
||||
throw new RpcResponseException($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getClassName($interface)
|
||||
{
|
||||
if (!interface_exists($interface)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('%s must be exist interface!', $interface)
|
||||
);
|
||||
}
|
||||
|
||||
$name = constant($interface . "::RPC");
|
||||
$proxyName = class_basename($interface) . "Service";
|
||||
$className = "rpc\\service\\${name}\\{$proxyName}";
|
||||
|
||||
if (!class_exists($className, false)) {
|
||||
$file = new PhpFile;
|
||||
$namespace = $file->addNamespace("rpc\\service\\${name}");
|
||||
$namespace->addUse(Proxy::class);
|
||||
$namespace->addUse($interface);
|
||||
|
||||
$class = $namespace->addClass($proxyName);
|
||||
|
||||
$class->setExtends(Proxy::class);
|
||||
$class->addImplement($interface);
|
||||
$class->addProperty('client', $name);
|
||||
$class->addProperty('interface', class_basename($interface));
|
||||
|
||||
$reflection = new ReflectionClass($interface);
|
||||
|
||||
foreach ($reflection->getMethods() as $methodRef) {
|
||||
$method = (new Factory)->fromMethodReflection($methodRef);
|
||||
$method->setBody("return \$this->proxyCall('{$methodRef->getName()}', func_get_args());");
|
||||
$class->addMember($method);
|
||||
}
|
||||
|
||||
if (function_exists('eval')) {
|
||||
eval($file);
|
||||
} else {
|
||||
$proxyFile = sprintf('%s/%s.php', sys_get_temp_dir(), $proxyName);
|
||||
$result = file_put_contents($proxyFile, $file);
|
||||
if ($result === false) {
|
||||
throw new RuntimeException(sprintf('Proxy file(%s) generate fail', $proxyFile));
|
||||
}
|
||||
require $proxyFile;
|
||||
unlink($proxyFile);
|
||||
}
|
||||
}
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
169
vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php
vendored
Normal file
169
vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\rpc\server;
|
||||
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use Swoole\Server;
|
||||
use think\App;
|
||||
use think\swoole\contract\rpc\ParserInterface;
|
||||
use think\swoole\rpc\Error;
|
||||
use Throwable;
|
||||
|
||||
class Dispatcher
|
||||
{
|
||||
const ACTION_INTERFACE = '@action_interface';
|
||||
|
||||
/**
|
||||
* Parser error
|
||||
*/
|
||||
const PARSER_ERROR = -32700;
|
||||
|
||||
/**
|
||||
* Invalid Request
|
||||
*/
|
||||
const INVALID_REQUEST = -32600;
|
||||
|
||||
/**
|
||||
* Method not found
|
||||
*/
|
||||
const METHOD_NOT_FOUND = -32601;
|
||||
|
||||
/**
|
||||
* Invalid params
|
||||
*/
|
||||
const INVALID_PARAMS = -32602;
|
||||
|
||||
/**
|
||||
* Internal error
|
||||
*/
|
||||
const INTERNAL_ERROR = -32603;
|
||||
|
||||
protected $app;
|
||||
|
||||
protected $parser;
|
||||
|
||||
protected $services;
|
||||
|
||||
protected $server;
|
||||
|
||||
public function __construct(App $app, ParserInterface $parser, Server $server, $services)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->parser = $parser;
|
||||
$this->server = $server;
|
||||
$this->prepareServices($services);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务接口
|
||||
* @param $services
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function prepareServices($services)
|
||||
{
|
||||
foreach ($services as $className) {
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
$interfaces = $reflectionClass->getInterfaceNames();
|
||||
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->services[class_basename($interface)] = [
|
||||
'interface' => $interface,
|
||||
'class' => $className,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取接口信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getInterfaces()
|
||||
{
|
||||
$interfaces = [];
|
||||
foreach ($this->services as $key => ['interface' => $interface]) {
|
||||
$interfaces[$key] = $this->getMethods($interface);
|
||||
}
|
||||
return $interfaces;
|
||||
}
|
||||
|
||||
protected function getMethods($interface)
|
||||
{
|
||||
$methods = [];
|
||||
|
||||
$reflection = new ReflectionClass($interface);
|
||||
foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
$returnType = $method->getReturnType();
|
||||
if ($returnType instanceof ReflectionNamedType) {
|
||||
$returnType = $returnType->getName();
|
||||
}
|
||||
$methods[$method->getName()] = [
|
||||
'parameters' => $this->getParameters($method),
|
||||
'returnType' => $returnType,
|
||||
'comment' => $method->getDocComment(),
|
||||
];
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
protected function getParameters(ReflectionMethod $method)
|
||||
{
|
||||
$parameters = [];
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
$type = $parameter->getType();
|
||||
if ($type instanceof ReflectionNamedType) {
|
||||
$type = $type->getName();
|
||||
}
|
||||
$param = [
|
||||
'name' => $parameter->getName(),
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
if ($parameter->isOptional()) {
|
||||
$param['default'] = $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
$parameters[] = $param;
|
||||
}
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度
|
||||
* @param int $fd
|
||||
* @param string $data
|
||||
*/
|
||||
public function dispatch(int $fd, string $data)
|
||||
{
|
||||
try {
|
||||
if ($data === Dispatcher::ACTION_INTERFACE) {
|
||||
$result = $this->getInterfaces();
|
||||
} else {
|
||||
$protocol = $this->parser->decode($data);
|
||||
|
||||
$interface = $protocol->getInterface();
|
||||
$method = $protocol->getMethod();
|
||||
$params = $protocol->getParams();
|
||||
$service = $this->services[$interface] ?? null;
|
||||
if (empty($service)) {
|
||||
throw new Exception(
|
||||
sprintf('Service %s is not founded!', $interface),
|
||||
self::INVALID_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->app->invoke([$this->app->make($service['class']), $method], $params);
|
||||
}
|
||||
} catch (Throwable | Exception $e) {
|
||||
$result = Error::make($e->getCode(), $e->getMessage());
|
||||
}
|
||||
|
||||
$data = $this->parser->encodeResponse($result);
|
||||
|
||||
$this->server->send($fd, $data . ParserInterface::EOF);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user