mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-30 10:47:44 +08:00
Merge branch 'master' of https://github.com/qifengzhang007/hyperf-1
This commit is contained in:
commit
62469f2ba4
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,19 +1,33 @@
|
||||
# v1.0.6 - TBD
|
||||
# v1.0.7 - TBD
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#266](https://github.com/hyperf-cloud/hyperf/pull/266) Fixed timeout when produce a amqp message.
|
||||
|
||||
# v1.0.6 - 2019-07-24
|
||||
|
||||
## Added
|
||||
|
||||
- [#203](https://github.com/hyperf-cloud/hyperf/pull/203) [#236](https://github.com/hyperf-cloud/hyperf/pull/236) [#247](https://github.com/hyperf-cloud/hyperf/pull/247) [#252](https://github.com/hyperf-cloud/hyperf/pull/252) Added View component, support for Blade engine and Smarty engine.
|
||||
- [#203](https://github.com/hyperf-cloud/hyperf/pull/203) Added support for Swoole Task mechanism.
|
||||
- [#245](https://github.com/hyperf-cloud/hyperf/pull/245) Added TaskWorkerStrategy and WorkerStrategy crontab strategies.
|
||||
- [#251](https://github.com/hyperf-cloud/hyperf/pull/251) Added coroutine memory driver for cache.
|
||||
- [#254](https://github.com/hyperf-cloud/hyperf/pull/254) Added support for array value of `RequestMapping::$methods`, `@RequestMapping(methods={"GET"})` and `@RequestMapping(methods={RequestMapping::GET})` are available now.
|
||||
- [#255](https://github.com/hyperf-cloud/hyperf/pull/255) Transfer `Hyperf\Utils\Contracts\Arrayable` result of Request to Response automatically, and added `text/plain` content-type header for string Response.
|
||||
- [#256](https://github.com/hyperf-cloud/hyperf/pull/256) If `Hyperf\Contract\IdGeneratorInterface` exist, the `json-rpc` client will generate a Request ID via IdGenerator automatically, and stored in Request attibute. Also added support for service register and health checks of `jsonrpc` TCP protocol.
|
||||
|
||||
## Changed
|
||||
|
||||
- [#247](https://github.com/hyperf-cloud/hyperf/pull/247) Use Use `WorkerStrategy` as the default crontab strategy.
|
||||
- [#247](https://github.com/hyperf-cloud/hyperf/pull/247) Use `WorkerStrategy` as the default crontab strategy.
|
||||
- [#256](https://github.com/hyperf-cloud/hyperf/pull/256) Optimized error handling of json-rpc, server will response a standard json-rpc error object when the rpc method does not exist.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#235](https://github.com/hyperf-cloud/hyperf/pull/235) Added default exception handler for `grpc-server` and optimized code.
|
||||
- [#240](https://github.com/hyperf-cloud/hyperf/pull/240) Fixed OnPipeMessage event will be dispatch by another listener.
|
||||
- [#257](https://github.com/hyperf-cloud/hyperf/pull/257) Fixed cannot get the Internal IP in some special environment.
|
||||
|
||||
# v1.0.5 - 2019-07-07
|
||||
# v1.0.5 - 2019-07-17
|
||||
|
||||
## Added
|
||||
|
||||
|
@ -150,6 +150,7 @@
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HyperfTest\\AsyncQueue\\": "./src/async-queue/tests/",
|
||||
"HyperfTest\\Cache\\": "./src/cache/tests/",
|
||||
"HyperfTest\\ConfigApollo\\": "./src/config-apollo/tests/",
|
||||
"HyperfTest\\Constants\\": "./src/constants/tests/",
|
||||
"HyperfTest\\Consul\\": "./src/consul/tests/",
|
||||
@ -182,8 +183,8 @@
|
||||
"config": [
|
||||
"Hyperf\\Amqp\\ConfigProvider",
|
||||
"Hyperf\\AsyncQueue\\ConfigProvider",
|
||||
"Hyperf\\CircuitBreaker\\ConfigProvider",
|
||||
"Hyperf\\Cache\\ConfigProvider",
|
||||
"Hyperf\\CircuitBreaker\\ConfigProvider",
|
||||
"Hyperf\\Config\\ConfigProvider",
|
||||
"Hyperf\\ConfigApollo\\ConfigProvider",
|
||||
"Hyperf\\Devtool\\ConfigProvider",
|
||||
|
@ -184,3 +184,51 @@ public function updateUserBook(int $id)
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存驱动
|
||||
|
||||
### Redis驱动
|
||||
|
||||
`Hyperf\Cache\Driver\RedisDriver` 会把缓存数据存放到 `Redis` 中,需要用户配置相应的 `Redis配置`。此方式为默认方式。
|
||||
|
||||
### 协程内存驱动
|
||||
|
||||
> 本驱动乃Beta版本,请谨慎使用。
|
||||
|
||||
如果您需要将数据缓存到 `Context` 中,可以尝试此驱动。例如以下应用场景 `Demo::get` 会在多个地方调用多次,但是又不想每次都到 `Redis` 中进行查询。
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Cache\Annotation\Cacheable;
|
||||
|
||||
class Demo {
|
||||
|
||||
public function get($userId, $id)
|
||||
{
|
||||
return $this->getArray($userId)[$id] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Cacheable(prefix="test", group="co")
|
||||
* @param int $userId
|
||||
* @return array
|
||||
*/
|
||||
public function getArray($userId)
|
||||
{
|
||||
return $this->redis->hGetAll($userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
对应配置如下:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'co' => [
|
||||
'driver' => Hyperf\Cache\Driver\CoroutineMemoryDriver::class,
|
||||
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 配置
|
||||
|
||||
当您使用的是 [hyperf-cloud/hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton) 项目创建的项目时,或基于 [hyperf-cloud/installer](https://github.com/hyperf-cloud/installer) 创建的项目,Hyperf 的所有配置文件均处于根目录下的 `config` 文件夹内,每个选项都有说明,您可以随时查看并熟悉有哪些选项可以使用。
|
||||
当您使用的是 [hyperf-cloud/hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton) 项目创建的项目时,Hyperf 的所有配置文件均处于根目录下的 `config` 文件夹内,每个选项都有说明,您可以随时查看并熟悉有哪些选项可以使用。
|
||||
|
||||
# 安装
|
||||
|
||||
|
@ -39,6 +39,7 @@ namespace App\JsonRpc;
|
||||
use Hyperf\RpcServer\Annotation\RpcService;
|
||||
|
||||
/**
|
||||
* 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性
|
||||
* @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="jsonrpc-http")
|
||||
*/
|
||||
class CalculatorService implements CalculatorServiceInterface
|
||||
@ -131,11 +132,11 @@ return [
|
||||
|
||||
配置完成后,在启动服务时,Hyperf 会自动地将 `@RpcService` 定义了 `publishTo` 属性为 `consul` 的服务注册到服务中心去。
|
||||
|
||||
> 目前仅支持 `jsonrpc-http` 协议发布到服务中心去,其它协议的健康检查尚未实现
|
||||
> 目前仅支持 `jsonrpc` 和 `jsonrpc-http` 协议发布到服务中心去,其它协议尚未实现服务注册
|
||||
|
||||
## 定义服务消费者
|
||||
|
||||
一个 `服务消费者(ServiceConsumer)` 可以理解为就是一个客户端类,但在 Hyperf 里您无需处理连接和请求相关的事情,只需要定义一个类及相关属性即可。(v1.1会提供动态代理实现的客户端,使之更加简单便捷)
|
||||
一个 `服务消费者(ServiceConsumer)` 可以理解为就是一个客户端类,但在 Hyperf 里您无需处理连接和请求相关的事情,只需要定义一个类及相关属性即可。(后续版本迭代会提供动态代理实现的客户端,使之更加简单便捷)
|
||||
|
||||
```php
|
||||
<?php
|
||||
@ -144,7 +145,7 @@ namespace App\JsonRpc;
|
||||
|
||||
use Hyperf\RpcClient\AbstractServiceClient;
|
||||
|
||||
class CalculatorService extends AbstractServiceClient implements CalculatorServiceInterface
|
||||
class CalculatorServiceConsumer extends AbstractServiceClient implements CalculatorServiceInterface
|
||||
{
|
||||
/**
|
||||
* 定义对应服务提供者的服务名称
|
||||
@ -189,12 +190,12 @@ return [
|
||||
```
|
||||
|
||||
|
||||
这样我们便可以通过 `CalculatorService` 类来实现对服务的消费了,为了让这里的关系逻辑更加的合理,还应该在 `config/dependencies.php` 内定义 `CalculatorServiceInterface` 和 `CalculatorService` 的关系,示例如下:
|
||||
这样我们便可以通过 `CalculatorService` 类来实现对服务的消费了,为了让这里的关系逻辑更加的合理,还应该在 `config/dependencies.php` 内定义 `CalculatorServiceInterface` 和 `CalculatorServiceConsumer` 的关系,示例如下:
|
||||
|
||||
```php
|
||||
return [
|
||||
'dependencies' => [
|
||||
App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorService::class,
|
||||
App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorServiceConsumer::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
@ -39,4 +39,4 @@ class CalculatorService implements CalculatorServiceInterface
|
||||
`server` 属性为绑定该服务类发布所要承载的 `Server`,默认值为 `jsonrpc-http`,该属性对应 `config/autoload/server.php` 文件内 `servers` 下所对应的 `name`,这里也就意味着我们需要定义一个对应的 `Server`,我们下一章节具体阐述这里应该怎样去处理;
|
||||
`publishTo` 属性为定义该服务所要发布的服务中心,目前仅支持 `consul` 或为空,为空时代表不发布该服务到服务中心去,但也就意味着您需要手动处理服务发现的问题,当值为 `consul` 时需要对应配置好 [hyperf/consul](./consul.md) 组件的相关配置,要使用此功能需安装 [hyperf/service-governance](https://github.com/hyperf-cloud/service-governance) 组件;
|
||||
|
||||
> 使用 `@RpcService` 注解需 use Hyperf\RpcServer\Annotation\RpcService; 命名空间。
|
||||
> 使用 `@RpcService` 注解需 `use Hyperf\RpcServer\Annotation\RpcService;` 命名空间。
|
||||
|
@ -35,6 +35,7 @@ return [
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
Task 组件提供了 `主动方法投递` 和 `注解投递` 两种使用方法。
|
||||
@ -66,6 +67,7 @@ $exec = $container->get(TaskExecutor::class);
|
||||
$result = $exec->execute(new Task([MethodTask::class, 'handle'], Coroutine::id()));
|
||||
|
||||
```
|
||||
|
||||
### 使用注解
|
||||
|
||||
通过 `主动方法投递` 时,并不是特别直观,这里我们实现了对应的 `@Task` 注解,并通过 `AOP` 重写了方法调用。当在 `Worker` 进程时,自动投递到 `Task` 进程,并协程等待 数据返回。
|
||||
@ -111,3 +113,87 @@ Swoole 暂时没有协程化的函数列表
|
||||
- pdo_odbc
|
||||
- pdo_firebird
|
||||
|
||||
### MongoDB
|
||||
|
||||
> 因为 `MongoDB` 没有办法被 `hook`,所以我们可以通过 `Task` 来调用,下面就简单介绍一下如何通过注解方式调用 `MongoDB`。
|
||||
|
||||
以下我们实现两个方法 `insert` 和 `query`,其中需要注意的是 `manager` 方法不能使用 `Task`,
|
||||
因为 `Task` 会在对应的 `Task进程` 中处理,然后将数据从 `Task进程` 返回到 `Worker进程` 。
|
||||
所以 `Task方法` 的入参和出参最好不要携带任何 `IO`,比如返回一个实例化后的 `Redis` 等等。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Task;
|
||||
|
||||
use Hyperf\Task\Annotation\Task;
|
||||
use MongoDB\Driver\BulkWrite;
|
||||
use MongoDB\Driver\Manager;
|
||||
use MongoDB\Driver\Query;
|
||||
use MongoDB\Driver\WriteConcern;
|
||||
|
||||
class MongoTask
|
||||
{
|
||||
/**
|
||||
* @var Manager
|
||||
*/
|
||||
public $manager;
|
||||
|
||||
/**
|
||||
* @Task
|
||||
* @param string $namespace
|
||||
* @param array $document
|
||||
*/
|
||||
public function insert($namespace, $document)
|
||||
{
|
||||
$writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000);
|
||||
$bulk = new BulkWrite();
|
||||
$bulk->insert($document);
|
||||
|
||||
$result = $this->manager()->executeBulkWrite($namespace, $bulk, $writeConcern);
|
||||
return $result->getUpsertedCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Task
|
||||
* @param string $namespace
|
||||
* @param array $filter
|
||||
* @param array $options
|
||||
*/
|
||||
public function query($namespace, $filter = [], $options = [])
|
||||
{
|
||||
$query = new Query($filter, $options);
|
||||
$cursor = $this->manager()->executeQuery($namespace, $query);
|
||||
return $cursor->toArray();
|
||||
}
|
||||
|
||||
protected function manager()
|
||||
{
|
||||
if ($this->manager instanceof Manager) {
|
||||
return $this->manager;
|
||||
}
|
||||
$uri = 'mongodb://127.0.0.1:27017';
|
||||
return $this->manager = new Manager($uri, []);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
使用如下
|
||||
|
||||
```php
|
||||
<?php
|
||||
use App\Task\MongoTask;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
|
||||
$client = ApplicationContext::getContainer()->get(MongoTask::class);
|
||||
$client->insert('hyperf.test', ['id' => rand(0, 99999999)]);
|
||||
|
||||
$result = $client->query('hyperf.test', [], [
|
||||
'sort' => ['id' => -1],
|
||||
'limit' => 5,
|
||||
]);
|
||||
```
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory suffix="Test.php">./src/async-queue/tests</directory>
|
||||
<directory suffix="Test.php">./src/cache/tests</directory>
|
||||
<directory suffix="Test.php">./src/constants/tests</directory>
|
||||
<directory suffix="Test.php">./src/consul/tests</directory>
|
||||
<directory suffix="Test.php">./src/database/tests</directory>
|
||||
|
@ -139,7 +139,7 @@ class Connection extends BaseConnection implements ConnectionInterface
|
||||
$class = AMQPSwooleConnection::class;
|
||||
}
|
||||
|
||||
$this->lastHeartbeatTime = 0;
|
||||
$this->lastHeartbeatTime = microtime(true);
|
||||
return new $class($this->config['host'] ?? 'localhost', $this->config['port'] ?? 5672, $this->config['user'] ?? 'guest', $this->config['password'] ?? 'guest', $this->config['vhost'] ?? '/', $this->params->isInsist(), $this->params->getLoginMethod(), $this->params->getLoginResponse(), $this->params->getLocale(), $this->params->getConnectionTimeout(), $this->params->getReadWriteTimeout(), $this->params->getContext(), $this->params->isKeepalive(), $this->params->getHeartbeat());
|
||||
}
|
||||
|
||||
@ -149,14 +149,9 @@ class Connection extends BaseConnection implements ConnectionInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastHeartbeatTime = $this->lastHeartbeatTime;
|
||||
$currentTime = microtime(true);
|
||||
|
||||
if ($lastHeartbeatTime && $lastHeartbeatTime > 0) {
|
||||
if ($currentTime - $lastHeartbeatTime > $this->params->getHeartbeat()) {
|
||||
if (microtime(true) - $this->lastHeartbeatTime > $this->params->getHeartbeat()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
38
src/cache/src/Collector/CoroutineMemory.php
vendored
Normal file
38
src/cache/src/Collector/CoroutineMemory.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Cache\Collector;
|
||||
|
||||
use Hyperf\Utils\Collection;
|
||||
use Hyperf\Utils\Str;
|
||||
use Hyperf\Utils\Traits\StaticInstance;
|
||||
|
||||
class CoroutineMemory extends Collection
|
||||
{
|
||||
use StaticInstance;
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->items = [];
|
||||
}
|
||||
|
||||
public function clearPrefix(string $prefix)
|
||||
{
|
||||
foreach ($this->items as $key => $item) {
|
||||
if (Str::startsWith($prefix, $key)) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
21
src/cache/src/Collector/CoroutineMemoryKey.php
vendored
Normal file
21
src/cache/src/Collector/CoroutineMemoryKey.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Cache\Collector;
|
||||
|
||||
use Hyperf\Utils\Collection;
|
||||
use Hyperf\Utils\Traits\StaticInstance;
|
||||
|
||||
class CoroutineMemoryKey extends Collection
|
||||
{
|
||||
use StaticInstance;
|
||||
}
|
115
src/cache/src/Driver/CoroutineMemoryDriver.php
vendored
Normal file
115
src/cache/src/Driver/CoroutineMemoryDriver.php
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Cache\Driver;
|
||||
|
||||
use Hyperf\Cache\Collector\CoroutineMemory;
|
||||
use Hyperf\Cache\Collector\CoroutineMemoryKey;
|
||||
|
||||
class CoroutineMemoryDriver extends Driver implements KeyCollectorInterface
|
||||
{
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
return $this->getCollection()->get($key, $default);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
return $this->getCollection()->offsetSet($key, $value);
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
return $this->getCollection()->offsetUnset($key);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
return $this->getCollection()->clear();
|
||||
}
|
||||
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = $this->get($key, $default);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
$this->set($key, $values, $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
$this->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
return $this->getCollection()->has($key);
|
||||
}
|
||||
|
||||
public function fetch(string $key, $default = null): array
|
||||
{
|
||||
if (! $this->has($key)) {
|
||||
return [false, $default];
|
||||
}
|
||||
|
||||
return [true, $this->get($key)];
|
||||
}
|
||||
|
||||
public function clearPrefix(string $prefix): bool
|
||||
{
|
||||
return $this->getCollection()->clearPrefix($prefix);
|
||||
}
|
||||
|
||||
public function addKey(string $collector, string $key): bool
|
||||
{
|
||||
$instance = CoroutineMemoryKey::instance();
|
||||
$data = $instance->get($collector, []);
|
||||
$data[] = $key;
|
||||
$instance->put($collector, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function keys(string $collector): array
|
||||
{
|
||||
return CoroutineMemoryKey::instance()->get($collector, []);
|
||||
}
|
||||
|
||||
public function delKey(string $collector, ...$key): bool
|
||||
{
|
||||
$instance = CoroutineMemoryKey::instance();
|
||||
$result = [];
|
||||
$data = $instance->get($collector, []);
|
||||
foreach ($data as $item) {
|
||||
if (! in_array($item, $key)) {
|
||||
$result[] = $item;
|
||||
}
|
||||
}
|
||||
$instance->put($collector, $result);
|
||||
}
|
||||
|
||||
protected function getCollection()
|
||||
{
|
||||
return CoroutineMemory::instance();
|
||||
}
|
||||
}
|
48
src/cache/tests/Cases/CoroutineMemoryDriverTest.php
vendored
Normal file
48
src/cache/tests/Cases/CoroutineMemoryDriverTest.php
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\Cache\Cases;
|
||||
|
||||
use Hyperf\Cache\Driver\CoroutineMemoryDriver;
|
||||
use Hyperf\Utils\Packer\PhpSerializerPacker;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class CoroutineMemoryDriverTest extends TestCase
|
||||
{
|
||||
public function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testCacheableOnlyInSameCoroutine()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
$container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker());
|
||||
|
||||
$driver = new CoroutineMemoryDriver($container, []);
|
||||
$this->assertSame(null, $driver->get('test', null));
|
||||
$driver->set('test', 'xxx');
|
||||
$this->assertSame('xxx', $driver->get('test', null));
|
||||
|
||||
parallel([function () use ($driver) {
|
||||
$this->assertSame(null, $driver->get('test', null));
|
||||
$driver->set('test', 'xxx2');
|
||||
$this->assertSame('xxx2', $driver->get('test', null));
|
||||
}]);
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@ class OnPipeMessageListener implements ListenerInterface
|
||||
$this->config->set($key, $value);
|
||||
$this->logger->debug(sprintf('Config [%s] is updated', $key));
|
||||
}
|
||||
ReleaseKey::set($cacheKey, $data['releaseKey']);
|
||||
ReleaseKey::set($cacheKey, $data->releaseKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
src/contract/src/IdGeneratorInterface.php
Normal file
18
src/contract/src/IdGeneratorInterface.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Contract;
|
||||
|
||||
interface IdGeneratorInterface
|
||||
{
|
||||
public function generate(): string;
|
||||
}
|
@ -54,7 +54,7 @@ abstract class AbstractAnnotation implements AnnotationInterface, Arrayable
|
||||
AnnotationCollector::collectProperty($className, $target, static::class, $this);
|
||||
}
|
||||
|
||||
protected function bindMainProperty(string $key, array $value)
|
||||
protected function bindMainProperty(string $key, ?array $value)
|
||||
{
|
||||
if (isset($value['value'])) {
|
||||
$this->{$key} = $value['value'];
|
||||
|
@ -239,11 +239,11 @@ class Request implements RequestInterface
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
$header = 'host';
|
||||
if ($this->hasHeader('host')) {
|
||||
$header = $this->getHeaderLine('host');
|
||||
$host = $this->getHeaderLine('host');
|
||||
} else {
|
||||
$header = 'Host';
|
||||
$this->headerNames['host'] = 'Host';
|
||||
$this->headerNames['host'] = 'host';
|
||||
}
|
||||
// Ensure Host is the first header.
|
||||
$this->headers = [$header => [$host]] + $this->headers;
|
||||
|
@ -20,6 +20,21 @@ use Hyperf\Utils\Str;
|
||||
*/
|
||||
class RequestMapping extends Mapping
|
||||
{
|
||||
|
||||
public const GET = 'GET';
|
||||
|
||||
public const POST = 'POST';
|
||||
|
||||
public const PUT = 'PUT';
|
||||
|
||||
public const PATCH = 'PATCH';
|
||||
|
||||
public const DELETE = 'DELETE';
|
||||
|
||||
public const HEADER = 'HEADER';
|
||||
|
||||
public const OPTIONS = 'OPTIONS';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -29,8 +44,16 @@ class RequestMapping extends Mapping
|
||||
{
|
||||
parent::__construct($value);
|
||||
if (isset($value['methods'])) {
|
||||
if (is_string($value['methods'])) {
|
||||
// Explode a string to a array
|
||||
$this->methods = explode(',', Str::upper(str_replace(' ', '', $value['methods'])));
|
||||
} else {
|
||||
$methods = [];
|
||||
foreach ($value['methods'] as $method) {
|
||||
$methods[] = Str::upper(str_replace(' ', '', $method));
|
||||
}
|
||||
$this->methods = $methods;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,15 +146,18 @@ class CoreMiddleware implements MiddlewareInterface
|
||||
/**
|
||||
* Transfer the non-standard response content to a standard response object.
|
||||
*
|
||||
* @param array|string $response
|
||||
* @param array|string|Jsonable|Arrayable $response
|
||||
*/
|
||||
protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (is_string($response)) {
|
||||
return $this->response()->withBody(new SwooleStream($response));
|
||||
return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream($response));
|
||||
}
|
||||
|
||||
if (is_array($response)) {
|
||||
if (is_array($response) || $response instanceof Arrayable) {
|
||||
if ($response instanceof Arrayable) {
|
||||
$response = $response->toArray();
|
||||
}
|
||||
return $this->response()
|
||||
->withAddedHeader('content-type', 'application/json')
|
||||
->withBody(new SwooleStream(json_encode($response, JSON_UNESCAPED_UNICODE)));
|
||||
@ -166,7 +169,7 @@ class CoreMiddleware implements MiddlewareInterface
|
||||
->withBody(new SwooleStream((string) $response));
|
||||
}
|
||||
|
||||
return $this->response()->withBody(new SwooleStream((string) $response));
|
||||
return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream((string) $response));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,7 +105,7 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface
|
||||
$psr7Response = $exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
|
||||
} finally {
|
||||
// Send the Response to client.
|
||||
if (! $psr7Response || ! $psr7Response instanceof Psr7Response) {
|
||||
if (! isset($psr7Response) || ! $psr7Response instanceof Psr7Response) {
|
||||
return;
|
||||
}
|
||||
$psr7Response->send();
|
||||
|
@ -12,12 +12,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace HyperfTest\HttpServer;
|
||||
|
||||
use Hyperf\HttpServer\CoreMiddleware;
|
||||
use Hyperf\HttpServer\Router\DispatcherFactory;
|
||||
use Hyperf\Utils\Contracts\Arrayable;
|
||||
use Hyperf\Utils\Contracts\Jsonable;
|
||||
use HyperfTest\HttpServer\Stub\CoreMiddlewareStub;
|
||||
use HyperfTest\HttpServer\Stub\DemoController;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -35,6 +41,60 @@ class CoreMiddlewareTest extends TestCase
|
||||
$this->assertSame([$id, 'Hyperf', []], $params);
|
||||
}
|
||||
|
||||
public function testTransferToResponse()
|
||||
{
|
||||
$middleware = new CoreMiddlewareStub($container = $this->getContainer(), 'http');
|
||||
$reflectionMethod = new ReflectionMethod(CoreMiddleware::class, 'transferToResponse');
|
||||
$reflectionMethod->setAccessible(true);
|
||||
$request = Mockery::mock(ServerRequestInterface::class);
|
||||
/** @var ResponseInterface $response */
|
||||
|
||||
// String
|
||||
$response = $reflectionMethod->invoke($middleware, $body = 'foo', $request);
|
||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||
$this->assertSame($body, $response->getBody()->getContents());
|
||||
$this->assertSame('text/plain', $response->getHeaderLine('content-type'));
|
||||
|
||||
// Array
|
||||
$response = $reflectionMethod->invoke($middleware, $body = ['foo' => 'bar'], $request);
|
||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||
$this->assertSame(json_encode($body), $response->getBody()->getContents());
|
||||
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
|
||||
|
||||
// Arrayable
|
||||
$response = $reflectionMethod->invoke($middleware, new class() implements Arrayable {
|
||||
public function toArray(): array
|
||||
{
|
||||
return ['foo' => 'bar'];
|
||||
}
|
||||
}, $request);
|
||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||
$this->assertSame(json_encode(['foo' => 'bar']), $response->getBody()->getContents());
|
||||
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
|
||||
|
||||
// Jsonable
|
||||
$response = $reflectionMethod->invoke($middleware, new class() implements Jsonable {
|
||||
public function __toString(): string
|
||||
{
|
||||
return json_encode(['foo' => 'bar'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}, $request);
|
||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||
$this->assertSame(json_encode(['foo' => 'bar']), $response->getBody()->getContents());
|
||||
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
|
||||
|
||||
// __toString
|
||||
$response = $reflectionMethod->invoke($middleware, new class() {
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'This is a string';
|
||||
}
|
||||
}, $request);
|
||||
$this->assertInstanceOf(ResponseInterface::class, $response);
|
||||
$this->assertSame('This is a string', $response->getBody()->getContents());
|
||||
$this->assertSame('text/plain', $response->getHeaderLine('content-type'));
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
|
54
src/http-server/tests/MappingAnnotationTest.php
Normal file
54
src/http-server/tests/MappingAnnotationTest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace HyperfTest\HttpServer;
|
||||
|
||||
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MappingAnnotationTest extends TestCase
|
||||
{
|
||||
|
||||
public function testRequestMapping()
|
||||
{
|
||||
$mapping = new RequestMapping([]);
|
||||
// Assert default methods
|
||||
$this->assertSame(['GET', 'POST'], $mapping->methods);
|
||||
$this->assertNull($mapping->path);
|
||||
|
||||
// Normal case
|
||||
$mapping = new RequestMapping([
|
||||
'methods' => 'get,post,put',
|
||||
'path' => $path = '/foo',
|
||||
]);
|
||||
$this->assertSame(['GET', 'POST', 'PUT'], $mapping->methods);
|
||||
$this->assertSame($path, $mapping->path);
|
||||
|
||||
// The methods have space
|
||||
$mapping = new RequestMapping([
|
||||
'methods' => 'get, post, put',
|
||||
'path' => $path,
|
||||
]);
|
||||
$this->assertSame(['GET', 'POST', 'PUT'], $mapping->methods);
|
||||
$this->assertSame($path, $mapping->path);
|
||||
}
|
||||
|
||||
public function testRequestMappingWithArrayMethods()
|
||||
{
|
||||
$mapping = new RequestMapping([
|
||||
'methods' => [
|
||||
'GET', 'POST ', 'put'
|
||||
],
|
||||
'path' => $path = '/foo',
|
||||
]);
|
||||
$this->assertSame(['GET', 'POST', 'PUT'], $mapping->methods);
|
||||
$this->assertSame($path, $mapping->path);
|
||||
}
|
||||
|
||||
public function testRequestMappingBindMainProperty()
|
||||
{
|
||||
$mapping = new RequestMapping(['value' => '/foo']);
|
||||
$this->assertSame(['GET', 'POST'], $mapping->methods);
|
||||
$this->assertSame('/foo', $mapping->path);
|
||||
}
|
||||
}
|
@ -12,7 +12,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace HyperfTest\HttpServer\Stub;
|
||||
|
||||
use Hyperf\HttpMessage\Server\Response;
|
||||
use Hyperf\HttpServer\CoreMiddleware;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class CoreMiddlewareStub extends CoreMiddleware
|
||||
{
|
||||
@ -20,4 +22,10 @@ class CoreMiddlewareStub extends CoreMiddleware
|
||||
{
|
||||
return parent::parseParameters($controller, $action, $arguments);
|
||||
}
|
||||
|
||||
protected function response(): ResponseInterface
|
||||
{
|
||||
return new Response();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Hyperf\JsonRpc;
|
||||
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Closure;
|
||||
use Hyperf\Rpc\ProtocolManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@ -38,6 +38,11 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
|
||||
*/
|
||||
protected $packer;
|
||||
|
||||
/**
|
||||
* @var \Hyperf\JsonRpc\ResponseBuilder
|
||||
*/
|
||||
protected $responseBuilder;
|
||||
|
||||
public function __construct(ContainerInterface $container, string $serverName)
|
||||
{
|
||||
parent::__construct($container, $serverName);
|
||||
@ -45,18 +50,41 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
|
||||
$protocolName = 'jsonrpc';
|
||||
$this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName));
|
||||
$this->packer = $container->get($this->protocolManager->getPacker($protocolName));
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $this->dataFormatter,
|
||||
'packer' => $this->packer,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleFound(array $routes, ServerRequestInterface $request)
|
||||
{
|
||||
if ($routes[1] instanceof Closure) {
|
||||
$response = call($routes[1]);
|
||||
} else {
|
||||
[$controller, $action] = $this->prepareHandler($routes[1]);
|
||||
$controllerInstance = $this->container->get($controller);
|
||||
if (! method_exists($controller, $action)) {
|
||||
// Route found, but the handler does not exist.
|
||||
return $this->responseBuilder->buildErrorResponse($request, -32603);
|
||||
}
|
||||
$parameters = $this->parseParameters($controller, $action, $request->getParsedBody());
|
||||
$response = $controllerInstance->{$action}(...$parameters);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function handleNotFound(ServerRequestInterface $request)
|
||||
{
|
||||
return $this->responseBuilder->buildErrorResponse($request, -32601);
|
||||
}
|
||||
|
||||
protected function handleMethodNotAllowed(array $routes, ServerRequestInterface $request)
|
||||
{
|
||||
return $this->handleNotFound($request);
|
||||
}
|
||||
|
||||
protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
return $this->response()
|
||||
->withAddedHeader('content-type', 'application/json')
|
||||
->withBody(new SwooleStream($this->format($response, $request)));
|
||||
}
|
||||
|
||||
protected function format($response, ServerRequestInterface $request): string
|
||||
{
|
||||
$response = $this->dataFormatter->formatResponse([$request->getAttribute('request_id') ?? '', $response]);
|
||||
return $this->packer->pack($response);
|
||||
return $this->responseBuilder->buildResponse($request, $response);
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,12 @@ class DataFormatter implements DataFormatterInterface
|
||||
{
|
||||
public function formatRequest($data)
|
||||
{
|
||||
[$path, $params] = $data;
|
||||
[$path, $params, $id] = $data;
|
||||
return [
|
||||
'jsonrpc' => '2.0',
|
||||
'method' => $path,
|
||||
'params' => $params,
|
||||
'id' => $id,
|
||||
];
|
||||
}
|
||||
|
||||
@ -35,4 +36,20 @@ class DataFormatter implements DataFormatterInterface
|
||||
'result' => $result,
|
||||
];
|
||||
}
|
||||
|
||||
public function formatErrorResponse($data)
|
||||
{
|
||||
[$id, $code, $message, $data] = $data;
|
||||
return [
|
||||
'jsonrpc' => '2.0',
|
||||
'id' => $id ?? null,
|
||||
'error' => [
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ class HttpCoreMiddleware extends CoreMiddleware
|
||||
$protocolName = 'jsonrpc-http';
|
||||
$this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName));
|
||||
$this->packer = $container->get($this->protocolManager->getPacker($protocolName));
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $this->dataFormatter,
|
||||
'packer' => $this->packer,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handleNotFound(ServerRequestInterface $request)
|
||||
|
@ -37,6 +37,11 @@ class HttpServer extends Server
|
||||
*/
|
||||
protected $packer;
|
||||
|
||||
/**
|
||||
* @var \Hyperf\JsonRpc\ResponseBuilder
|
||||
*/
|
||||
protected $responseBuilder;
|
||||
|
||||
public function __construct(
|
||||
string $serverName,
|
||||
string $coreHandler,
|
||||
@ -46,8 +51,13 @@ class HttpServer extends Server
|
||||
) {
|
||||
parent::__construct($serverName, $coreHandler, $container, $dispatcher);
|
||||
$this->protocolManager = $protocolManager;
|
||||
$packerClass = $this->protocolManager->getPacker('jsonrpc-http');
|
||||
$protocolName = 'jsonrpc-http';
|
||||
$packerClass = $this->protocolManager->getPacker($protocolName);
|
||||
$this->packer = $this->container->get($packerClass);
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $container->get($this->protocolManager->getDataFormatter($protocolName)),
|
||||
'packer' => $this->packer,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function initRequestAndResponse(SwooleRequest $request, SwooleResponse $response): array
|
||||
@ -56,22 +66,24 @@ class HttpServer extends Server
|
||||
$psr7Request = Psr7Request::loadFromSwooleRequest($request);
|
||||
if (! $this->isHealthCheck($psr7Request)) {
|
||||
if (strpos($psr7Request->getHeaderLine('content-type'), 'application/json') === false) {
|
||||
throw new InvalidArgumentException('Invalid Json RPC request.');
|
||||
$this->responseBuilder->buildErrorResponse($request, -32700);
|
||||
}
|
||||
// @TODO Optimize the error handling of encode.
|
||||
$content = $this->packer->unpack($psr7Request->getBody()->getContents());
|
||||
if (! isset($content['jsonrpc'], $content['method'], $content['params'])) {
|
||||
throw new InvalidArgumentException('Invalid Json RPC request.');
|
||||
$this->responseBuilder->buildErrorResponse($request, -32600);
|
||||
}
|
||||
}
|
||||
$psr7Request = $psr7Request->withUri($psr7Request->getUri()->withPath($content['method'] ?? '/'))
|
||||
->withParsedBody($content['params'] ?? null)
|
||||
->withAttribute('data', $content ?? []);
|
||||
->withAttribute('data', $content ?? [])
|
||||
->withAttribute('request_id', $content['id'] ?? null);
|
||||
Context::set(ServerRequestInterface::class, $psr7Request);
|
||||
Context::set(ResponseInterface::class, $psr7Response = new Psr7Response($response));
|
||||
return [$psr7Request, $psr7Response];
|
||||
}
|
||||
|
||||
protected function isHealthCheck(RequestInterface $request)
|
||||
protected function isHealthCheck(RequestInterface $request): bool
|
||||
{
|
||||
return $request->getHeaderLine('user-agent') === 'Consul Health Check';
|
||||
}
|
||||
|
89
src/json-rpc/src/ResponseBuilder.php
Normal file
89
src/json-rpc/src/ResponseBuilder.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://doc.hyperf.io
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\JsonRpc;
|
||||
|
||||
use Hyperf\Contract\PackerInterface;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\Rpc\Contract\DataFormatterInterface;
|
||||
use Hyperf\Utils\Context;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class ResponseBuilder
|
||||
{
|
||||
/**
|
||||
* @var \Hyperf\Rpc\Contract\DataFormatterInterface
|
||||
*/
|
||||
protected $dataFormatter;
|
||||
|
||||
/**
|
||||
* @var PackerInterface
|
||||
*/
|
||||
protected $packer;
|
||||
|
||||
public function __construct(DataFormatterInterface $dataFormatter, PackerInterface $packer)
|
||||
{
|
||||
$this->dataFormatter = $dataFormatter;
|
||||
$this->packer = $packer;
|
||||
}
|
||||
|
||||
public function buildErrorResponse(ServerRequestInterface $request, int $code): ResponseInterface
|
||||
{
|
||||
$body = new SwooleStream($this->formatErrorResponse($request, $code));
|
||||
return $this->response()->withAddedHeader('content-type', 'application/json')->withBody($body);
|
||||
}
|
||||
|
||||
public function buildResponse(ServerRequestInterface $request, $response): ResponseInterface
|
||||
{
|
||||
$body = new SwooleStream($this->formatResponse($response, $request));
|
||||
return $this->response()
|
||||
->withAddedHeader('content-type', 'application/json')
|
||||
->withBody($body);
|
||||
}
|
||||
|
||||
protected function formatResponse($response, ServerRequestInterface $request): string
|
||||
{
|
||||
$response = $this->dataFormatter->formatResponse([$request->getAttribute('request_id') ?? '', $response]);
|
||||
return $this->packer->pack($response);
|
||||
}
|
||||
|
||||
protected function formatErrorResponse(ServerRequestInterface $request, int $code): string
|
||||
{
|
||||
[$code, $message] = $this->error($code);
|
||||
$response = $this->dataFormatter->formatErrorResponse([$request->getAttribute('request_id') ?? '', $code, $message, null]);
|
||||
return $this->packer->pack($response);
|
||||
}
|
||||
|
||||
protected function error(int $code, ?string $message = null): array
|
||||
{
|
||||
$mapping = [
|
||||
-32700 => 'Parse error.',
|
||||
-32600 => 'Invalid request.',
|
||||
-32601 => 'Method not found.',
|
||||
-32602 => 'Invalid params.',
|
||||
-32603 => 'Internal error.',
|
||||
];
|
||||
if (isset($mapping[$code])) {
|
||||
return [$code, $mapping[$code]];
|
||||
}
|
||||
return [$code, $message ?? ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response instance from context.
|
||||
*/
|
||||
protected function response(): ResponseInterface
|
||||
{
|
||||
return Context::get(ResponseInterface::class);
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ use Hyperf\HttpMessage\Uri\Uri;
|
||||
use Hyperf\Rpc\ProtocolManager;
|
||||
use Hyperf\RpcServer\Server;
|
||||
use Hyperf\Server\ServerManager;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@ -37,6 +36,11 @@ class TcpServer extends Server
|
||||
*/
|
||||
protected $packer;
|
||||
|
||||
/**
|
||||
* @var \Hyperf\JsonRpc\ResponseBuilder
|
||||
*/
|
||||
protected $responseBuilder;
|
||||
|
||||
public function __construct(
|
||||
string $serverName,
|
||||
string $coreHandler,
|
||||
@ -47,8 +51,13 @@ class TcpServer extends Server
|
||||
) {
|
||||
parent::__construct($serverName, $coreHandler, $container, $dispatcher, $logger);
|
||||
$this->protocolManager = $protocolManager;
|
||||
$packerClass = $this->protocolManager->getPacker('jsonrpc');
|
||||
$protocolName = 'jsonrpc';
|
||||
$packerClass = $this->protocolManager->getPacker($protocolName);
|
||||
$this->packer = $this->container->get($packerClass);
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $container->get($this->protocolManager->getDataFormatter($protocolName)),
|
||||
'packer' => $this->packer,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function buildResponse(int $fd, SwooleServer $server): ResponseInterface
|
||||
@ -62,11 +71,8 @@ class TcpServer extends Server
|
||||
$class = $this->protocolManager->getPacker('jsonrpc');
|
||||
$packer = $this->container->get($class);
|
||||
$data = $this->packer->unpack($data);
|
||||
if (isset($data['jsonrpc'])) {
|
||||
return $this->buildJsonRpcRequest($fd, $fromId, $data);
|
||||
}
|
||||
throw new InvalidArgumentException('Doesn\'t match JSON RPC protocol.');
|
||||
}
|
||||
|
||||
protected function buildJsonRpcRequest(int $fd, int $fromId, array $data)
|
||||
{
|
||||
@ -80,9 +86,14 @@ class TcpServer extends Server
|
||||
[$type, $port] = ServerManager::get($this->serverName);
|
||||
|
||||
$uri = (new Uri())->withPath($data['method'])->withHost($port->host)->withPort($port->port);
|
||||
return (new Psr7Request('POST', $uri))->withAttribute('fd', $fd)
|
||||
$request = (new Psr7Request('POST', $uri))->withAttribute('fd', $fd)
|
||||
->withAttribute('fromId', $fromId)
|
||||
->withAttribute('data', $data)
|
||||
->withParsedBody($data['params']);
|
||||
->withAttribute('request_id', $data['id'] ?? null)
|
||||
->withParsedBody($data['params'] ?? '');
|
||||
if (! isset($data['jsonrpc'])) {
|
||||
return $this->responseBuilder->buildErrorResponse($request, -32600);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use Hyperf\Consul\Agent;
|
||||
use Hyperf\Consul\Health;
|
||||
use Hyperf\Consul\HealthInterface;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\IdGeneratorInterface;
|
||||
use Hyperf\Contract\PackerInterface;
|
||||
use Hyperf\Guzzle\ClientFactory;
|
||||
use Hyperf\LoadBalancer\LoadBalancerInterface;
|
||||
@ -44,7 +45,7 @@ abstract class AbstractServiceClient
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $protocol = 'jsonrpc';
|
||||
protected $protocol = 'jsonrpc-http';
|
||||
|
||||
/**
|
||||
* The load balancer of the client, this name of the load balancer
|
||||
@ -89,6 +90,11 @@ abstract class AbstractServiceClient
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var null|\Hyperf\Contract\IdGeneratorInterface
|
||||
*/
|
||||
protected $idGenerator;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
@ -101,14 +107,25 @@ abstract class AbstractServiceClient
|
||||
$this->client = make(Client::class)
|
||||
->setPacker($this->createPacker())
|
||||
->setTransporter($transporter);
|
||||
if ($container->has(IdGeneratorInterface::class)) {
|
||||
$this->idGenerator = $container->get(IdGeneratorInterface::class);
|
||||
}
|
||||
}
|
||||
|
||||
protected function __request(string $method, array $params)
|
||||
protected function __request(string $method, array $params, ?string $id = null)
|
||||
{
|
||||
$response = $this->client->send($this->__generateData($method, $params));
|
||||
if (is_array($response) && isset($response['result'])) {
|
||||
if ($this->idGenerator instanceof IdGeneratorInterface && ! $id) {
|
||||
$id = $this->idGenerator->generate();
|
||||
}
|
||||
$response = $this->client->send($this->__generateData($method, $params, $id));
|
||||
if (is_array($response)) {
|
||||
if (isset($response['result'])) {
|
||||
return $response['result'];
|
||||
}
|
||||
if (isset($response['error'])) {
|
||||
return $response['error'];
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Invalid response.');
|
||||
}
|
||||
|
||||
@ -120,9 +137,9 @@ abstract class AbstractServiceClient
|
||||
return $this->pathGenerator->generate($this->serviceName, $methodName);
|
||||
}
|
||||
|
||||
protected function __generateData(string $methodName, array $params)
|
||||
protected function __generateData(string $methodName, array $params, ?string $id)
|
||||
{
|
||||
return $this->dataFormatter->formatRequest([$this->__generateRpcPath($methodName), $params]);
|
||||
return $this->dataFormatter->formatRequest([$this->__generateRpcPath($methodName), $params, $id]);
|
||||
}
|
||||
|
||||
protected function createLoadBalancer(array $nodes, callable $refresh = null): LoadBalancerInterface
|
||||
|
@ -39,7 +39,7 @@ class Client
|
||||
$packer = $this->getPacker();
|
||||
$packedData = $packer->pack($data);
|
||||
$response = $this->getTransporter()->send($packedData);
|
||||
return $packer->unpack($response);
|
||||
return $packer->unpack((string) $response);
|
||||
}
|
||||
|
||||
public function getPacker(): PackerInterface
|
||||
|
@ -32,11 +32,6 @@ class CoreMiddleware extends \Hyperf\HttpServer\CoreMiddleware
|
||||
$this->dispatcher = $factory->getDispatcher($serverName);
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
return parent::process($request, $handler);
|
||||
}
|
||||
|
||||
protected function handleFound(array $routes, ServerRequestInterface $request)
|
||||
{
|
||||
if ($routes[1] instanceof Closure) {
|
||||
|
@ -118,9 +118,7 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf
|
||||
if (! $response || ! $response instanceof ResponseInterface) {
|
||||
$response = $this->transferToResponse($response);
|
||||
}
|
||||
if (! $response) {
|
||||
$this->logger->debug(sprintf('No content to response at fd[%d]', $fd));
|
||||
} else {
|
||||
if ($response) {
|
||||
$server->send($fd, (string) $response);
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,6 @@ interface DataFormatterInterface
|
||||
public function formatRequest($data);
|
||||
|
||||
public function formatResponse($data);
|
||||
|
||||
public function formatErrorResponse($data);
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ class RegisterServiceListener implements ListenerInterface
|
||||
*/
|
||||
public function process(object $event)
|
||||
{
|
||||
foreach ($this->consulAgent->services()->json() as $service) {
|
||||
$this->consulAgent->deregisterService($service['ID']);
|
||||
}
|
||||
$services = $this->serviceManager->all();
|
||||
$servers = $this->getServers();
|
||||
foreach ($services as $serviceName => $paths) {
|
||||
@ -94,7 +97,7 @@ class RegisterServiceListener implements ListenerInterface
|
||||
} else {
|
||||
$nextId = $this->generateId($this->getLastServiceId($serviceName));
|
||||
}
|
||||
$response = $this->consulAgent->registerService([
|
||||
$requestBody = [
|
||||
'Name' => $serviceName,
|
||||
'ID' => $nextId,
|
||||
'Address' => $address,
|
||||
@ -102,12 +105,22 @@ class RegisterServiceListener implements ListenerInterface
|
||||
'Meta' => [
|
||||
'Protocol' => $service['protocol'],
|
||||
],
|
||||
'Check' => [
|
||||
];
|
||||
if ($service['protocol'] === 'jsonrpc-http') {
|
||||
$requestBody['Check'] = [
|
||||
'DeregisterCriticalServiceAfter' => '90m',
|
||||
'HTTP' => "http://{$address}:{$port}/",
|
||||
'Interval' => '1s',
|
||||
],
|
||||
]);
|
||||
];
|
||||
}
|
||||
if ($service['protocol'] === 'jsonrpc') {
|
||||
$requestBody['Check'] = [
|
||||
'DeregisterCriticalServiceAfter' => '90m',
|
||||
'TCP' => "{$address}:{$port}",
|
||||
'Interval' => '1s',
|
||||
];
|
||||
}
|
||||
$response = $this->consulAgent->registerService($requestBody);
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$this->logger->info(sprintf('Service %s[%s]:%s register to the consul successfully.', $serviceName, $path, $nextId), $this->defaultLoggerContext);
|
||||
} else {
|
||||
@ -209,6 +222,14 @@ class RegisterServiceListener implements ListenerInterface
|
||||
|
||||
private function getInternalIp(): string
|
||||
{
|
||||
return gethostbyname(gethostname());
|
||||
$ips = swoole_get_local_ip();
|
||||
if (is_array($ips)) {
|
||||
return current($ips);
|
||||
}
|
||||
$ip = gethostbyname(gethostname());
|
||||
if (is_string($ip)) {
|
||||
return $ip;
|
||||
}
|
||||
throw new \RuntimeException('Can not get the internal IP.');
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use Hyperf\View\Engine\SmartyEngine;
|
||||
use Hyperf\View\Exception\EngineNotFindException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class Render
|
||||
class Render implements RenderInterface
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
|
Loading…
Reference in New Issue
Block a user