This commit is contained in:
张奇峰 2019-07-25 02:07:20 +08:00
commit 62469f2ba4
36 changed files with 805 additions and 77 deletions

View File

@ -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 ## 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. - [#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 ## 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 ## Fixed
- [#235](https://github.com/hyperf-cloud/hyperf/pull/235) Added default exception handler for `grpc-server` and optimized code. - [#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. - [#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 ## Added

View File

@ -150,6 +150,7 @@
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"HyperfTest\\AsyncQueue\\": "./src/async-queue/tests/", "HyperfTest\\AsyncQueue\\": "./src/async-queue/tests/",
"HyperfTest\\Cache\\": "./src/cache/tests/",
"HyperfTest\\ConfigApollo\\": "./src/config-apollo/tests/", "HyperfTest\\ConfigApollo\\": "./src/config-apollo/tests/",
"HyperfTest\\Constants\\": "./src/constants/tests/", "HyperfTest\\Constants\\": "./src/constants/tests/",
"HyperfTest\\Consul\\": "./src/consul/tests/", "HyperfTest\\Consul\\": "./src/consul/tests/",
@ -182,8 +183,8 @@
"config": [ "config": [
"Hyperf\\Amqp\\ConfigProvider", "Hyperf\\Amqp\\ConfigProvider",
"Hyperf\\AsyncQueue\\ConfigProvider", "Hyperf\\AsyncQueue\\ConfigProvider",
"Hyperf\\CircuitBreaker\\ConfigProvider",
"Hyperf\\Cache\\ConfigProvider", "Hyperf\\Cache\\ConfigProvider",
"Hyperf\\CircuitBreaker\\ConfigProvider",
"Hyperf\\Config\\ConfigProvider", "Hyperf\\Config\\ConfigProvider",
"Hyperf\\ConfigApollo\\ConfigProvider", "Hyperf\\ConfigApollo\\ConfigProvider",
"Hyperf\\Devtool\\ConfigProvider", "Hyperf\\Devtool\\ConfigProvider",

View File

@ -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,
],
];
```

View File

@ -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` 文件夹内,每个选项都有说明,您可以随时查看并熟悉有哪些选项可以使用。
# 安装 # 安装

View File

@ -39,6 +39,7 @@ namespace App\JsonRpc;
use Hyperf\RpcServer\Annotation\RpcService; use Hyperf\RpcServer\Annotation\RpcService;
/** /**
* 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性
* @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="jsonrpc-http") * @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="jsonrpc-http")
*/ */
class CalculatorService implements CalculatorServiceInterface class CalculatorService implements CalculatorServiceInterface
@ -131,11 +132,11 @@ return [
配置完成后在启动服务时Hyperf 会自动地将 `@RpcService` 定义了 `publishTo` 属性为 `consul` 的服务注册到服务中心去。 配置完成后在启动服务时Hyperf 会自动地将 `@RpcService` 定义了 `publishTo` 属性为 `consul` 的服务注册到服务中心去。
> 目前仅支持 `jsonrpc-http` 协议发布到服务中心去,其它协议的健康检查尚未实现 > 目前仅支持 `jsonrpc` 和 `jsonrpc-http` 协议发布到服务中心去,其它协议尚未实现服务注册
## 定义服务消费者 ## 定义服务消费者
一个 `服务消费者(ServiceConsumer)` 可以理解为就是一个客户端类,但在 Hyperf 里您无需处理连接和请求相关的事情,只需要定义一个类及相关属性即可。(v1.1会提供动态代理实现的客户端,使之更加简单便捷) 一个 `服务消费者(ServiceConsumer)` 可以理解为就是一个客户端类,但在 Hyperf 里您无需处理连接和请求相关的事情,只需要定义一个类及相关属性即可。(后续版本迭代会提供动态代理实现的客户端,使之更加简单便捷)
```php ```php
<?php <?php
@ -144,7 +145,7 @@ namespace App\JsonRpc;
use Hyperf\RpcClient\AbstractServiceClient; 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 ```php
return [ return [
'dependencies' => [ 'dependencies' => [
App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorService::class, App\JsonRpc\CalculatorServiceInterface::class => App\JsonRpc\CalculatorServiceConsumer::class,
], ],
]; ];
``` ```

View File

@ -39,4 +39,4 @@ class CalculatorService implements CalculatorServiceInterface
`server` 属性为绑定该服务类发布所要承载的 `Server`,默认值为 `jsonrpc-http`,该属性对应 `config/autoload/server.php` 文件内 `servers` 下所对应的 `name`,这里也就意味着我们需要定义一个对应的 `Server`,我们下一章节具体阐述这里应该怎样去处理; `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) 组件; `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;` 命名空间。

View File

@ -35,6 +35,7 @@ return [
]; ];
``` ```
## 使用 ## 使用
Task 组件提供了 `主动方法投递``注解投递` 两种使用方法。 Task 组件提供了 `主动方法投递``注解投递` 两种使用方法。
@ -66,6 +67,7 @@ $exec = $container->get(TaskExecutor::class);
$result = $exec->execute(new Task([MethodTask::class, 'handle'], Coroutine::id())); $result = $exec->execute(new Task([MethodTask::class, 'handle'], Coroutine::id()));
``` ```
### 使用注解 ### 使用注解
通过 `主动方法投递` 时,并不是特别直观,这里我们实现了对应的 `@Task` 注解,并通过 `AOP` 重写了方法调用。当在 `Worker` 进程时,自动投递到 `Task` 进程,并协程等待 数据返回。 通过 `主动方法投递` 时,并不是特别直观,这里我们实现了对应的 `@Task` 注解,并通过 `AOP` 重写了方法调用。当在 `Worker` 进程时,自动投递到 `Task` 进程,并协程等待 数据返回。
@ -111,3 +113,87 @@ Swoole 暂时没有协程化的函数列表
- pdo_odbc - pdo_odbc
- pdo_firebird - 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,
]);
```

View File

@ -11,6 +11,7 @@
<testsuites> <testsuites>
<testsuite name="Tests"> <testsuite name="Tests">
<directory suffix="Test.php">./src/async-queue/tests</directory> <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/constants/tests</directory>
<directory suffix="Test.php">./src/consul/tests</directory> <directory suffix="Test.php">./src/consul/tests</directory>
<directory suffix="Test.php">./src/database/tests</directory> <directory suffix="Test.php">./src/database/tests</directory>

View File

@ -139,7 +139,7 @@ class Connection extends BaseConnection implements ConnectionInterface
$class = AMQPSwooleConnection::class; $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()); 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,13 +149,8 @@ class Connection extends BaseConnection implements ConnectionInterface
return false; return false;
} }
$lastHeartbeatTime = $this->lastHeartbeatTime; if (microtime(true) - $this->lastHeartbeatTime > $this->params->getHeartbeat()) {
$currentTime = microtime(true); return true;
if ($lastHeartbeatTime && $lastHeartbeatTime > 0) {
if ($currentTime - $lastHeartbeatTime > $this->params->getHeartbeat()) {
return true;
}
} }
return false; return false;

View 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;
}
}

View 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;
}

View 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();
}
}

View 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));
}]);
}
}

View File

@ -86,7 +86,7 @@ class OnPipeMessageListener implements ListenerInterface
$this->config->set($key, $value); $this->config->set($key, $value);
$this->logger->debug(sprintf('Config [%s] is updated', $key)); $this->logger->debug(sprintf('Config [%s] is updated', $key));
} }
ReleaseKey::set($cacheKey, $data['releaseKey']); ReleaseKey::set($cacheKey, $data->releaseKey);
} }
} }
} }

View 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;
}

View File

@ -54,7 +54,7 @@ abstract class AbstractAnnotation implements AnnotationInterface, Arrayable
AnnotationCollector::collectProperty($className, $target, static::class, $this); 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'])) { if (isset($value['value'])) {
$this->{$key} = $value['value']; $this->{$key} = $value['value'];

View File

@ -239,11 +239,11 @@ class Request implements RequestInterface
$host .= ':' . $port; $host .= ':' . $port;
} }
$header = 'host';
if ($this->hasHeader('host')) { if ($this->hasHeader('host')) {
$header = $this->getHeaderLine('host'); $host = $this->getHeaderLine('host');
} else { } else {
$header = 'Host'; $this->headerNames['host'] = 'host';
$this->headerNames['host'] = 'Host';
} }
// Ensure Host is the first header. // Ensure Host is the first header.
$this->headers = [$header => [$host]] + $this->headers; $this->headers = [$header => [$host]] + $this->headers;

View File

@ -20,6 +20,21 @@ use Hyperf\Utils\Str;
*/ */
class RequestMapping extends Mapping 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 * @var array
*/ */
@ -29,8 +44,16 @@ class RequestMapping extends Mapping
{ {
parent::__construct($value); parent::__construct($value);
if (isset($value['methods'])) { if (isset($value['methods'])) {
// Explode a string to a array if (is_string($value['methods'])) {
$this->methods = explode(',', Str::upper(str_replace(' ', '', $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;
}
} }
} }
} }

View File

@ -146,15 +146,18 @@ class CoreMiddleware implements MiddlewareInterface
/** /**
* Transfer the non-standard response content to a standard response object. * 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 protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
{ {
if (is_string($response)) { 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() return $this->response()
->withAddedHeader('content-type', 'application/json') ->withAddedHeader('content-type', 'application/json')
->withBody(new SwooleStream(json_encode($response, JSON_UNESCAPED_UNICODE))); ->withBody(new SwooleStream(json_encode($response, JSON_UNESCAPED_UNICODE)));
@ -166,7 +169,7 @@ class CoreMiddleware implements MiddlewareInterface
->withBody(new SwooleStream((string) $response)); ->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));
} }
/** /**

View File

@ -105,7 +105,7 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface
$psr7Response = $exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); $psr7Response = $exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
} finally { } finally {
// Send the Response to client. // Send the Response to client.
if (! $psr7Response || ! $psr7Response instanceof Psr7Response) { if (! isset($psr7Response) || ! $psr7Response instanceof Psr7Response) {
return; return;
} }
$psr7Response->send(); $psr7Response->send();

View File

@ -12,12 +12,18 @@ declare(strict_types=1);
namespace HyperfTest\HttpServer; namespace HyperfTest\HttpServer;
use Hyperf\HttpServer\CoreMiddleware;
use Hyperf\HttpServer\Router\DispatcherFactory; use Hyperf\HttpServer\Router\DispatcherFactory;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use HyperfTest\HttpServer\Stub\CoreMiddlewareStub; use HyperfTest\HttpServer\Stub\CoreMiddlewareStub;
use HyperfTest\HttpServer\Stub\DemoController; use HyperfTest\HttpServer\Stub\DemoController;
use Mockery; use Mockery;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionMethod;
/** /**
* @internal * @internal
@ -35,6 +41,60 @@ class CoreMiddlewareTest extends TestCase
$this->assertSame([$id, 'Hyperf', []], $params); $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() protected function getContainer()
{ {
$container = Mockery::mock(ContainerInterface::class); $container = Mockery::mock(ContainerInterface::class);

View 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);
}
}

View File

@ -12,7 +12,9 @@ declare(strict_types=1);
namespace HyperfTest\HttpServer\Stub; namespace HyperfTest\HttpServer\Stub;
use Hyperf\HttpMessage\Server\Response;
use Hyperf\HttpServer\CoreMiddleware; use Hyperf\HttpServer\CoreMiddleware;
use Psr\Http\Message\ResponseInterface;
class CoreMiddlewareStub extends CoreMiddleware class CoreMiddlewareStub extends CoreMiddleware
{ {
@ -20,4 +22,10 @@ class CoreMiddlewareStub extends CoreMiddleware
{ {
return parent::parseParameters($controller, $action, $arguments); return parent::parseParameters($controller, $action, $arguments);
} }
protected function response(): ResponseInterface
{
return new Response();
}
} }

View File

@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Hyperf\JsonRpc; namespace Hyperf\JsonRpc;
use Hyperf\HttpMessage\Stream\SwooleStream; use Closure;
use Hyperf\Rpc\ProtocolManager; use Hyperf\Rpc\ProtocolManager;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@ -38,6 +38,11 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
*/ */
protected $packer; protected $packer;
/**
* @var \Hyperf\JsonRpc\ResponseBuilder
*/
protected $responseBuilder;
public function __construct(ContainerInterface $container, string $serverName) public function __construct(ContainerInterface $container, string $serverName)
{ {
parent::__construct($container, $serverName); parent::__construct($container, $serverName);
@ -45,18 +50,41 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
$protocolName = 'jsonrpc'; $protocolName = 'jsonrpc';
$this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName)); $this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName));
$this->packer = $container->get($this->protocolManager->getPacker($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 protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
{ {
return $this->response() return $this->responseBuilder->buildResponse($request, $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);
} }
} }

View File

@ -18,11 +18,12 @@ class DataFormatter implements DataFormatterInterface
{ {
public function formatRequest($data) public function formatRequest($data)
{ {
[$path, $params] = $data; [$path, $params, $id] = $data;
return [ return [
'jsonrpc' => '2.0', 'jsonrpc' => '2.0',
'method' => $path, 'method' => $path,
'params' => $params, 'params' => $params,
'id' => $id,
]; ];
} }
@ -35,4 +36,20 @@ class DataFormatter implements DataFormatterInterface
'result' => $result, '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,
],
];
}
} }

View File

@ -28,6 +28,10 @@ class HttpCoreMiddleware extends CoreMiddleware
$protocolName = 'jsonrpc-http'; $protocolName = 'jsonrpc-http';
$this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName)); $this->dataFormatter = $container->get($this->protocolManager->getDataFormatter($protocolName));
$this->packer = $container->get($this->protocolManager->getPacker($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) protected function handleNotFound(ServerRequestInterface $request)

View File

@ -37,6 +37,11 @@ class HttpServer extends Server
*/ */
protected $packer; protected $packer;
/**
* @var \Hyperf\JsonRpc\ResponseBuilder
*/
protected $responseBuilder;
public function __construct( public function __construct(
string $serverName, string $serverName,
string $coreHandler, string $coreHandler,
@ -46,8 +51,13 @@ class HttpServer extends Server
) { ) {
parent::__construct($serverName, $coreHandler, $container, $dispatcher); parent::__construct($serverName, $coreHandler, $container, $dispatcher);
$this->protocolManager = $protocolManager; $this->protocolManager = $protocolManager;
$packerClass = $this->protocolManager->getPacker('jsonrpc-http'); $protocolName = 'jsonrpc-http';
$packerClass = $this->protocolManager->getPacker($protocolName);
$this->packer = $this->container->get($packerClass); $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 protected function initRequestAndResponse(SwooleRequest $request, SwooleResponse $response): array
@ -56,22 +66,24 @@ class HttpServer extends Server
$psr7Request = Psr7Request::loadFromSwooleRequest($request); $psr7Request = Psr7Request::loadFromSwooleRequest($request);
if (! $this->isHealthCheck($psr7Request)) { if (! $this->isHealthCheck($psr7Request)) {
if (strpos($psr7Request->getHeaderLine('content-type'), 'application/json') === false) { 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()); $content = $this->packer->unpack($psr7Request->getBody()->getContents());
if (! isset($content['jsonrpc'], $content['method'], $content['params'])) { 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'] ?? '/')) $psr7Request = $psr7Request->withUri($psr7Request->getUri()->withPath($content['method'] ?? '/'))
->withParsedBody($content['params'] ?? null) ->withParsedBody($content['params'] ?? null)
->withAttribute('data', $content ?? []); ->withAttribute('data', $content ?? [])
->withAttribute('request_id', $content['id'] ?? null);
Context::set(ServerRequestInterface::class, $psr7Request); Context::set(ServerRequestInterface::class, $psr7Request);
Context::set(ResponseInterface::class, $psr7Response = new Psr7Response($response)); Context::set(ResponseInterface::class, $psr7Response = new Psr7Response($response));
return [$psr7Request, $psr7Response]; return [$psr7Request, $psr7Response];
} }
protected function isHealthCheck(RequestInterface $request) protected function isHealthCheck(RequestInterface $request): bool
{ {
return $request->getHeaderLine('user-agent') === 'Consul Health Check'; return $request->getHeaderLine('user-agent') === 'Consul Health Check';
} }

View 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);
}
}

View File

@ -18,7 +18,6 @@ use Hyperf\HttpMessage\Uri\Uri;
use Hyperf\Rpc\ProtocolManager; use Hyperf\Rpc\ProtocolManager;
use Hyperf\RpcServer\Server; use Hyperf\RpcServer\Server;
use Hyperf\Server\ServerManager; use Hyperf\Server\ServerManager;
use InvalidArgumentException;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@ -37,6 +36,11 @@ class TcpServer extends Server
*/ */
protected $packer; protected $packer;
/**
* @var \Hyperf\JsonRpc\ResponseBuilder
*/
protected $responseBuilder;
public function __construct( public function __construct(
string $serverName, string $serverName,
string $coreHandler, string $coreHandler,
@ -47,8 +51,13 @@ class TcpServer extends Server
) { ) {
parent::__construct($serverName, $coreHandler, $container, $dispatcher, $logger); parent::__construct($serverName, $coreHandler, $container, $dispatcher, $logger);
$this->protocolManager = $protocolManager; $this->protocolManager = $protocolManager;
$packerClass = $this->protocolManager->getPacker('jsonrpc'); $protocolName = 'jsonrpc';
$packerClass = $this->protocolManager->getPacker($protocolName);
$this->packer = $this->container->get($packerClass); $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 protected function buildResponse(int $fd, SwooleServer $server): ResponseInterface
@ -62,10 +71,7 @@ class TcpServer extends Server
$class = $this->protocolManager->getPacker('jsonrpc'); $class = $this->protocolManager->getPacker('jsonrpc');
$packer = $this->container->get($class); $packer = $this->container->get($class);
$data = $this->packer->unpack($data); $data = $this->packer->unpack($data);
if (isset($data['jsonrpc'])) { return $this->buildJsonRpcRequest($fd, $fromId, $data);
return $this->buildJsonRpcRequest($fd, $fromId, $data);
}
throw new InvalidArgumentException('Doesn\'t match JSON RPC protocol.');
} }
protected function buildJsonRpcRequest(int $fd, int $fromId, array $data) protected function buildJsonRpcRequest(int $fd, int $fromId, array $data)
@ -80,9 +86,14 @@ class TcpServer extends Server
[$type, $port] = ServerManager::get($this->serverName); [$type, $port] = ServerManager::get($this->serverName);
$uri = (new Uri())->withPath($data['method'])->withHost($port->host)->withPort($port->port); $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('fromId', $fromId)
->withAttribute('data', $data) ->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;
} }
} }

View File

@ -16,6 +16,7 @@ use Hyperf\Consul\Agent;
use Hyperf\Consul\Health; use Hyperf\Consul\Health;
use Hyperf\Consul\HealthInterface; use Hyperf\Consul\HealthInterface;
use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\IdGeneratorInterface;
use Hyperf\Contract\PackerInterface; use Hyperf\Contract\PackerInterface;
use Hyperf\Guzzle\ClientFactory; use Hyperf\Guzzle\ClientFactory;
use Hyperf\LoadBalancer\LoadBalancerInterface; use Hyperf\LoadBalancer\LoadBalancerInterface;
@ -44,7 +45,7 @@ abstract class AbstractServiceClient
* *
* @var string * @var string
*/ */
protected $protocol = 'jsonrpc'; protected $protocol = 'jsonrpc-http';
/** /**
* The load balancer of the client, this name of the load balancer * The load balancer of the client, this name of the load balancer
@ -89,6 +90,11 @@ abstract class AbstractServiceClient
*/ */
protected $config; protected $config;
/**
* @var null|\Hyperf\Contract\IdGeneratorInterface
*/
protected $idGenerator;
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
$this->container = $container; $this->container = $container;
@ -101,13 +107,24 @@ abstract class AbstractServiceClient
$this->client = make(Client::class) $this->client = make(Client::class)
->setPacker($this->createPacker()) ->setPacker($this->createPacker())
->setTransporter($transporter); ->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 ($this->idGenerator instanceof IdGeneratorInterface && ! $id) {
if (is_array($response) && isset($response['result'])) { $id = $this->idGenerator->generate();
return $response['result']; }
$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.'); throw new RuntimeException('Invalid response.');
} }
@ -120,9 +137,9 @@ abstract class AbstractServiceClient
return $this->pathGenerator->generate($this->serviceName, $methodName); 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 protected function createLoadBalancer(array $nodes, callable $refresh = null): LoadBalancerInterface

View File

@ -39,7 +39,7 @@ class Client
$packer = $this->getPacker(); $packer = $this->getPacker();
$packedData = $packer->pack($data); $packedData = $packer->pack($data);
$response = $this->getTransporter()->send($packedData); $response = $this->getTransporter()->send($packedData);
return $packer->unpack($response); return $packer->unpack((string) $response);
} }
public function getPacker(): PackerInterface public function getPacker(): PackerInterface

View File

@ -32,11 +32,6 @@ class CoreMiddleware extends \Hyperf\HttpServer\CoreMiddleware
$this->dispatcher = $factory->getDispatcher($serverName); $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) protected function handleFound(array $routes, ServerRequestInterface $request)
{ {
if ($routes[1] instanceof Closure) { if ($routes[1] instanceof Closure) {

View File

@ -118,9 +118,7 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf
if (! $response || ! $response instanceof ResponseInterface) { if (! $response || ! $response instanceof ResponseInterface) {
$response = $this->transferToResponse($response); $response = $this->transferToResponse($response);
} }
if (! $response) { if ($response) {
$this->logger->debug(sprintf('No content to response at fd[%d]', $fd));
} else {
$server->send($fd, (string) $response); $server->send($fd, (string) $response);
} }
} }

View File

@ -17,4 +17,6 @@ interface DataFormatterInterface
public function formatRequest($data); public function formatRequest($data);
public function formatResponse($data); public function formatResponse($data);
public function formatErrorResponse($data);
} }

View File

@ -74,6 +74,9 @@ class RegisterServiceListener implements ListenerInterface
*/ */
public function process(object $event) public function process(object $event)
{ {
foreach ($this->consulAgent->services()->json() as $service) {
$this->consulAgent->deregisterService($service['ID']);
}
$services = $this->serviceManager->all(); $services = $this->serviceManager->all();
$servers = $this->getServers(); $servers = $this->getServers();
foreach ($services as $serviceName => $paths) { foreach ($services as $serviceName => $paths) {
@ -94,7 +97,7 @@ class RegisterServiceListener implements ListenerInterface
} else { } else {
$nextId = $this->generateId($this->getLastServiceId($serviceName)); $nextId = $this->generateId($this->getLastServiceId($serviceName));
} }
$response = $this->consulAgent->registerService([ $requestBody = [
'Name' => $serviceName, 'Name' => $serviceName,
'ID' => $nextId, 'ID' => $nextId,
'Address' => $address, 'Address' => $address,
@ -102,12 +105,22 @@ class RegisterServiceListener implements ListenerInterface
'Meta' => [ 'Meta' => [
'Protocol' => $service['protocol'], 'Protocol' => $service['protocol'],
], ],
'Check' => [ ];
if ($service['protocol'] === 'jsonrpc-http') {
$requestBody['Check'] = [
'DeregisterCriticalServiceAfter' => '90m', 'DeregisterCriticalServiceAfter' => '90m',
'HTTP' => "http://{$address}:{$port}/", 'HTTP' => "http://{$address}:{$port}/",
'Interval' => '1s', 'Interval' => '1s',
], ];
]); }
if ($service['protocol'] === 'jsonrpc') {
$requestBody['Check'] = [
'DeregisterCriticalServiceAfter' => '90m',
'TCP' => "{$address}:{$port}",
'Interval' => '1s',
];
}
$response = $this->consulAgent->registerService($requestBody);
if ($response->getStatusCode() === 200) { if ($response->getStatusCode() === 200) {
$this->logger->info(sprintf('Service %s[%s]:%s register to the consul successfully.', $serviceName, $path, $nextId), $this->defaultLoggerContext); $this->logger->info(sprintf('Service %s[%s]:%s register to the consul successfully.', $serviceName, $path, $nextId), $this->defaultLoggerContext);
} else { } else {
@ -209,6 +222,14 @@ class RegisterServiceListener implements ListenerInterface
private function getInternalIp(): string 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.');
} }
} }

View File

@ -20,7 +20,7 @@ use Hyperf\View\Engine\SmartyEngine;
use Hyperf\View\Exception\EngineNotFindException; use Hyperf\View\Exception\EngineNotFindException;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
class Render class Render implements RenderInterface
{ {
/** /**
* @var ContainerInterface * @var ContainerInterface