├── .gitignore
├── src
├── Health
│ ├── GPBMetadata
│ │ └── Health.php
│ ├── proto
│ │ └── health.proto
│ ├── HealthInterface.php
│ ├── StreamHealth.php
│ ├── HealthCheckRequest.php
│ ├── HealthCheckResponse.php
│ ├── HealthClient.php
│ ├── Health.php
│ └── ServingStatus.php
├── Reflection
│ ├── GPBMetadata
│ │ └── Reflection.php
│ ├── ServerReflectionInterface.php
│ ├── StreamReflection.php
│ ├── ServerReflectionClient.php
│ ├── ServiceResponse.php
│ ├── ListServiceResponse.php
│ ├── ErrorResponse.php
│ ├── ExtensionRequest.php
│ ├── FileDescriptorResponse.php
│ ├── ExtensionNumberResponse.php
│ ├── Reflection.php
│ ├── proto
│ │ └── reflection.proto
│ ├── ServerReflectionRequest.php
│ └── ServerReflectionResponse.php
├── Exception
│ ├── GrpcStreamException.php
│ └── GrpcException.php
├── Server
│ ├── Http2Frame
│ │ ├── FrameParserInterface.php
│ │ ├── Http2Frame.php
│ │ └── FrameParser.php
│ ├── Http2Stream
│ │ ├── Http2Stream.php
│ │ └── StreamManager.php
│ ├── Server.php
│ ├── StreamServer.php
│ ├── Response
│ │ └── GrpcStream.php
│ └── Handler
│ │ └── StreamHandler.php
├── Tracer
│ └── TracerFactory.php
├── Listener
│ ├── ServerStartListener.php
│ ├── RegisterConsul4GrpcDriverListener.php
│ └── RegisterGrpcServiceListener.php
├── GrpcHelper.php
├── ConfigProvider.php
├── Middleware
│ └── GrpcTraceMiddleware.php
└── Consul
│ └── ConsulDriver.php
├── tests
├── bootstrap.php
└── Cases
│ ├── AbstractTestCase.php
│ └── ExampleTest.php
├── LICENSE
├── publish
├── grpc.php
├── services.php
└── opentracing.php
├── composer.json
├── README.md
└── class_map
└── Protobuf
└── DescriptorPool.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor/
3 | composer.lock
4 | *.cache
5 | *.log
--------------------------------------------------------------------------------
/src/Health/GPBMetadata/Health.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crayxn/hyperf-grpc/HEAD/src/Health/GPBMetadata/Health.php
--------------------------------------------------------------------------------
/src/Reflection/GPBMetadata/Reflection.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crayxn/hyperf-grpc/HEAD/src/Reflection/GPBMetadata/Reflection.php
--------------------------------------------------------------------------------
/src/Exception/GrpcStreamException.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Exception;
9 |
10 | class GrpcStreamException extends \Exception
11 | {
12 | }
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Http2Frame;
9 |
10 | interface FrameParserInterface
11 | {
12 | public function unpack(string $frame_data,&$result): void;
13 |
14 | public function pack(Http2Frame $frame): string;
15 | }
--------------------------------------------------------------------------------
/tests/Cases/AbstractTestCase.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Http2Stream;
9 |
10 | use Swoole\Coroutine\Channel;
11 |
12 | class Http2Stream
13 | {
14 | public Channel $receiveChannel;
15 |
16 | public bool $active = true;
17 |
18 | public function __construct(public int $id)
19 | {
20 | // create receive channel
21 | $this->receiveChannel = new Channel(5);
22 | }
23 | }
--------------------------------------------------------------------------------
/tests/Cases/ExampleTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Exception/GrpcException.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Exception;
9 |
10 | use Hyperf\Grpc\StatusCode;
11 | use Hyperf\GrpcServer\Exception\Handler\GrpcExceptionHandler;
12 | use PHPUnit\Event\Code\Throwable;
13 |
14 | class GrpcException extends \Hyperf\GrpcServer\Exception\GrpcException
15 | {
16 | public function __construct(string $message = '', int $code = 0, int $statusCode = StatusCode::ABORTED, ?Throwable $previous = null)
17 | {
18 | $message = "$code#$message";
19 | parent::__construct($message, $statusCode, $previous);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Health/proto/health.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package grpc.health.v1;
4 |
5 | option php_generic_services = true;
6 | option php_namespace = "Crayoon\\HyperfGrpc\\Health";
7 | option php_metadata_namespace = "Crayoon\\HyperfGrpc\\Health\\GPBMetadata";
8 |
9 | message HealthCheckRequest {
10 | string service = 1;
11 | }
12 |
13 | enum ServingStatus {
14 | UNKNOWN = 0;
15 | SERVING = 1;
16 | NOT_SERVING = 2;
17 | SERVICE_UNKNOWN = 3; // Used only by the Watch method.
18 | }
19 |
20 | message HealthCheckResponse {
21 | ServingStatus status = 1;
22 | }
23 |
24 | service Health {
25 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
26 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
27 | }
--------------------------------------------------------------------------------
/src/Tracer/TracerFactory.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Tracer;
9 |
10 | use Exception;
11 | use Hyperf\Stringable\Str;
12 | use Hyperf\Tracer\Contract\NamedFactoryInterface;
13 |
14 | class TracerFactory implements NamedFactoryInterface
15 | {
16 | /**
17 | * @throws Exception
18 | */
19 | public function make(string $name): \OpenTracing\Tracer
20 | {
21 | $class = sprintf("OpenTracing\\%sTracer", Str::studly($name));
22 | if (!class_exists($class)) {
23 | throw new Exception("$class Tracer no found");
24 | }
25 | return new $class;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Reflection/ServerReflectionInterface.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ServerReflection
9 | */
10 | interface ServerReflectionInterface
11 | {
12 | /**
13 | * The reflection service is structured as a bidirectional stream, ensuring
14 | * all related requests go to a single server.
15 | *
16 | * Method serverReflectionInfo
17 | *
18 | * @param \Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest $request
19 | * @return \Crayoon\HyperfGrpc\Reflection\ServerReflectionResponse
20 | */
21 | public function serverReflectionInfo(\Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest $request);
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/Health/HealthInterface.php:
--------------------------------------------------------------------------------
1 | grpc.health.v1.Health
9 | */
10 | interface HealthInterface
11 | {
12 | /**
13 | * Method check
14 | *
15 | * @param \Crayoon\HyperfGrpc\Health\HealthCheckRequest $request
16 | * @return \Crayoon\HyperfGrpc\Health\HealthCheckResponse
17 | */
18 | public function check(\Crayoon\HyperfGrpc\Health\HealthCheckRequest $request);
19 |
20 | /**
21 | * Method watch
22 | *
23 | * @param \Crayoon\HyperfGrpc\Health\HealthCheckRequest $request
24 | * @return \Crayoon\HyperfGrpc\Health\HealthCheckResponse
25 | */
26 | public function watch(\Crayoon\HyperfGrpc\Health\HealthCheckRequest $request);
27 |
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/Listener/ServerStartListener.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Listener;
9 |
10 | use Hyperf\Context\ApplicationContext;
11 | use Hyperf\Contract\ContainerInterface;
12 | use Hyperf\Event\Contract\ListenerInterface;
13 | use Hyperf\Framework\Event\OnStart;
14 |
15 | class ServerStartListener implements ListenerInterface
16 | {
17 | public function listen(): array
18 | {
19 | return [
20 | OnStart::class
21 | ];
22 | }
23 |
24 | public function process(object $event): void
25 | {
26 | if($event instanceof \Swoole\Server){
27 | /**
28 | * @var ContainerInterface $container
29 | */
30 | $container = ApplicationContext::getContainer();
31 | $container->set(\Swoole\Server::class, $event);
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Reflection/StreamReflection.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Reflection;
9 |
10 | use Crayoon\HyperfGrpc\Server\Handler\StreamHandler;
11 | use Crayoon\HyperfGrpc\Server\Http2Frame\Http2Frame;
12 | use Google\Protobuf\Internal\DescriptorPool;
13 | use Hyperf\Context\Context;
14 | use Hyperf\Grpc\Parser;
15 | use Hyperf\Grpc\StatusCode;
16 | use Hyperf\GrpcServer\Exception\GrpcException;
17 | use Psr\Http\Message\ServerRequestInterface;
18 |
19 | class StreamReflection extends Reflection
20 | {
21 | public function streamServerReflectionInfo(): void
22 | {
23 | /**
24 | * @var StreamHandler $handler
25 | */
26 | $handler = Context::get(StreamHandler::class);
27 | /**
28 | * @var ServerReflectionRequest $request
29 | */
30 | while (Http2Frame::EOF !== $request = $handler->receive(ServerReflectionRequest::class)) {
31 | $handler->push($this->serverReflectionInfo($request));
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Server/Http2Frame/Http2Frame.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Http2Frame;
9 |
10 | use Hyperf\Grpc\Parser;
11 |
12 | class Http2Frame
13 | {
14 | const HTTP2_FRAME_TYPE_HEAD = 1;
15 | const HTTP2_FRAME_TYPE_DATA = 0;
16 | const HTTP2_FRAME_TYPE_RST = 3;
17 | const HTTP2_FRAME_TYPE_SETTING = 4;
18 | const HTTP2_FRAME_TYPE_GOAWAY = 7;
19 | const HTTP2_FLAG_NONE = 0;
20 | const HTTP2_FLAG_ACK = 1;
21 | const HTTP2_FLAG_END_STREAM = 1;
22 | const HTTP2_FLAG_END_HEADERS = 4;
23 | const HTTP2_FLAG_PADDED = 8;
24 | const HTTP2_FLAG_PRIORITY = 20;
25 |
26 | const SETTING_HEX = '00030000008000040000ffff000500004000';
27 | const EOF = '\r\n';
28 |
29 | public int $length = 0;
30 |
31 | public function __construct(
32 | public string $payload,
33 | public int $type,
34 | public int $flags,
35 | public int $streamId
36 | )
37 | {
38 | $this->length = strlen($this->payload);
39 | }
40 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Hyperf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Health/StreamHealth.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Health;
9 |
10 | use Crayoon\HyperfGrpc\Server\Handler\StreamHandler;
11 | use Hyperf\Context\Context;
12 |
13 | class StreamHealth extends Health
14 | {
15 | public function streamCheck(): void
16 | {
17 | /**
18 | * @var StreamHandler $handler
19 | */
20 | $handler = Context::get(StreamHandler::class);
21 | $response = new HealthCheckResponse();
22 | $response->setStatus(ServingStatus::SERVING);
23 | $handler->push($response);
24 | }
25 |
26 | public function streamWatch(): void
27 | {
28 | /**
29 | * @var StreamHandler $handler
30 | */
31 | $handler = Context::get(StreamHandler::class);
32 | $response = new HealthCheckResponse();
33 | $response->setStatus(ServingStatus::SERVING);
34 | //Streaming Response
35 | while (true) {
36 | if (!$handler->push($response)) {
37 | break;
38 | };
39 | sleep($this->config['wait'] ?? 300);
40 | }
41 | $this->stdoutLogger->debug("Grpc watcher close");
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Listener/RegisterConsul4GrpcDriverListener.php:
--------------------------------------------------------------------------------
1 | driverManager = $container->get(DriverManager::class);
24 | $this->config = $container->get(ConfigInterface::class);
25 | }
26 |
27 | public function listen(): array {
28 | return [
29 | BootApplication::class,
30 | ];
31 | }
32 |
33 | public function process(object $event): void {
34 | if ($this->config->get("grpc.register.enable") && $this->config->get("grpc.register.driver_name") == "consul4grpc") {
35 | $this->driverManager->register('consul4grpc', make(ConsulDriver::class));
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/publish/grpc.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | return [
9 | // 服务注册
10 | "register" => [
11 | //是否开启注册
12 | "enable" => (bool)\Hyperf\Support\env("REGISTER_ENABLE", true),
13 | // 对应服务名称 默认grpc
14 | "server_name" => "grpc",
15 | // 支持 nacos、consul4grpc、consul
16 | "driver_name" => \Hyperf\Support\env("REGISTER_DRIVER", "nacos"),
17 | // 负载算法 支持 random、round-robin、weighted-random、weighted-round-robin 默认round-robin
18 | "algo" => \Hyperf\Support\env("REGISTER_ALGO", "round-robin"),
19 | ],
20 | "trace" => [
21 | //是否开启追踪
22 | "enable" => (bool)\Hyperf\Support\env("TRACER_ENABLE", true)
23 | ],
24 | "reflection" => [
25 | //是否开启服务反射 默认是true
26 | "enable" => (bool)\Hyperf\Support\env("REFLECTION_ENABLE", true),
27 | //反射路径 指protoc生成的GPBMetadata文件路径
28 | "path" => \Hyperf\Support\env("REFLECTION_PATH", 'app/Grpc/GPBMetadata'),
29 | //需要引入的 基础proto文件名 如 google/protobuf/struct.proto
30 | "base_files" => [
31 | 'google/protobuf/struct.proto',
32 | 'google/protobuf/empty.proto',
33 | 'google/protobuf/any.proto',
34 | 'google/protobuf/timestamp.proto',
35 | 'google/protobuf/duration.proto'
36 | ],
37 | ]
38 | ];
--------------------------------------------------------------------------------
/src/Health/HealthCheckRequest.php:
--------------------------------------------------------------------------------
1 | grpc.health.v1.HealthCheckRequest
13 | */
14 | class HealthCheckRequest extends \Google\Protobuf\Internal\Message
15 | {
16 | /**
17 | * Generated from protobuf field string service = 1;
18 | */
19 | protected $service = '';
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param array $data {
25 | * Optional. Data for populating the Message object.
26 | *
27 | * @type string $service
28 | * }
29 | */
30 | public function __construct($data = NULL) {
31 | \Crayoon\HyperfGrpc\Health\GPBMetadata\Health::initOnce();
32 | parent::__construct($data);
33 | }
34 |
35 | /**
36 | * Generated from protobuf field string service = 1;
37 | * @return string
38 | */
39 | public function getService()
40 | {
41 | return $this->service;
42 | }
43 |
44 | /**
45 | * Generated from protobuf field string service = 1;
46 | * @param string $var
47 | * @return $this
48 | */
49 | public function setService($var)
50 | {
51 | GPBUtil::checkString($var, True);
52 | $this->service = $var;
53 |
54 | return $this;
55 | }
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/publish/services.php:
--------------------------------------------------------------------------------
1 | [
14 | 'discovery' => true,
15 | 'register' => true,
16 | ],
17 | 'consumers' => [],
18 | 'providers' => [],
19 | 'drivers' => [
20 | 'consul' => [
21 | 'uri' => env("CONSUL_HOST", "consul:8500"),
22 | 'token' => env("CONSUL_TOKEN", "consul:8500"),
23 | 'check' => [
24 | 'deregister_critical_service_after' => '90m',
25 | 'interval' => '5s',
26 | ],
27 | ],
28 | 'nacos' => [
29 | // nacos server url like https://nacos.hyperf.io, Priority is higher than host:port
30 | // 'url' => '',
31 | // The nacos host info
32 | 'host' => env("NACOS_HOST", "nacos"),
33 | 'port' => intval(env("NACOS_PORT", 8848)),
34 | // The nacos account info
35 | 'username' => env("NACOS_USER", "nacos"),
36 | 'password' => env("NACOS_PWD", "nacos"),
37 | 'guzzle' => [
38 | 'config' => null,
39 | ],
40 | 'group_name' => env("NACOS_GROUP", "api"),
41 | 'namespace_id' => env("NACOS_NAMESPACE", ""),
42 | 'heartbeat' => intval(env("NACOS_HEARTBEAT", 5)),
43 | 'ephemeral' => true,
44 | ],
45 | ],
46 | ];
47 |
--------------------------------------------------------------------------------
/src/Health/HealthCheckResponse.php:
--------------------------------------------------------------------------------
1 | grpc.health.v1.HealthCheckResponse
13 | */
14 | class HealthCheckResponse extends \Google\Protobuf\Internal\Message
15 | {
16 | /**
17 | * Generated from protobuf field .grpc.health.v1.ServingStatus status = 1;
18 | */
19 | protected $status = 0;
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param array $data {
25 | * Optional. Data for populating the Message object.
26 | *
27 | * @type int $status
28 | * }
29 | */
30 | public function __construct($data = NULL) {
31 | \Crayoon\HyperfGrpc\Health\GPBMetadata\Health::initOnce();
32 | parent::__construct($data);
33 | }
34 |
35 | /**
36 | * Generated from protobuf field .grpc.health.v1.ServingStatus status = 1;
37 | * @return int
38 | */
39 | public function getStatus()
40 | {
41 | return $this->status;
42 | }
43 |
44 | /**
45 | * Generated from protobuf field .grpc.health.v1.ServingStatus status = 1;
46 | * @param int $var
47 | * @return $this
48 | */
49 | public function setStatus($var)
50 | {
51 | GPBUtil::checkEnum($var, \Crayoon\HyperfGrpc\Health\ServingStatus::class);
52 | $this->status = $var;
53 |
54 | return $this;
55 | }
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/src/Health/HealthClient.php:
--------------------------------------------------------------------------------
1 | _simpleRequest('/grpc.health.v1.Health/Check',
28 | $argument,
29 | ['\Crayoon\HyperfGrpc\Health\HealthCheckResponse', 'decode'],
30 | $metadata, $options);
31 | }
32 |
33 | /**
34 | * @param \Crayoon\HyperfGrpc\Health\HealthCheckRequest $argument input argument
35 | * @param array $metadata metadata
36 | * @param array $options call options
37 | * @return \Grpc\ServerStreamingCall
38 | */
39 | public function Watch(\Crayoon\HyperfGrpc\Health\HealthCheckRequest $argument,
40 | $metadata = [], $options = []) {
41 | return $this->_serverStreamRequest('/grpc.health.v1.Health/Watch',
42 | $argument,
43 | ['\Crayoon\HyperfGrpc\Health\HealthCheckResponse', 'decode'],
44 | $metadata, $options);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Health/Health.php:
--------------------------------------------------------------------------------
1 | config = $config->get("grpc.health") ?? [];
24 | }
25 |
26 | public function check(HealthCheckRequest $request): HealthCheckResponse
27 | {
28 | $response = new HealthCheckResponse();
29 | $response->setStatus(ServingStatus::SERVING);
30 | return $response;
31 | }
32 |
33 | public function watch(HealthCheckRequest $request): HealthCheckResponse
34 | {
35 | $response = new HealthCheckResponse();
36 | $response->setStatus(ServingStatus::SERVING);
37 | //Streaming Response
38 | try {
39 | $stream = new GrpcStream();
40 | while (true) {
41 | if (!$stream->write($response)) {
42 | break;
43 | };
44 | sleep($this->config['wait'] ?? 300);
45 | }
46 | $stream->close();
47 | $this->stdoutLogger->debug("Grpc watcher close");
48 | } catch (GrpcStreamException $exception) {
49 | $this->stdoutLogger->error("Create stream fail: " . $exception->getMessage());
50 | }
51 |
52 | return $response;
53 | }
54 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crayoon/hyperf-grpc",
3 | "type": "library",
4 | "license": "MIT",
5 | "keywords": [
6 | "php",
7 | "hyperf",
8 | "grpc"
9 | ],
10 | "description": "hyperf grpc extend",
11 | "autoload": {
12 | "psr-4": {
13 | "Crayoon\\HyperfGrpc\\": "src/"
14 | }
15 | },
16 | "autoload-dev": {
17 | "psr-4": {
18 | "HyperfTest\\HyperfGrpc\\": "tests"
19 | }
20 | },
21 | "require": {
22 | "php": ">=8.0",
23 | "amphp/hpack": "^3.1",
24 | "hyperf/di": "3.0.*",
25 | "hyperf/grpc-server": "^3.0",
26 | "hyperf/http-server": "^3.0",
27 | "hyperf/service-governance": "^3.0",
28 | "hyperf/service-governance-consul": "^3.0",
29 | "hyperf/service-governance-nacos": "^3.0",
30 | "hyperf/tracer": "^3.0",
31 | "hyperf/utils": "^3.0",
32 | "jonahgeorge/jaeger-client-php": "^1.4"
33 | },
34 | "require-dev": {
35 | "friendsofphp/php-cs-fixer": "^3.0",
36 | "mockery/mockery": "^1.0",
37 | "phpstan/phpstan": "^1.0",
38 | "phpunit/phpunit": ">=7.0",
39 | "swoole/ide-helper": "^4.5"
40 | },
41 | "suggest": {
42 | },
43 | "minimum-stability": "dev",
44 | "prefer-stable": true,
45 | "config": {
46 | "optimize-autoloader": true,
47 | "sort-packages": true
48 | },
49 | "scripts": {
50 | "test": "phpunit -c phpunit.xml --colors=always",
51 | "analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
52 | "cs-fix": "php-cs-fixer fix $1"
53 | },
54 | "extra": {
55 | "hyperf": {
56 | "config": "Crayoon\\HyperfGrpc\\ConfigProvider"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Server/Http2Stream/StreamManager.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Http2Stream;
9 |
10 | /**
11 | * stream manager
12 | */
13 | class StreamManager
14 | {
15 | /**
16 | * @var Http2Stream[] $pool
17 | */
18 | private array $pool = [];
19 |
20 | /**
21 | * @param int $streamId
22 | * @return Http2Stream
23 | */
24 | public function get(int $streamId): Http2Stream
25 | {
26 | if (!isset($this->pool[$streamId])) $this->pool[$streamId] = new Http2Stream($streamId);
27 | return $this->pool[$streamId];
28 | }
29 |
30 | /**
31 | * @param int $streamId
32 | * @return void
33 | */
34 | public function remove(int $streamId): void
35 | {
36 | if (isset($this->pool[$streamId])) {
37 | if ($this->pool[$streamId] instanceof Http2Stream) {
38 | //close channel
39 | $this->pool[$streamId]->receiveChannel->close();
40 | }
41 | unset($this->pool[$streamId]);
42 | }
43 | }
44 |
45 | /**
46 | * @param int $streamId
47 | * @return bool
48 | */
49 | public function checkActive(int $streamId): bool
50 | {
51 | return isset($this->pool[$streamId]) ? $this->pool[$streamId]->active : false;
52 | }
53 |
54 | /**
55 | * @param int $streamId
56 | * @return void
57 | */
58 | public function down(int $streamId): void
59 | {
60 | if (isset($this->pool[$streamId])) {
61 | $stream = $this->pool[$streamId];
62 | $stream->active = false;
63 | $this->pool[$streamId] = $stream;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/Health/ServingStatus.php:
--------------------------------------------------------------------------------
1 | grpc.health.v1.ServingStatus
11 | */
12 | class ServingStatus
13 | {
14 | /**
15 | * Generated from protobuf enum UNKNOWN = 0;
16 | */
17 | const UNKNOWN = 0;
18 | /**
19 | * Generated from protobuf enum SERVING = 1;
20 | */
21 | const SERVING = 1;
22 | /**
23 | * Generated from protobuf enum NOT_SERVING = 2;
24 | */
25 | const NOT_SERVING = 2;
26 | /**
27 | * Used only by the Watch method.
28 | *
29 | * Generated from protobuf enum SERVICE_UNKNOWN = 3;
30 | */
31 | const SERVICE_UNKNOWN = 3;
32 |
33 | private static $valueToName = [
34 | self::UNKNOWN => 'UNKNOWN',
35 | self::SERVING => 'SERVING',
36 | self::NOT_SERVING => 'NOT_SERVING',
37 | self::SERVICE_UNKNOWN => 'SERVICE_UNKNOWN',
38 | ];
39 |
40 | public static function name($value)
41 | {
42 | if (!isset(self::$valueToName[$value])) {
43 | throw new UnexpectedValueException(sprintf(
44 | 'Enum %s has no name defined for value %s', __CLASS__, $value));
45 | }
46 | return self::$valueToName[$value];
47 | }
48 |
49 |
50 | public static function value($name)
51 | {
52 | $const = __CLASS__ . '::' . strtoupper($name);
53 | if (!defined($const)) {
54 | throw new UnexpectedValueException(sprintf(
55 | 'Enum %s has no value defined for name %s', __CLASS__, $name));
56 | }
57 | return constant($const);
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/src/Reflection/ServerReflectionClient.php:
--------------------------------------------------------------------------------
1 | _bidiRequest('/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo',
45 | ['\Crayoon\HyperfGrpc\Reflection\ServerReflectionResponse','decode'],
46 | $metadata, $options);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # crayoon/hyperf-grpc
2 |
3 | Hyperf Grpc 服务插件,协助完成grpc服务注册、服务链路追踪、服务健康、服务反射等
4 |
5 | 使用教程 https://learnku.com/articles/75681 如果有帮助到您的话,还请给个星哦
6 |
7 | *请先阅读hyperf文档grpc服务一节 https://hyperf.wiki/3.0/#/zh-cn/grpc*
8 |
9 | ## Update
10 | ### [2.x]
11 | - 服务处理重写,支持GRPC全模式(一元模式、客户端流模式、服务端流模式、双向流模式)
12 |
13 | ### [1.x]
14 | - 重构服务反射,提升速度
15 | - TracerDriver 默认配置调整为Noop类型
16 | - 服务注册优化异常时跳过
17 | - 增加支持 Grpc Streaming
18 | - 增积支持 GrpcHealth Watch Streaming模式
19 |
20 | ## 快速开始
21 |
22 | ### 引入
23 |
24 | ```
25 | composer require crayoon/hyperf-grpc dev-2.0-alpha
26 | ```
27 |
28 | ### 生成配置文件
29 |
30 | ```
31 | php bin/hyperf.php vendor:publish crayoon/hyperf-grpc
32 | ```
33 |
34 | ### 配置
35 | 修改 config/autoload/annotations.php
36 | ```php
37 | return [
38 | 'scan' => [
39 | 'paths' => [
40 | BASE_PATH . '/app',
41 | ],
42 | 'ignore_annotations' => [
43 | 'mixin',
44 | ],
45 | 'class_map' => [
46 | \Google\Protobuf\Internal\DescriptorPool::class => BASE_PATH.'/vendor/crayoon/hyperf-grpc/class_map/Protobuf/DescriptorPool.php'
47 | ]
48 | ]
49 | ];
50 | ```
51 | 修改 config/autoload/server.php
52 | ```php
53 | 'servers' => [
54 | [
55 | 'name' => 'grpc',
56 | 'type' => Server::SERVER_BASE,
57 | 'host' => '0.0.0.0',
58 | 'port' => 9501,
59 | 'sock_type' => SWOOLE_SOCK_TCP,
60 | 'callbacks' => [
61 | Event::ON_RECEIVE => [\Crayoon\HyperfGrpc\Server\StreamServer::class, 'onReceive']
62 | ],
63 | ],
64 | ...
65 | ],
66 | ```
67 |
68 | ### 流模式使用
69 |
70 | ```php
71 | // config/routes.php
72 | // 路由使用助手类注册
73 | GrpcHelper::RegisterRoutes(function () {
74 | // 在此处添加路由
75 | Router::addGroup('/goods.v1.Goods', function () {
76 | Router::post('/info', [\App\Controller\Grpc\GoodsController::class, "info"]);
77 | ...
78 | });
79 | ...
80 | }, 'grpc', [], true);
81 | ```
82 |
83 | 可以参考 https://github.com/crayxn/grpc-stream-demo
84 |
--------------------------------------------------------------------------------
/src/GrpcHelper.php:
--------------------------------------------------------------------------------
1 |
6 | * @contact crayxn@qq.com
7 | */
8 |
9 | namespace Crayoon\HyperfGrpc;
10 |
11 | use Crayoon\HyperfGrpc\Health\Health;
12 | use Crayoon\HyperfGrpc\Health\StreamHealth;
13 | use Crayoon\HyperfGrpc\Middleware\GrpcTraceMiddleware;
14 | use Crayoon\HyperfGrpc\Reflection\Reflection;
15 | use Crayoon\HyperfGrpc\Reflection\StreamReflection;
16 | use Crayoon\HyperfGrpc\Server\Handler\StreamHandler;
17 | use Hyperf\Context\Context;
18 | use Hyperf\HttpServer\Router\Router;
19 |
20 | class GrpcHelper
21 | {
22 |
23 | /**
24 | * register routers
25 | * @param callable $callback
26 | * @param string $serverName
27 | * @param array $options
28 | * @return void
29 | */
30 | public static function RegisterRoutes(callable $callback, string $serverName = "grpc", array $options = [], bool $streamMode = false): void
31 | {
32 | Router::addServer($serverName, function () use ($callback, $options, $streamMode) {
33 | //reflection
34 | Router::addGroup('/grpc.reflection.v1alpha.ServerReflection', function () use ($streamMode) {
35 | Router::post('/ServerReflectionInfo', $streamMode ? [StreamReflection::class, 'streamServerReflectionInfo'] : [Reflection::class, 'serverReflectionInfo']);
36 | }, [
37 | "register" => false
38 | ]);
39 |
40 | //health
41 | Router::addGroup('/grpc.health.v1.Health', function () use ($streamMode) {
42 | Router::post('/Check', $streamMode ? [StreamHealth::class, 'streamCheck'] : [Health::class, 'check']);
43 | Router::post('/Watch', $streamMode ? [StreamHealth::class, 'streamWatch'] : [Health::class, 'watch']);
44 | }, [
45 | "register" => false
46 | ]);
47 |
48 | //other
49 | Router::addGroup("", $callback, array_merge([
50 | "middleware" => [
51 | GrpcTraceMiddleware::class
52 | ]
53 | ], $options));
54 | });
55 | }
56 | }
--------------------------------------------------------------------------------
/src/ConfigProvider.php:
--------------------------------------------------------------------------------
1 |
6 | * @contact crayxn@qq.com
7 | */
8 |
9 | namespace Crayoon\HyperfGrpc;
10 |
11 | use Crayoon\HyperfGrpc\Listener\RegisterConsul4GrpcDriverListener;
12 | use Crayoon\HyperfGrpc\Listener\RegisterGrpcServiceListener;
13 | use Crayoon\HyperfGrpc\Server\Http2Frame\FrameParser;
14 | use Crayoon\HyperfGrpc\Server\Http2Frame\FrameParserInterface;
15 |
16 | class ConfigProvider {
17 | public function __invoke(): array {
18 | return [
19 | 'dependencies' => [
20 | FrameParserInterface::class => FrameParser::class
21 | ],
22 | 'commands' => [
23 | ],
24 | 'annotations' => [
25 | 'scan' => [
26 | 'paths' => [
27 | __DIR__,
28 | ],
29 | ],
30 | ],
31 | 'listeners' => [
32 | RegisterConsul4GrpcDriverListener::class,
33 | RegisterGrpcServiceListener::class
34 | ],
35 | 'publish' => [
36 | [
37 | 'id' => 'config',
38 | 'description' => 'the config for grpc',
39 | 'source' => __DIR__ . '/../publish/grpc.php',
40 | 'destination' => BASE_PATH . '/config/autoload/grpc.php',
41 | ],
42 | [
43 | 'id' => 'config',
44 | 'description' => 'The config for tracer.',
45 | 'source' => __DIR__ . '/../publish/opentracing.php',
46 | 'destination' => BASE_PATH . '/config/autoload/opentracing.php',
47 | ],
48 | [
49 | 'id' => 'config',
50 | 'description' => 'The config for services.',
51 | 'source' => __DIR__ . '/../publish/services.php',
52 | 'destination' => BASE_PATH . '/config/autoload/services.php',
53 | ],
54 | ]
55 | ];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Reflection/ServiceResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ServiceResponse
16 | */
17 | class ServiceResponse extends \Google\Protobuf\Internal\Message
18 | {
19 | /**
20 | * Full name of a registered service, including its package name. The format
21 | * is .
22 | *
23 | * Generated from protobuf field string name = 1;
24 | */
25 | protected $name = '';
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param array $data {
31 | * Optional. Data for populating the Message object.
32 | *
33 | * @type string $name
34 | * Full name of a registered service, including its package name. The format
35 | * is .
36 | * }
37 | */
38 | public function __construct($data = NULL) {
39 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
40 | parent::__construct($data);
41 | }
42 |
43 | /**
44 | * Full name of a registered service, including its package name. The format
45 | * is .
46 | *
47 | * Generated from protobuf field string name = 1;
48 | * @return string
49 | */
50 | public function getName()
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * Full name of a registered service, including its package name. The format
57 | * is .
58 | *
59 | * Generated from protobuf field string name = 1;
60 | * @param string $var
61 | * @return $this
62 | */
63 | public function setName($var)
64 | {
65 | GPBUtil::checkString($var, True);
66 | $this->name = $var;
67 |
68 | return $this;
69 | }
70 |
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/src/Server/Http2Frame/FrameParser.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Http2Frame;
9 |
10 | use Amp\Http\HPack;
11 | use Amp\Http\HPackException;
12 |
13 | class FrameParser implements FrameParserInterface
14 | {
15 | protected HPack $hPack;
16 |
17 | public function __construct()
18 | {
19 | $this->hPack = new HPack();
20 | }
21 |
22 | /**
23 | * @param string $frame_data
24 | * @param null|Http2Frame[] $result
25 | * @throws \Exception
26 | */
27 | public function unpack(string $frame_data, &$result): void
28 | {
29 | if (strlen($frame_data) < 9) {
30 | return;
31 | }
32 | //header
33 | $lengthPack = unpack('C3', substr($frame_data, 0, 3));
34 | $length = ($lengthPack[1] << 16) | ($lengthPack[2] << 8) | $lengthPack[3];
35 | $headers = unpack('Ctype/Cflags/NstreamId', substr($frame_data, 3, 6));
36 | $result[] = new Http2Frame(
37 | substr($frame_data, 9, $length), //payload
38 | $headers['type'],
39 | $headers['flags'],
40 | $headers['streamId'] & 0x7FFFFFFF
41 | );
42 | if ('' != $next = substr($frame_data, $length + 9)) {
43 | static::unpack($next, $result);
44 | }
45 | }
46 |
47 | public function pack(Http2Frame $frame): string
48 | {
49 | return (substr(pack("NccN", $frame->length, $frame->type, $frame->flags, $frame->streamId), 1) . $frame->payload);
50 | }
51 |
52 | public function decodeHeaderFrame(Http2Frame $frame): ?array
53 | {
54 | if ($frame->type !== Http2Frame::HTTP2_FRAME_TYPE_HEAD) return null;
55 | return $this->hPack->decode($frame->payload, 4096);
56 | }
57 |
58 | public function encodeHeaderFrame($headers, $streamId): ?Http2Frame
59 | {
60 | try {
61 | $compressedHeaders = $this->hPack->encode($headers);
62 | return new Http2Frame(
63 | $compressedHeaders,
64 | Http2Frame::HTTP2_FRAME_TYPE_HEAD,
65 | Http2Frame::HTTP2_FLAG_END_HEADERS,
66 | $streamId);
67 | } catch (HPackException) {
68 | return null;
69 | }
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/src/Reflection/ListServiceResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ListServiceResponse
15 | */
16 | class ListServiceResponse extends \Google\Protobuf\Internal\Message
17 | {
18 | /**
19 | * The information of each service may be expanded in the future, so we use
20 | * ServiceResponse message to encapsulate it.
21 | *
22 | * Generated from protobuf field repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;
23 | */
24 | private $service;
25 |
26 | /**
27 | * Constructor.
28 | *
29 | * @param array $data {
30 | * Optional. Data for populating the Message object.
31 | *
32 | * @type \Crayoon\HyperfGrpc\Reflection\ServiceResponse[]|\Google\Protobuf\Internal\RepeatedField $service
33 | * The information of each service may be expanded in the future, so we use
34 | * ServiceResponse message to encapsulate it.
35 | * }
36 | */
37 | public function __construct($data = NULL) {
38 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
39 | parent::__construct($data);
40 | }
41 |
42 | /**
43 | * The information of each service may be expanded in the future, so we use
44 | * ServiceResponse message to encapsulate it.
45 | *
46 | * Generated from protobuf field repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;
47 | * @return \Google\Protobuf\Internal\RepeatedField
48 | */
49 | public function getService()
50 | {
51 | return $this->service;
52 | }
53 |
54 | /**
55 | * The information of each service may be expanded in the future, so we use
56 | * ServiceResponse message to encapsulate it.
57 | *
58 | * Generated from protobuf field repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;
59 | * @param \Crayoon\HyperfGrpc\Reflection\ServiceResponse[]|\Google\Protobuf\Internal\RepeatedField $var
60 | * @return $this
61 | */
62 | public function setService($var)
63 | {
64 | $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Crayoon\HyperfGrpc\Reflection\ServiceResponse::class);
65 | $this->service = $arr;
66 |
67 | return $this;
68 | }
69 |
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/src/Reflection/ErrorResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ErrorResponse
15 | */
16 | class ErrorResponse extends \Google\Protobuf\Internal\Message
17 | {
18 | /**
19 | * This field uses the error codes defined in grpc::StatusCode.
20 | *
21 | * Generated from protobuf field int32 error_code = 1;
22 | */
23 | protected $error_code = 0;
24 | /**
25 | * Generated from protobuf field string error_message = 2;
26 | */
27 | protected $error_message = '';
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * @param array $data {
33 | * Optional. Data for populating the Message object.
34 | *
35 | * @type int $error_code
36 | * This field uses the error codes defined in grpc::StatusCode.
37 | * @type string $error_message
38 | * }
39 | */
40 | public function __construct($data = NULL) {
41 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
42 | parent::__construct($data);
43 | }
44 |
45 | /**
46 | * This field uses the error codes defined in grpc::StatusCode.
47 | *
48 | * Generated from protobuf field int32 error_code = 1;
49 | * @return int
50 | */
51 | public function getErrorCode()
52 | {
53 | return $this->error_code;
54 | }
55 |
56 | /**
57 | * This field uses the error codes defined in grpc::StatusCode.
58 | *
59 | * Generated from protobuf field int32 error_code = 1;
60 | * @param int $var
61 | * @return $this
62 | */
63 | public function setErrorCode($var)
64 | {
65 | GPBUtil::checkInt32($var);
66 | $this->error_code = $var;
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * Generated from protobuf field string error_message = 2;
73 | * @return string
74 | */
75 | public function getErrorMessage()
76 | {
77 | return $this->error_message;
78 | }
79 |
80 | /**
81 | * Generated from protobuf field string error_message = 2;
82 | * @param string $var
83 | * @return $this
84 | */
85 | public function setErrorMessage($var)
86 | {
87 | GPBUtil::checkString($var, True);
88 | $this->error_message = $var;
89 |
90 | return $this;
91 | }
92 |
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/src/Reflection/ExtensionRequest.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ExtensionRequest
16 | */
17 | class ExtensionRequest extends \Google\Protobuf\Internal\Message
18 | {
19 | /**
20 | * Fully-qualified type name. The format should be .
21 | *
22 | * Generated from protobuf field string containing_type = 1;
23 | */
24 | protected $containing_type = '';
25 | /**
26 | * Generated from protobuf field int32 extension_number = 2;
27 | */
28 | protected $extension_number = 0;
29 |
30 | /**
31 | * Constructor.
32 | *
33 | * @param array $data {
34 | * Optional. Data for populating the Message object.
35 | *
36 | * @type string $containing_type
37 | * Fully-qualified type name. The format should be .
38 | * @type int $extension_number
39 | * }
40 | */
41 | public function __construct($data = NULL) {
42 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
43 | parent::__construct($data);
44 | }
45 |
46 | /**
47 | * Fully-qualified type name. The format should be .
48 | *
49 | * Generated from protobuf field string containing_type = 1;
50 | * @return string
51 | */
52 | public function getContainingType()
53 | {
54 | return $this->containing_type;
55 | }
56 |
57 | /**
58 | * Fully-qualified type name. The format should be .
59 | *
60 | * Generated from protobuf field string containing_type = 1;
61 | * @param string $var
62 | * @return $this
63 | */
64 | public function setContainingType($var)
65 | {
66 | GPBUtil::checkString($var, True);
67 | $this->containing_type = $var;
68 |
69 | return $this;
70 | }
71 |
72 | /**
73 | * Generated from protobuf field int32 extension_number = 2;
74 | * @return int
75 | */
76 | public function getExtensionNumber()
77 | {
78 | return $this->extension_number;
79 | }
80 |
81 | /**
82 | * Generated from protobuf field int32 extension_number = 2;
83 | * @param int $var
84 | * @return $this
85 | */
86 | public function setExtensionNumber($var)
87 | {
88 | GPBUtil::checkInt32($var);
89 | $this->extension_number = $var;
90 |
91 | return $this;
92 | }
93 |
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/src/Reflection/FileDescriptorResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.FileDescriptorResponse
17 | */
18 | class FileDescriptorResponse extends \Google\Protobuf\Internal\Message
19 | {
20 | /**
21 | * Serialized FileDescriptorProto messages. We avoid taking a dependency on
22 | * descriptor.proto, which uses proto2 only features, by making them opaque
23 | * bytes instead.
24 | *
25 | * Generated from protobuf field repeated bytes file_descriptor_proto = 1;
26 | */
27 | private $file_descriptor_proto;
28 |
29 | /**
30 | * Constructor.
31 | *
32 | * @param array $data {
33 | * Optional. Data for populating the Message object.
34 | *
35 | * @type string[]|\Google\Protobuf\Internal\RepeatedField $file_descriptor_proto
36 | * Serialized FileDescriptorProto messages. We avoid taking a dependency on
37 | * descriptor.proto, which uses proto2 only features, by making them opaque
38 | * bytes instead.
39 | * }
40 | */
41 | public function __construct($data = NULL) {
42 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
43 | parent::__construct($data);
44 | }
45 |
46 | /**
47 | * Serialized FileDescriptorProto messages. We avoid taking a dependency on
48 | * descriptor.proto, which uses proto2 only features, by making them opaque
49 | * bytes instead.
50 | *
51 | * Generated from protobuf field repeated bytes file_descriptor_proto = 1;
52 | * @return \Google\Protobuf\Internal\RepeatedField
53 | */
54 | public function getFileDescriptorProto()
55 | {
56 | return $this->file_descriptor_proto;
57 | }
58 |
59 | /**
60 | * Serialized FileDescriptorProto messages. We avoid taking a dependency on
61 | * descriptor.proto, which uses proto2 only features, by making them opaque
62 | * bytes instead.
63 | *
64 | * Generated from protobuf field repeated bytes file_descriptor_proto = 1;
65 | * @param string[]|\Google\Protobuf\Internal\RepeatedField $var
66 | * @return $this
67 | */
68 | public function setFileDescriptorProto($var)
69 | {
70 | $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::BYTES);
71 | $this->file_descriptor_proto = $arr;
72 |
73 | return $this;
74 | }
75 |
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/src/Middleware/GrpcTraceMiddleware.php:
--------------------------------------------------------------------------------
1 | tracer = $container->get(Tracer::class);
34 | $this->config = $container->get(ConfigInterface::class);
35 | }
36 |
37 | /**
38 | * @throws Throwable
39 | */
40 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
41 | // 是否全局关闭 链路追踪
42 | if ($this->config->get("grpc.trace.enable") !== true) {
43 | return $handler->handle($request);
44 | }
45 |
46 | $option = [];
47 | // 判断存在传递的父节点
48 | if ($request->hasHeader("tracer.carrier")) {
49 | $carrier = json_decode($request->getHeaderLine("tracer.carrier"));
50 | $spanContext = $this->tracer->extract(TEXT_MAP, $carrier);
51 | if ($spanContext) {
52 | $option['child_of'] = $spanContext;
53 | }
54 | }
55 |
56 | $path = $request->getUri()->getPath();
57 | $key = "[GRPC] {$path}";
58 | $span = $this->startSpan($key, $option);
59 | $span->setTag('rpc.path', $path);
60 | foreach ($request->getHeaders() as $key => $value) {
61 | $span->setTag('rpc.headers' . '.' . $key, implode(', ', $value));
62 | }
63 | try {
64 | /**
65 | * @var Response $response
66 | */
67 | $response = $handler->handle($request);
68 | $status = $response->getTrailer("grpc-status");
69 | if ($status != StatusCode::OK) {
70 | $span->setTag('error', true);
71 | }
72 | $span->setTag('rpc.status', $status);
73 | $span->setTag('rpc.message', $response->getTrailer("grpc-message"));
74 | } catch (Throwable $e) {
75 | $span->setTag('error', true);
76 | $span->log(['message' => $e->getMessage(), 'code' => $e->getCode(), 'stacktrace' => $e->getTraceAsString()]);
77 | throw $e;
78 | } finally {
79 | //提交
80 | $span->finish();
81 | $this->tracer->flush();
82 | }
83 | return $response;
84 | }
85 | }
--------------------------------------------------------------------------------
/src/Reflection/ExtensionNumberResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ExtensionNumberResponse
16 | */
17 | class ExtensionNumberResponse extends \Google\Protobuf\Internal\Message
18 | {
19 | /**
20 | * Full name of the base type, including the package name. The format
21 | * is .
22 | *
23 | * Generated from protobuf field string base_type_name = 1;
24 | */
25 | protected $base_type_name = '';
26 | /**
27 | * Generated from protobuf field repeated int32 extension_number = 2;
28 | */
29 | private $extension_number;
30 |
31 | /**
32 | * Constructor.
33 | *
34 | * @param array $data {
35 | * Optional. Data for populating the Message object.
36 | *
37 | * @type string $base_type_name
38 | * Full name of the base type, including the package name. The format
39 | * is .
40 | * @type int[]|\Google\Protobuf\Internal\RepeatedField $extension_number
41 | * }
42 | */
43 | public function __construct($data = NULL) {
44 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
45 | parent::__construct($data);
46 | }
47 |
48 | /**
49 | * Full name of the base type, including the package name. The format
50 | * is .
51 | *
52 | * Generated from protobuf field string base_type_name = 1;
53 | * @return string
54 | */
55 | public function getBaseTypeName()
56 | {
57 | return $this->base_type_name;
58 | }
59 |
60 | /**
61 | * Full name of the base type, including the package name. The format
62 | * is .
63 | *
64 | * Generated from protobuf field string base_type_name = 1;
65 | * @param string $var
66 | * @return $this
67 | */
68 | public function setBaseTypeName($var)
69 | {
70 | GPBUtil::checkString($var, True);
71 | $this->base_type_name = $var;
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Generated from protobuf field repeated int32 extension_number = 2;
78 | * @return \Google\Protobuf\Internal\RepeatedField
79 | */
80 | public function getExtensionNumber()
81 | {
82 | return $this->extension_number;
83 | }
84 |
85 | /**
86 | * Generated from protobuf field repeated int32 extension_number = 2;
87 | * @param int[]|\Google\Protobuf\Internal\RepeatedField $var
88 | * @return $this
89 | */
90 | public function setExtensionNumber($var)
91 | {
92 | $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::INT32);
93 | $this->extension_number = $arr;
94 |
95 | return $this;
96 | }
97 |
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/src/Server/Server.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server;
9 |
10 | use Hyperf\Context\Context;
11 | use Hyperf\Coordinator\Constants;
12 | use Hyperf\Coordinator\CoordinatorManager;
13 | use Hyperf\HttpMessage\Server\Response as Psr7Response;
14 | use Hyperf\HttpServer\Event\RequestHandled;
15 | use Hyperf\HttpServer\Event\RequestReceived;
16 | use Hyperf\HttpServer\Event\RequestTerminated;
17 | use Hyperf\HttpServer\MiddlewareManager;
18 | use Hyperf\HttpServer\Router\Dispatched;
19 | use Hyperf\Support\SafeCaller;
20 | use Psr\Http\Message\ResponseInterface;
21 | use Swoole\Http\Request;
22 | use Swoole\Http\Response;
23 | use function Hyperf\Coroutine\defer;
24 |
25 | class Server extends \Hyperf\GrpcServer\Server
26 | {
27 | public function onRequest($request, $response): void
28 | {
29 | try {
30 | CoordinatorManager::until(Constants::WORKER_START)->yield();
31 |
32 | [$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
33 |
34 | $this->option?->isEnableRequestLifecycle() && $this->event?->dispatch(new RequestReceived($psr7Request, $psr7Response));
35 |
36 | $psr7Request = $this->coreMiddleware->dispatch($psr7Request);
37 | /** @var Dispatched $dispatched */
38 | $dispatched = $psr7Request->getAttribute(Dispatched::class);
39 | $middlewares = $this->middlewares;
40 |
41 | if ($dispatched->isFound()) {
42 | $registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
43 | $middlewares = array_merge($middlewares, $registeredMiddlewares);
44 | }
45 |
46 | $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
47 | } catch (\Throwable $throwable) {
48 | // Delegate the exception to exception handler.
49 | $psr7Response = $this->container->get(SafeCaller::class)->call(function () use ($throwable) {
50 | return $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
51 | }, static function () {
52 | return (new Psr7Response())->withStatus(400);
53 | });
54 | } finally {
55 | if (isset($psr7Request) && $this->option?->isEnableRequestLifecycle()) {
56 | defer(fn() => $this->event?->dispatch(new RequestTerminated($psr7Request, $psr7Response ?? null, $throwable ?? null)));
57 |
58 | $this->event?->dispatch(new RequestHandled($psr7Request, $psr7Response ?? null, $throwable ?? null));
59 | }
60 |
61 | // Send the Response to client.
62 | // Add check the Response isWritable
63 | if (!isset($psr7Response) || !$psr7Response instanceof ResponseInterface || !$response->isWritable()) {
64 | return;
65 | }
66 |
67 |
68 | if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
69 | $this->responseEmitter->emit($psr7Response, $response, false);
70 | } else {
71 | $this->responseEmitter->emit($psr7Response, $response);
72 | }
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/publish/opentracing.php:
--------------------------------------------------------------------------------
1 | \Hyperf\Support\env('TRACER_DRIVER', 'noop'),
18 | 'enable' => [
19 | 'guzzle' => \Hyperf\Support\env('TRACER_ENABLE_GUZZLE', false),
20 | 'redis' => \Hyperf\Support\env('TRACER_ENABLE_REDIS', false),
21 | 'db' => \Hyperf\Support\env('TRACER_ENABLE_DB', false),
22 | 'method' => \Hyperf\Support\env('TRACER_ENABLE_METHOD', true),
23 | 'exception' => \Hyperf\Support\env('TRACER_ENABLE_EXCEPTION', false)
24 | ],
25 | 'tracer' => [
26 | 'noop' => [
27 | 'driver' => \Crayoon\HyperfGrpc\Tracer\TracerFactory::class
28 | ],
29 | 'zipkin' => [
30 | 'driver' => Hyperf\Tracer\Adapter\ZipkinTracerFactory::class,
31 | 'app' => [
32 | 'name' => \Hyperf\Support\env('APP_NAME', 'skeleton'),
33 | // Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null
34 | 'ipv4' => '127.0.0.1',
35 | 'ipv6' => null,
36 | 'port' => 9501,
37 | ],
38 | 'options' => [
39 | 'endpoint_url' => \Hyperf\Support\env('ZIPKIN_ENDPOINT_URL', 'http://localhost:9411/api/v2/spans'),
40 | 'timeout' => \Hyperf\Support\env('ZIPKIN_TIMEOUT', 1),
41 | ],
42 | 'sampler' => BinarySampler::createAsAlwaysSample(),
43 | ],
44 | 'jaeger' => [
45 | 'driver' => Hyperf\Tracer\Adapter\JaegerTracerFactory::class,
46 | 'name' => \Hyperf\Support\env('JAEGER_NAME', 'skeleton'),
47 | 'options' => [
48 | /*
49 | * You can uncomment the sampler lines to use custom strategy.
50 | *
51 | * For more available configurations,
52 | * @see https://github.com/jonahgeorge/jaeger-client-php
53 | */
54 | 'sampler' => [
55 | 'type' => \Jaeger\SAMPLER_TYPE_CONST,
56 | 'param' => true,
57 | ],
58 | 'local_agent' => [
59 | 'reporting_host' => \Hyperf\Support\env('JAEGER_REPORTING_HOST', 'jaeger'),
60 | 'reporting_port' => \Hyperf\Support\env('JAEGER_REPORTING_PORT', 14268),
61 | ],
62 | 'dispatch_mode' => \Jaeger\Config::JAEGER_OVER_BINARY_HTTP
63 | ],
64 | ],
65 | ],
66 | 'tags' => [
67 | 'http_client' => [
68 | 'http.url' => 'http.url',
69 | 'http.method' => 'http.method',
70 | 'http.status_code' => 'http.status_code',
71 | ],
72 | 'redis' => [
73 | 'arguments' => 'arguments',
74 | 'result' => 'result',
75 | ],
76 | 'db' => [
77 | 'db.query' => 'db.query',
78 | 'db.statement' => 'db.statement',
79 | 'db.query_time' => 'db.query_time',
80 | ],
81 | 'exception' => [
82 | 'class' => 'exception.class',
83 | 'code' => 'exception.code',
84 | 'message' => 'exception.message',
85 | 'stack_trace' => 'exception.stack_trace',
86 | ],
87 | 'request' => [
88 | 'path' => 'request.path',
89 | 'method' => 'request.method',
90 | 'header' => 'request.header',
91 | ],
92 | 'coroutine' => [
93 | 'id' => 'coroutine.id',
94 | ],
95 | 'response' => [
96 | 'status_code' => 'response.status_code',
97 | ],
98 | ],
99 | ];
100 |
--------------------------------------------------------------------------------
/src/Server/StreamServer.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server;
9 |
10 | use Crayoon\HyperfGrpc\Server\Handler\StreamHandler;
11 | use Crayoon\HyperfGrpc\Server\Http2Frame\FrameParser;
12 | use Crayoon\HyperfGrpc\Server\Http2Frame\Http2Frame;
13 | use Crayoon\HyperfGrpc\Server\Http2Stream\StreamManager;
14 | use Hyperf\Context\Context;
15 | use Hyperf\Coordinator\Constants;
16 | use Hyperf\Coordinator\CoordinatorManager;
17 | use Hyperf\Grpc\StatusCode;
18 | use Hyperf\GrpcServer\Server;
19 | use Hyperf\HttpServer\MiddlewareManager;
20 | use Hyperf\HttpServer\Router\Dispatched;
21 | use Hyperf\Support\SafeCaller;
22 | use Swoole\Http\Response;
23 | use Swoole\Server as SwooleServer;
24 | use Throwable;
25 |
26 | class StreamServer extends Server
27 | {
28 | /**
29 | * @param SwooleServer $server
30 | * @param int $fd
31 | * @param int $reactor_id
32 | * @param string $data
33 | * @return void
34 | * @throws Throwable
35 | */
36 | public function onReceive(SwooleServer $server, int $fd, int $reactor_id, string $data): void
37 | {
38 | $parser = $this->container->get(FrameParser::class);
39 | $streamManager = $this->container->get(StreamManager::class);
40 |
41 | $parser->unpack($this->format($data), $frames);
42 | if (empty($frames)) return;
43 |
44 | foreach ($frames as $frame) {
45 | if ($frame->type == Http2Frame::HTTP2_FRAME_TYPE_HEAD && $frame->length > 0) {
46 | // new request
47 | \Hyperf\Coroutine\go(function () use ($server, $fd, $frame) {
48 | $this->request($server, $fd, $frame);
49 | });
50 | } elseif ($frame->type == Http2Frame::HTTP2_FRAME_TYPE_DATA && $frame->flags != Http2Frame::HTTP2_FLAG_END_STREAM) {
51 | // push data
52 | $streamManager->get($frame->streamId)->receiveChannel->push($frame->payload);
53 | } elseif ($frame->type == Http2Frame::HTTP2_FRAME_TYPE_GOAWAY || $frame->type == Http2Frame::HTTP2_FRAME_TYPE_RST) {
54 | //the stream can not push
55 | $streamManager->down($frame->streamId);
56 | }
57 |
58 | // end stream
59 | if ($frame->flags == Http2Frame::HTTP2_FLAG_END_STREAM) {
60 | $streamManager->get($frame->streamId)->receiveChannel->push(Http2Frame::EOF);
61 | }
62 | }
63 | }
64 |
65 | /**
66 | * @param SwooleServer $server
67 | * @param int $fd
68 | * @param Http2Frame $headerFrame
69 | * @return void
70 | * @throws Throwable
71 | */
72 | public function request(SwooleServer $server, int $fd, Http2Frame $headerFrame): void
73 | {
74 | $streamManager = $this->container->get(StreamManager::class);
75 |
76 | // new request
77 | $handler = new StreamHandler($server, $fd, $headerFrame);
78 | Context::set(StreamHandler::class, $handler);
79 |
80 | try {
81 | CoordinatorManager::until(Constants::WORKER_START)->yield();
82 | [$psr7Request,] = $this->initRequestAndResponse($handler->getRequest(), new Response());
83 |
84 | $psr7Request = $this->coreMiddleware->dispatch($psr7Request);
85 | /** @var Dispatched $dispatched */
86 | $dispatched = $psr7Request->getAttribute(Dispatched::class);
87 | $middlewares = $this->middlewares;
88 |
89 | if ($dispatched->isFound()) {
90 | $registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
91 | $middlewares = array_merge($middlewares, $registeredMiddlewares);
92 | }
93 |
94 | $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
95 |
96 | // close request
97 | $handler->end();
98 | } catch (Throwable $throwable) {
99 | $this->container->get(SafeCaller::class)->call(function () use ($throwable) {
100 | return $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
101 | });
102 | $handler->end(StatusCode::ABORTED, 'Service error');
103 | } finally {
104 | // close the data channel
105 | $streamManager->remove($headerFrame->streamId);
106 | }
107 | }
108 |
109 | /**
110 | * @param $data
111 | * @return string
112 | */
113 | public function format($data): string
114 | {
115 | if (str_contains($data, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")) {
116 | $data = substr($data, 24);
117 | }
118 | return $data;
119 | }
120 | }
--------------------------------------------------------------------------------
/src/Reflection/Reflection.php:
--------------------------------------------------------------------------------
1 |
6 | * @contact crayxn@qq.com
7 | */
8 |
9 | namespace Crayoon\HyperfGrpc\Reflection;
10 |
11 | use Crayoon\HyperfGrpc\Health\GPBMetadata\Health;
12 | use Google\Protobuf\Internal\DescriptorPool;
13 | use Hyperf\Contract\ConfigInterface;
14 | use Hyperf\Di\ReflectionManager;
15 | use Hyperf\Grpc\StatusCode;
16 | use Hyperf\GrpcServer\Exception\GrpcException;
17 | use Hyperf\HttpServer\Router\DispatcherFactory;
18 | use Hyperf\HttpServer\Router\Handler;
19 | use Hyperf\Utils\Str;
20 | use Psr\Container\ContainerExceptionInterface;
21 | use Psr\Container\ContainerInterface;
22 | use Psr\Container\NotFoundExceptionInterface;
23 |
24 | class Reflection implements ServerReflectionInterface
25 | {
26 | protected ConfigInterface $config;
27 |
28 | protected DispatcherFactory $dispatcherFactory;
29 |
30 | protected array $servers = [];
31 | protected array $files = [];
32 |
33 | protected array $baseProtoFiles = [];
34 |
35 | public function __construct(protected ContainerInterface $container)
36 | {
37 | $this->config = $this->container->get(ConfigInterface::class);
38 | $this->dispatcherFactory = $this->container->get(DispatcherFactory::class);
39 | $this->baseProtoFiles = $this->config->get('grpc.reflection.base_files', []);
40 |
41 | $paths = $this->config->get("grpc.reflection.path");
42 | // Health
43 | Health::initOnce();
44 | try {
45 | // Other
46 | $class = ReflectionManager::getAllClasses(is_array($paths) ? $paths : [$paths]);
47 | foreach ($class as $item => $reflection) {
48 | call_user_func("{$item}::initOnce");
49 | }
50 | }catch (\Throwable $throwable){
51 | }
52 |
53 | //获取服务
54 | $this->servers = $this->servers();
55 | }
56 |
57 | /**
58 | * @param ServerReflectionRequest $request
59 | * @return ServerReflectionResponse
60 | */
61 | public function serverReflectionInfo(ServerReflectionRequest $request): ServerReflectionResponse
62 | {
63 | // Get gpb class pool
64 | $descriptorPool = DescriptorPool::getGeneratedPool();
65 | // New response
66 | $resp = new ServerReflectionResponse();
67 | $resp->setOriginalRequest($request);
68 | switch ($request->getMessageRequest()) {
69 | case "list_services":
70 | $servers = [];
71 | foreach (array_keys($this->servers) as $server) {
72 | $servers[] = (new ServiceResponse())->setName($server);
73 | }
74 | $resp->setListServicesResponse(
75 | (new ListServiceResponse())->setService($servers)
76 | );
77 | break;
78 | case "file_containing_symbol":
79 | $symbol = $request->getFileContainingSymbol();
80 | // set file
81 | $resp->setFileDescriptorResponse(
82 | (new FileDescriptorResponse())->setFileDescriptorProto(array_merge([
83 | $this->servers[$symbol]],
84 | $this->otherProto($descriptorPool)
85 | ))
86 | );
87 | break;
88 | case "file_by_filename":
89 | $fileName = $request->getFileByFilename();
90 | $file = $descriptorPool->getContentByProtoName($fileName);
91 | if (empty($file)) throw new GrpcException("{$fileName} not found", StatusCode::ABORTED);
92 | $resp->setFileDescriptorResponse(
93 | (new FileDescriptorResponse())->setFileDescriptorProto([$file])
94 | );
95 | break;
96 | }
97 | return $resp;
98 | }
99 |
100 | /**
101 | * Get google proto
102 | * @param DescriptorPool $descriptorPool
103 | * @return array
104 | */
105 | private function otherProto(DescriptorPool $descriptorPool): array
106 | {
107 | $tmp = [];
108 | foreach ($this->baseProtoFiles as $proto) {
109 | if ('' !== $file = $descriptorPool->getContentByProtoName($proto)) $tmp[] = $file;
110 | }
111 | return $tmp;
112 | }
113 |
114 | /**
115 | * Get server by router
116 | * @return array
117 | */
118 | private function servers(): array
119 | {
120 | // get gpb class pool
121 | $descriptorPool = DescriptorPool::getGeneratedPool();
122 |
123 | $routes = $this->dispatcherFactory
124 | ->getRouter($this->config->get("grpc.register.server_name", "grpc"))
125 | ->getData();
126 | $services = [];
127 | /**
128 | * @var Handler $handler
129 | */
130 | if (!empty($routes) && isset($routes[0]['POST'])) foreach ($routes[0]['POST'] as $handler) {
131 | $service = current(explode("/", trim($handler->route, "/")));
132 | $file = $descriptorPool->getContentByServerName($service);
133 | if (!isset($services[$service]) && '' != $file) {
134 | $services[$service] = $file;
135 | }
136 | }
137 | return $services;
138 | }
139 | }
--------------------------------------------------------------------------------
/src/Server/Response/GrpcStream.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Response;
9 |
10 | use Amp\Http\HPack;
11 | use Amp\Http\HPackException;
12 | use Crayoon\HyperfGrpc\Exception\GrpcStreamException;
13 | use Crayoon\HyperfGrpc\Server\Http2Frame\FrameParser;
14 | use Crayoon\HyperfGrpc\Server\Http2Frame\Http2Frame;
15 | use Hyperf\Context\ApplicationContext;
16 | use Hyperf\Context\Context;
17 | use Hyperf\Contract\ContainerInterface;
18 | use Hyperf\Grpc\Parser;
19 | use Hyperf\Grpc\StatusCode;
20 | use Swoole\Http\Request;
21 | use Swoole\Http\Response;
22 | use Swoole\Http\Server;
23 | use Psr\Http\Message\ResponseInterface;
24 | use Psr\Http\Message\ServerRequestInterface;
25 | use Hyperf\Engine\Http\WritableConnection;
26 |
27 | class GrpcStream
28 | {
29 | /**
30 | * @var Server
31 | */
32 | protected Server $server;
33 | /**
34 | * @var Request
35 | */
36 | protected Request $request;
37 |
38 | /**
39 | * @var Response
40 | */
41 | protected Response $response;
42 |
43 | /**
44 | * @var FrameParser
45 | */
46 | protected FrameParser $frameParser;
47 |
48 | protected bool $withHeader = false;
49 |
50 |
51 | /**
52 | * @throws GrpcStreamException
53 | */
54 | public function __construct(?Request $request = null, ?Response $response = null)
55 | {
56 | /**
57 | * @var ContainerInterface $container
58 | */
59 | $container = ApplicationContext::getContainer();
60 | try {
61 | // Get Server
62 | $this->server = $container->get(\Swoole\Server::class);
63 |
64 | // Get swoole request and response
65 | $this->request = Context::get(ServerRequestInterface::class)->getSwooleRequest();
66 | /**
67 | * @var WritableConnection $connect
68 | */
69 | $connect = Context::get(ResponseInterface::class)->getConnection();
70 | if (!$connect) {
71 | throw new \Exception('undefined response');
72 | }
73 | $this->response = $connect->getSocket();
74 |
75 | // Get Parser
76 | $this->frameParser = $container->get(FrameParser::class);
77 |
78 | } catch (\Throwable $e) {
79 | throw new GrpcStreamException($e->getMessage());
80 | }
81 | }
82 |
83 | /**
84 | * @param null|Http2Frame|Http2Frame[] $frames
85 | * @return bool
86 | */
87 | public function emit(null|Http2Frame|array $frames): bool
88 | {
89 | if (!$frames) return false;
90 | $mixedStream = implode('', array_map(function ($item) {
91 | return $this->frameParser->pack($item);
92 | }, is_array($frames) ? $frames : [$frames]));
93 |
94 | return $this->server->send($this->response->fd, $mixedStream);
95 | }
96 |
97 | /**
98 | * @param mixed $data
99 | * @return bool
100 | */
101 | public function write(mixed $data): bool
102 | {
103 | if (!$this->response->isWritable()) {
104 | return false;
105 | }
106 |
107 | $streams = [];
108 | // add header
109 | if (!$this->withHeader) {
110 | $streams[] = $this->buildHeader();
111 | $this->withHeader = true;
112 | }
113 | // add message
114 | $streams[] = new Http2Frame(
115 | $data ? Parser::serializeMessage($data) : '',
116 | Http2Frame::HTTP2_FRAME_TYPE_DATA,
117 | Http2Frame::HTTP2_FLAG_NONE,
118 | $this->request->streamId
119 | );
120 |
121 | return $this->emit($streams);
122 | }
123 |
124 | /**
125 | * @param int $status
126 | * @param string $message
127 | * @return bool
128 | */
129 | public function close(int $status = StatusCode::OK, string $message = ''): bool
130 | {
131 | if (!$this->response->isWritable()) {
132 | return true;
133 | }
134 |
135 | $headerStream = $this->buildHeader(true, [
136 | [':status', '200'],
137 | ['content-type', 'application/grpc+proto'],
138 | ['trailer', 'grpc-status, grpc-message'],
139 | ['grpc-status', (string)$status],
140 | ['grpc-message', $message],
141 | ]);
142 |
143 | $res = $this->emit($headerStream);
144 | if ($res) $this->response->detach();
145 |
146 | return $res;
147 | }
148 |
149 |
150 | /**
151 | * @param bool $end
152 | * @param array $headers
153 | * @return Http2Frame
154 | */
155 | private function buildHeader(bool $end = false, array $headers = [
156 | [':status', '200'],
157 | ['content-type', 'application/grpc+proto']
158 | ]): Http2Frame
159 | {
160 | $frame = $this->frameParser->encodeHeaderFrame($headers, $this->request->streamId);
161 | if ($frame && $end) {
162 | $frame->flags = Http2Frame::HTTP2_FLAG_END_STREAM | Http2Frame::HTTP2_FLAG_END_HEADERS;
163 | }
164 | return $frame;
165 | }
166 |
167 | public function isWritable()
168 | {
169 | return $this->response->isWritable();
170 | }
171 | }
--------------------------------------------------------------------------------
/src/Server/Handler/StreamHandler.php:
--------------------------------------------------------------------------------
1 |
5 | * @contact crayxn@qq.com
6 | */
7 |
8 | namespace Crayoon\HyperfGrpc\Server\Handler;
9 |
10 | use Crayoon\HyperfGrpc\Server\Channel\ChannelManager;
11 | use Crayoon\HyperfGrpc\Server\Http2Frame\FrameParser;
12 | use Crayoon\HyperfGrpc\Server\Http2Frame\Http2Frame;
13 | use Crayoon\HyperfGrpc\Server\Http2Stream\StreamManager;
14 | use Exception;
15 | use Google\Protobuf\Internal\Message;
16 | use Hyperf\Context\ApplicationContext;
17 | use Hyperf\Grpc\Parser;
18 | use Hyperf\Grpc\StatusCode;
19 | use Hyperf\HttpMessage\Server\Request;
20 | use Hyperf\HttpMessage\Uri\Uri;
21 | use Psr\Container\ContainerExceptionInterface;
22 | use Psr\Container\ContainerInterface;
23 | use Psr\Container\NotFoundExceptionInterface;
24 | use Psr\Http\Message\ServerRequestInterface;
25 | use Swoole\Coroutine\Channel;
26 | use Swoole\Server as SwooleServer;
27 |
28 | class StreamHandler
29 | {
30 | /**
31 | * @var SwooleServer
32 | */
33 | private SwooleServer $swooleServer;
34 |
35 | /**
36 | * @var ContainerInterface
37 | */
38 | private ContainerInterface $container;
39 |
40 | /**
41 | * @var ServerRequestInterface
42 | */
43 | private ServerRequestInterface $request;
44 |
45 | /**
46 | * @var StreamManager
47 | */
48 | private StreamManager $streamManager;
49 |
50 | /**
51 | * @var int
52 | */
53 | private int $fd;
54 |
55 | /**
56 | * @var int
57 | */
58 | private int $streamId;
59 |
60 | /**
61 | * @var FrameParser
62 | */
63 | private FrameParser $frameParser;
64 |
65 | /**
66 | * is polluted
67 | * @var bool
68 | */
69 | private bool $polluted = false;
70 |
71 |
72 | /**
73 | * @param SwooleServer $server
74 | * @param int $fd
75 | * @param Http2Frame $swooleHeaderFrame
76 | * @param mixed|null $body
77 | * @throws ContainerExceptionInterface
78 | * @throws NotFoundExceptionInterface
79 | * @throws Exception
80 | */
81 | public function __construct(SwooleServer $server, int $fd, Http2Frame $swooleHeaderFrame, mixed $body = null)
82 | {
83 | if ($swooleHeaderFrame->type != Http2Frame::HTTP2_FRAME_TYPE_HEAD && $swooleHeaderFrame->flags != Http2Frame::HTTP2_FLAG_END_HEADERS) {
84 | throw new Exception("Grpc request header frame is invalid!");
85 | }
86 | $this->swooleServer = $server;
87 | $this->fd = $fd;
88 | $this->streamId = $swooleHeaderFrame->streamId;
89 | $this->container = ApplicationContext::getContainer();
90 | $this->streamManager = $this->container->get(StreamManager::class);
91 | $this->frameParser = $this->container->get(FrameParser::class);
92 | $stream = $this->streamManager->get($this->streamId);
93 | // push data
94 | if ($body) $stream->receiveChannel->push($body);
95 | // emit setting
96 | $this->swooleServer->send($fd, $this->frameParser->pack(
97 | new Http2Frame(hex2bin(Http2Frame::SETTING_HEX), Http2Frame::HTTP2_FRAME_TYPE_SETTING, Http2Frame::HTTP2_FLAG_NONE, 0)
98 | ));
99 | // headers
100 | $swooleHeaders = [];
101 | foreach ($this->frameParser->decodeHeaderFrame($swooleHeaderFrame) ?? [] as [$key, $value]) {
102 | $swooleHeaders[$key] = $value;
103 | }
104 | //create request
105 | $uri = new Uri(sprintf("%s://%s:%d%s", $swooleHeaders[':scheme'] ?? 'http', $server->host, $server->port, $swooleHeaders[':path'] ?? '/'));
106 | $this->request = new Request($swooleHeaders[':method'], $uri, $swooleHeaders, $body, '2');
107 | }
108 |
109 | public function getRequest(): ServerRequestInterface
110 | {
111 | return $this->request;
112 | }
113 |
114 | public function receive(string|array $deserialize = null): mixed
115 | {
116 | $data = $this->streamManager->get($this->streamId)->receiveChannel->pop();
117 | if ($deserialize && $data != Http2Frame::EOF) {
118 | $data = Parser::deserializeMessage(is_array($deserialize) ? $deserialize : [$deserialize, 'mergeFromString'], $data);
119 | }
120 | return $data;
121 | }
122 |
123 |
124 | private function write(Http2Frame $frame): bool
125 | {
126 | // check stream status
127 | if(!$this->streamManager->checkActive($this->streamId)){
128 | return false;
129 | }
130 | $frames = [];
131 | if (!$this->polluted && $frame->type == Http2Frame::HTTP2_FRAME_TYPE_DATA) {
132 | //with header
133 | $frames[] = $this->frameParser->pack(
134 | $this->frameParser->encodeHeaderFrame([
135 | [':status', '200'],
136 | ['content-type', 'application/grpc'],
137 | ['trailer', 'grpc-status, grpc-message']
138 | ], $this->streamId)
139 | );
140 | $this->polluted = true;
141 | }
142 |
143 | $frames[] = $this->frameParser->pack($frame);
144 | // write
145 | return $this->swooleServer->send($this->fd, implode('', $frames));
146 | }
147 |
148 | public function push(Message $message): bool
149 | {
150 | return $this->write(new Http2Frame(Parser::serializeMessage($message), Http2Frame::HTTP2_FRAME_TYPE_DATA, Http2Frame::HTTP2_FLAG_NONE, $this->streamId));
151 | }
152 |
153 | public function end(int $status = StatusCode::OK, string $message = 'ok'): bool
154 | {
155 | //send status
156 | $end = $this->frameParser->encodeHeaderFrame([
157 | ['grpc-status', (string)$status],
158 | ['grpc-message', $message],
159 | ], $this->streamId);
160 | $end->flags = Http2Frame::HTTP2_FLAG_END_HEADERS | Http2Frame::HTTP2_FLAG_END_STREAM;
161 | return $this->write($end);
162 | }
163 | }
--------------------------------------------------------------------------------
/src/Reflection/proto/reflection.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2016 gRPC authors.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Service exported by server reflection
16 |
17 | syntax = "proto3";
18 |
19 | package grpc.reflection.v1alpha;
20 |
21 | option php_generic_services = true;
22 | option php_namespace = "Crayoon\\HyperfGrpc\\Reflection";
23 | option php_metadata_namespace = "Crayoon\\HyperfGrpc\\Reflection\\GPBMetadata";
24 |
25 | service ServerReflection {
26 | // The reflection service is structured as a bidirectional stream, ensuring
27 | // all related requests go to a single server.
28 | rpc ServerReflectionInfo(stream ServerReflectionRequest)
29 | returns (stream ServerReflectionResponse);
30 | }
31 |
32 | // The message sent by the client when calling ServerReflectionInfo method.
33 | message ServerReflectionRequest {
34 | string host = 1;
35 | // To use reflection service, the client should set one of the following
36 | // fields in message_request. The server distinguishes requests by their
37 | // defined field and then handles them using corresponding methods.
38 | oneof message_request {
39 | // Find a proto file by the file name.
40 | string file_by_filename = 3;
41 |
42 | // Find the proto file that declares the given fully-qualified symbol name.
43 | // This field should be a fully-qualified symbol name
44 | // (e.g. .[.] or .).
45 | string file_containing_symbol = 4;
46 |
47 | // Find the proto file which defines an extension extending the given
48 | // message type with the given field number.
49 | ExtensionRequest file_containing_extension = 5;
50 |
51 | // Finds the tag numbers used by all known extensions of the given message
52 | // type, and appends them to ExtensionNumberResponse in an undefined order.
53 | // Its corresponding method is best-effort: it's not guaranteed that the
54 | // reflection service will implement this method, and it's not guaranteed
55 | // that this method will provide all extensions. Returns
56 | // StatusCode::UNIMPLEMENTED if it's not implemented.
57 | // This field should be a fully-qualified type name. The format is
58 | // .
59 | string all_extension_numbers_of_type = 6;
60 |
61 | // List the full names of registered services. The content will not be
62 | // checked.
63 | string list_services = 7;
64 | }
65 | }
66 |
67 | // The type name and extension number sent by the client when requesting
68 | // file_containing_extension.
69 | message ExtensionRequest {
70 | // Fully-qualified type name. The format should be .
71 | string containing_type = 1;
72 | int32 extension_number = 2;
73 | }
74 |
75 | // The message sent by the server to answer ServerReflectionInfo method.
76 | message ServerReflectionResponse {
77 | string valid_host = 1;
78 | ServerReflectionRequest original_request = 2;
79 | // The server set one of the following fields accroding to the message_request
80 | // in the request.
81 | oneof message_response {
82 | // This message is used to answer file_by_filename, file_containing_symbol,
83 | // file_containing_extension requests with transitive dependencies. As
84 | // the repeated label is not allowed in oneof fields, we use a
85 | // FileDescriptorResponse message to encapsulate the repeated fields.
86 | // The reflection service is allowed to avoid sending FileDescriptorProtos
87 | // that were previously sent in response to earlier requests in the stream.
88 | FileDescriptorResponse file_descriptor_response = 4;
89 |
90 | // This message is used to answer all_extension_numbers_of_type requst.
91 | ExtensionNumberResponse all_extension_numbers_response = 5;
92 |
93 | // This message is used to answer list_services request.
94 | ListServiceResponse list_services_response = 6;
95 |
96 | // This message is used when an error occurs.
97 | ErrorResponse error_response = 7;
98 | }
99 | }
100 |
101 | // Serialized FileDescriptorProto messages sent by the server answering
102 | // a file_by_filename, file_containing_symbol, or file_containing_extension
103 | // request.
104 | message FileDescriptorResponse {
105 | // Serialized FileDescriptorProto messages. We avoid taking a dependency on
106 | // descriptor.proto, which uses proto2 only features, by making them opaque
107 | // bytes instead.
108 | repeated bytes file_descriptor_proto = 1;
109 | }
110 |
111 | // A list of extension numbers sent by the server answering
112 | // all_extension_numbers_of_type request.
113 | message ExtensionNumberResponse {
114 | // Full name of the base type, including the package name. The format
115 | // is .
116 | string base_type_name = 1;
117 | repeated int32 extension_number = 2;
118 | }
119 |
120 | // A list of ServiceResponse sent by the server answering list_services request.
121 | message ListServiceResponse {
122 | // The information of each service may be expanded in the future, so we use
123 | // ServiceResponse message to encapsulate it.
124 | repeated ServiceResponse service = 1;
125 | }
126 |
127 | // The information of a single service used by ListServiceResponse to answer
128 | // list_services request.
129 | message ServiceResponse {
130 | // Full name of a registered service, including its package name. The format
131 | // is .
132 | string name = 1;
133 | }
134 |
135 | // The error code and error message sent by the server when an error occurs.
136 | message ErrorResponse {
137 | // This field uses the error codes defined in grpc::StatusCode.
138 | int32 error_code = 1;
139 | string error_message = 2;
140 | }
--------------------------------------------------------------------------------
/src/Listener/RegisterGrpcServiceListener.php:
--------------------------------------------------------------------------------
1 | logger = $container->get(StdoutLoggerInterface::class);
46 | $this->serviceManager = $container->get(ServiceManager::class);
47 | $this->config = $container->get(ConfigInterface::class);
48 | $this->ipReader = $container->get(IPReaderInterface::class);
49 | $this->governanceManager = $container->get(DriverManager::class);
50 | $this->dispatcherFactory = $container->get(DispatcherFactory::class);
51 | }
52 |
53 | public function listen(): array {
54 | return [
55 | MainWorkerStart::class,
56 | ];
57 | }
58 |
59 | public function process(object $event): void {
60 | if ($this->config->get("grpc.register.enable") !== true) {
61 | $this->logger->info("grpc service register closed!");
62 | return;
63 | }
64 | $continue = true;
65 | $protocol = 'grpc';
66 | $serverName = $this->config->get("grpc.register.server_name", "grpc");
67 | $driverName = $this->config->get("grpc.register.driver_name", "nacos");
68 | $services = [];
69 | $routes = $this->dispatcherFactory
70 | ->getRouter($serverName)
71 | ->getData();
72 | /**
73 | * @var Handler $handler
74 | */
75 | if (!empty($routes) && isset($routes[0]['POST'])) foreach ($routes[0]['POST'] as $handler) {
76 | if (isset($handler->options['register']) && !$handler->options['register']) {
77 | continue;
78 | }
79 | $service = trim(current(explode(".", $handler->route)), "/");
80 | if (!in_array($service, $services)) {
81 | $services[] = $service;
82 | }
83 | }
84 | if (empty($services)) {
85 | $this->logger->info("grpc service is empty!");
86 | return;
87 | }
88 | while ($continue) {
89 | try {
90 | foreach ($services as $name) {
91 | [$host, $port] = $this->getServers()[$protocol];
92 | if ($governance = $this->governanceManager->get($driverName)) {
93 | try {
94 | if ($governance->isRegistered($name, $host, (int)$port, ['protocol' => $protocol])) {
95 | continue;
96 | }
97 | } catch (RequestException $requestException) {
98 | // 兼容nacos不同版本调用 错误抛400问题
99 | if ($requestException->getCode() != 400) {
100 | $this->logger->error($requestException->getMessage());
101 | break;
102 | }
103 | }
104 | $governance->register($name, $host, $port, ['protocol' => $protocol]);
105 | }
106 | }
107 | $continue = false;
108 | } catch (ServerException $throwable) {
109 | if (str_contains($throwable->getMessage(), 'Connection failed')) {
110 | $this->logger->warning('Cannot register service, connection of service center failed, re-register after 10 seconds.');
111 | sleep(10);
112 | } else {
113 | $continue = false;
114 | $this->logger->error($throwable->getMessage());
115 | }
116 | } catch (\Throwable $throwable) {
117 | $this->logger->error($throwable->getMessage());
118 | // 打印完日志就 退出吧
119 | $continue = false;
120 | }
121 | }
122 | }
123 |
124 | protected function getServers(): array {
125 | $result = [];
126 | $servers = $this->config->get('server.servers', []);
127 | foreach ($servers as $server) {
128 | if (!isset($server['name'], $server['host'], $server['port'])) {
129 | continue;
130 | }
131 | if (!$server['name']) {
132 | throw new \Exception('Invalid server name');
133 | }
134 | $host = $server['host'];
135 | if (in_array($host, ['0.0.0.0', 'localhost'])) {
136 | $host = $this->ipReader->read();
137 | }
138 | if (!filter_var($host, FILTER_VALIDATE_IP)) {
139 | throw new \Exception(sprintf('Invalid host %s', $host));
140 | }
141 | $port = $server['port'];
142 | if (!is_numeric($port) || ($port < 0 || $port > 65535)) {
143 | throw new \Exception(sprintf('Invalid port %s', $port));
144 | }
145 | $port = (int)$port;
146 | $result[$server['name']] = [$host, $port];
147 | }
148 | return $result;
149 | }
150 | }
--------------------------------------------------------------------------------
/src/Consul/ConsulDriver.php:
--------------------------------------------------------------------------------
1 |
6 | * @contact crayxn@qq.com
7 | */
8 |
9 | namespace Crayoon\HyperfGrpc\Consul;
10 |
11 | use Hyperf\Consul\AgentInterface;
12 | use Hyperf\Consul\Health;
13 | use Hyperf\Consul\HealthInterface;
14 | use Hyperf\Contract\ConfigInterface;
15 | use Hyperf\Contract\StdoutLoggerInterface;
16 | use Hyperf\Guzzle\ClientFactory;
17 | use Hyperf\ServiceGovernance\DriverInterface;
18 | use Hyperf\ServiceGovernance\Exception\ComponentRequiredException;
19 | use Hyperf\ServiceGovernanceConsul\ConsulAgent;
20 | use Psr\Container\ContainerInterface;
21 | use Psr\Log\LoggerInterface;
22 |
23 | class ConsulDriver implements DriverInterface {
24 | protected LoggerInterface $logger;
25 |
26 | protected ConfigInterface $config;
27 |
28 | protected array $registeredServices = [];
29 |
30 | protected ?HealthInterface $health = null;
31 |
32 | public function __construct(protected ContainerInterface $container) {
33 | $this->logger = $container->get(StdoutLoggerInterface::class);
34 | $this->config = $container->get(ConfigInterface::class);
35 | }
36 |
37 | public function getNodes(string $uri, string $name, array $metadata): array {
38 | $health = $this->createConsulHealth($uri);
39 | $services = $health->service($name)->json();
40 | $nodes = [];
41 | foreach ($services as $node) {
42 | $passing = true;
43 | $service = $node['Service'] ?? [];
44 | $checks = $node['Checks'] ?? [];
45 |
46 | if (isset($service['Meta']['Protocol']) && $metadata['protocol'] !== $service['Meta']['Protocol']) {
47 | // The node is invalid, if the protocol is not equal with the client's protocol.
48 | continue;
49 | }
50 |
51 | foreach ($checks as $check) {
52 | $status = $check['Status'] ?? false;
53 | if ($status !== 'passing') {
54 | $passing = false;
55 | }
56 | }
57 |
58 | if ($passing) {
59 | $address = $service['Address'] ?? '';
60 | $port = (int)($service['Port'] ?? 0);
61 | // @TODO Get and set the weight property.
62 | $address && $port && $nodes[] = ['host' => $address, 'port' => $port];
63 | }
64 | }
65 | return $nodes;
66 | }
67 |
68 | public function register(string $name, string $host, int $port, array $metadata): void {
69 | $nextId = empty($metadata['id']) ? $this->generateId($this->getLastServiceId($name)) : $metadata['id'];
70 | $protocol = $metadata['protocol'];
71 | $deregisterCriticalServiceAfter = $this->config->get('services.drivers.consul.check.deregister_critical_service_after') ?? '90m';
72 | $interval = $this->config->get('services.drivers.consul.check.interval') ?? '1s';
73 | $requestBody = [
74 | 'Name' => $name,
75 | 'ID' => $nextId,
76 | 'Address' => $host,
77 | 'Port' => $port,
78 | 'Meta' => [
79 | 'Protocol' => $protocol,
80 | ],
81 | ];
82 | if ($protocol === 'jsonrpc-http') {
83 | $requestBody['Check'] = [
84 | 'DeregisterCriticalServiceAfter' => $deregisterCriticalServiceAfter,
85 | 'HTTP' => "http://{$host}:{$port}/",
86 | 'Interval' => $interval,
87 | ];
88 | }
89 | if (in_array($protocol, ['jsonrpc', 'jsonrpc-tcp-length-check', 'multiplex.default'], true)) {
90 | $requestBody['Check'] = [
91 | 'DeregisterCriticalServiceAfter' => $deregisterCriticalServiceAfter,
92 | 'TCP' => "{$host}:{$port}",
93 | 'Interval' => $interval,
94 | ];
95 | }
96 | if ($protocol === 'grpc') {
97 | $requestBody['Check'] = [
98 | 'DeregisterCriticalServiceAfter' => $deregisterCriticalServiceAfter,
99 | 'GRPC' => "{$host}:{$port}",
100 | 'GRPCUseTLS' => false,
101 | 'Interval' => $interval,
102 | ];
103 | }
104 | $response = $this->client()->registerService($requestBody);
105 | if ($response->getStatusCode() === 200) {
106 | $this->registeredServices[$name][$protocol][$host][$port] = true;
107 | $this->logger->info(sprintf('Service %s:%s register to the consul successfully.', $name, $nextId));
108 | } else {
109 | $this->logger->warning(sprintf('Service %s register to the consul failed.', $name));
110 | }
111 | }
112 |
113 | public function isRegistered(string $name, string $host, int $port, array $metadata): bool {
114 | $protocol = $metadata['protocol'];
115 | if (isset($this->registeredServices[$name][$protocol][$host][$port])) {
116 | return true;
117 | }
118 | $client = $this->client();
119 | $response = $client->services();
120 | if ($response->getStatusCode() !== 200) {
121 | $this->logger->warning(sprintf('Service %s register to the consul failed.', $name));
122 | return false;
123 | }
124 | $services = $response->json();
125 | $glue = ',';
126 | $tag = implode($glue, [$name, $host, $port, $protocol]);
127 | foreach ($services as $service) {
128 | if (!isset($service['Service'], $service['Address'], $service['Port'], $service['Meta']['Protocol'])) {
129 | continue;
130 | }
131 | $currentTag = implode($glue, [
132 | $service['Service'],
133 | $service['Address'],
134 | $service['Port'],
135 | $service['Meta']['Protocol'],
136 | ]);
137 | if ($currentTag === $tag) {
138 | $this->registeredServices[$name][$protocol][$host][$port] = true;
139 | return true;
140 | }
141 | }
142 | return false;
143 | }
144 |
145 | protected function client(): AgentInterface {
146 | return $this->container->get(ConsulAgent::class);
147 | }
148 |
149 | protected function getLastServiceId(string $name) {
150 | $maxId = -1;
151 | $lastService = $name;
152 | $services = $this->client()->services()->json();
153 | foreach ($services ?? [] as $id => $service) {
154 | if (isset($service['Service']) && $service['Service'] === $name) {
155 | $exploded = explode('-', (string)$id);
156 | $length = count($exploded);
157 | if ($length > 1 && is_numeric($exploded[$length - 1]) && $maxId < $exploded[$length - 1]) {
158 | $maxId = $exploded[$length - 1];
159 | $lastService = $service;
160 | }
161 | }
162 | }
163 | return $lastService['ID'] ?? $name;
164 | }
165 |
166 | protected function generateId(string $name): string {
167 | $exploded = explode('-', $name);
168 | $length = count($exploded);
169 | $end = -1;
170 | if ($length > 1 && is_numeric($exploded[$length - 1])) {
171 | $end = $exploded[$length - 1];
172 | unset($exploded[$length - 1]);
173 | }
174 | $end = intval($end);
175 | ++$end;
176 | $exploded[] = $end;
177 | return implode('-', $exploded);
178 | }
179 |
180 | protected function createConsulHealth(string $baseUri): HealthInterface {
181 | if ($this->health instanceof HealthInterface) {
182 | return $this->health;
183 | }
184 |
185 | if (!class_exists(Health::class)) {
186 | throw new ComponentRequiredException('Component of \'hyperf/consul\' is required if you want the client fetch the nodes info from consul.');
187 | }
188 |
189 | $token = $this->config->get('services.drivers.consul.token', '');
190 | $options = [
191 | 'base_uri' => $baseUri,
192 | ];
193 |
194 | if (!empty($token)) {
195 | $options['headers'] = [
196 | 'X-Consul-Token' => $token,
197 | ];
198 | }
199 |
200 | return $this->health = make(Health::class, [
201 | 'clientFactory' => function () use ($options) {
202 | return $this->container->get(ClientFactory::class)->create($options);
203 | },
204 | ]);
205 | }
206 | }
--------------------------------------------------------------------------------
/class_map/Protobuf/DescriptorPool.php:
--------------------------------------------------------------------------------
1 | mergeFromString($data);
68 |
69 | /**
70 | * @var FileDescriptorProto $file_proto
71 | */
72 | foreach ($files->getFile() as $file_proto) {
73 |
74 | // add content
75 | $this->addContent($file_proto, $data);
76 |
77 | $file = FileDescriptor::buildFromProto($file_proto);
78 | foreach ($file->getMessageType() as $desc) {
79 | $this->addDescriptor($desc);
80 | }
81 | unset($desc);
82 |
83 | foreach ($file->getEnumType() as $desc) {
84 | $this->addEnumDescriptor($desc);
85 | }
86 | unset($desc);
87 |
88 | foreach ($file->getMessageType() as $desc) {
89 | $this->crossLink($desc);
90 | }
91 | unset($desc);
92 | }
93 | }
94 |
95 | public function addMessage($name, $klass): \Google\Protobuf\Internal\MessageBuilderContext
96 | {
97 | return new MessageBuilderContext($name, $klass, $this);
98 | }
99 |
100 | public function addEnum($name, $klass): \Google\Protobuf\Internal\EnumBuilderContext
101 | {
102 | return new EnumBuilderContext($name, $klass, $this);
103 | }
104 |
105 | public function addDescriptor(Descriptor $descriptor): void
106 | {
107 | $this->proto_to_class[$descriptor->getFullName()] =
108 | $descriptor->getClass();
109 | $this->class_to_desc[$descriptor->getClass()] = $descriptor;
110 | $this->class_to_desc[$descriptor->getLegacyClass()] = $descriptor;
111 | $this->class_to_desc[$descriptor->getPreviouslyUnreservedClass()] = $descriptor;
112 | foreach ($descriptor->getNestedType() as $nested_type) {
113 | $this->addDescriptor($nested_type);
114 | }
115 | foreach ($descriptor->getEnumType() as $enum_type) {
116 | $this->addEnumDescriptor($enum_type);
117 | }
118 | }
119 |
120 | public function addEnumDescriptor($descriptor): void
121 | {
122 | $this->proto_to_class[$descriptor->getFullName()] =
123 | $descriptor->getClass();
124 | $this->class_to_enum_desc[$descriptor->getClass()] = $descriptor;
125 | $this->class_to_enum_desc[$descriptor->getLegacyClass()] = $descriptor;
126 | }
127 |
128 | public function getDescriptorByClassName($klass)
129 | {
130 | return $this->class_to_desc[$klass] ?? null;
131 | }
132 |
133 | public function getEnumDescriptorByClassName($klass)
134 | {
135 | return $this->class_to_enum_desc[$klass] ?? null;
136 | }
137 |
138 | public function getDescriptorByProtoName($proto)
139 | {
140 | if (isset($this->proto_to_class[$proto])) {
141 | $klass = $this->proto_to_class[$proto];
142 | return $this->class_to_desc[$klass];
143 | } else {
144 | return null;
145 | }
146 | }
147 |
148 | public function getEnumDescriptorByProtoName($proto)
149 | {
150 | $klass = $this->proto_to_class[$proto];
151 | return $this->class_to_enum_desc[$klass];
152 | }
153 |
154 | private function crossLink(Descriptor $desc): void
155 | {
156 | /**
157 | * @var FieldDescriptor $field
158 | */
159 | foreach ($desc->getField() as $field) {
160 | switch ($field->getType()) {
161 | case GPBType::MESSAGE:
162 | $proto = $field->getMessageType();
163 | if ($proto[0] == '.') {
164 | $proto = substr($proto, 1);
165 | }
166 | $subdesc = $this->getDescriptorByProtoName($proto);
167 | if (is_null($subdesc)) {
168 | trigger_error(
169 | 'proto not added: ' . $proto
170 | . " for " . $desc->getFullName(), E_USER_ERROR);
171 | }
172 | $field->setMessageType($subdesc);
173 | break;
174 | case GPBType::ENUM:
175 | $proto = $field->getEnumType();
176 | if ($proto[0] == '.') {
177 | $proto = substr($proto, 1);
178 | }
179 | $field->setEnumType(
180 | $this->getEnumDescriptorByProtoName($proto));
181 | break;
182 | default:
183 | break;
184 | }
185 | }
186 | unset($field);
187 |
188 | foreach ($desc->getNestedType() as $nested_type) {
189 | $this->crossLink($nested_type);
190 | }
191 | unset($nested_type);
192 | }
193 |
194 | /**
195 | * @return void
196 | */
197 | public function finish(): void
198 | {
199 | foreach ($this->class_to_desc as $klass => $desc) {
200 | $this->crossLink($desc);
201 | }
202 | unset($desc);
203 | }
204 |
205 | /**
206 | * Get Content By Proto
207 | * @param string $proto
208 | * @return string
209 | */
210 | public function getContentByProtoName(string $proto): string
211 | {
212 | return $this->proto_to_content[$proto] ?? "";
213 | }
214 |
215 | /**
216 | * Get Content By Srv Name
217 | * @param string $server
218 | * @return string
219 | */
220 | public function getContentByServerName(string $server): string
221 | {
222 | return $this->proto_to_content[$this->class_to_proto[$server] ?? ""] ?? "";
223 | }
224 |
225 | /**
226 | * Add Proto Content
227 | * @param FileDescriptorProto $file_proto
228 | * @param string $content
229 | * @return void
230 | */
231 | public function addContent(FileDescriptorProto $file_proto, string $content): void
232 | {
233 | // Replace
234 | $content = str_replace('\\\\', "\\", $content);
235 | $content = str_replace(substr($content, 1, 3), "", $content);
236 | // Save
237 | if (!isset($this->proto_to_content[$file_proto->getName()])) {
238 | $this->proto_to_content[$file_proto->getName()] = $content;
239 | }
240 | /**
241 | * @var ServiceDescriptorProto $item
242 | */
243 | foreach ($file_proto->getService() as $item) {
244 | $this->class_to_proto["{$file_proto->getPackage()}.{$item->getName()}"] = $file_proto->getName();
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/Reflection/ServerReflectionRequest.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ServerReflectionRequest
15 | */
16 | class ServerReflectionRequest extends \Google\Protobuf\Internal\Message
17 | {
18 | /**
19 | * Generated from protobuf field string host = 1;
20 | */
21 | protected $host = '';
22 | protected $message_request;
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @param array $data {
28 | * Optional. Data for populating the Message object.
29 | *
30 | * @type string $host
31 | * @type string $file_by_filename
32 | * Find a proto file by the file name.
33 | * @type string $file_containing_symbol
34 | * Find the proto file that declares the given fully-qualified symbol name.
35 | * This field should be a fully-qualified symbol name
36 | * (e.g. .[.] or .).
37 | * @type \Crayoon\HyperfGrpc\Reflection\ExtensionRequest $file_containing_extension
38 | * Find the proto file which defines an extension extending the given
39 | * message type with the given field number.
40 | * @type string $all_extension_numbers_of_type
41 | * Finds the tag numbers used by all known extensions of the given message
42 | * type, and appends them to ExtensionNumberResponse in an undefined order.
43 | * Its corresponding method is best-effort: it's not guaranteed that the
44 | * reflection service will implement this method, and it's not guaranteed
45 | * that this method will provide all extensions. Returns
46 | * StatusCode::UNIMPLEMENTED if it's not implemented.
47 | * This field should be a fully-qualified type name. The format is
48 | * .
49 | * @type string $list_services
50 | * List the full names of registered services. The content will not be
51 | * checked.
52 | * }
53 | */
54 | public function __construct($data = NULL) {
55 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
56 | parent::__construct($data);
57 | }
58 |
59 | /**
60 | * Generated from protobuf field string host = 1;
61 | * @return string
62 | */
63 | public function getHost()
64 | {
65 | return $this->host;
66 | }
67 |
68 | /**
69 | * Generated from protobuf field string host = 1;
70 | * @param string $var
71 | * @return $this
72 | */
73 | public function setHost($var)
74 | {
75 | GPBUtil::checkString($var, True);
76 | $this->host = $var;
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * Find a proto file by the file name.
83 | *
84 | * Generated from protobuf field string file_by_filename = 3;
85 | * @return string
86 | */
87 | public function getFileByFilename()
88 | {
89 | return $this->readOneof(3);
90 | }
91 |
92 | public function hasFileByFilename()
93 | {
94 | return $this->hasOneof(3);
95 | }
96 |
97 | /**
98 | * Find a proto file by the file name.
99 | *
100 | * Generated from protobuf field string file_by_filename = 3;
101 | * @param string $var
102 | * @return $this
103 | */
104 | public function setFileByFilename($var)
105 | {
106 | GPBUtil::checkString($var, True);
107 | $this->writeOneof(3, $var);
108 |
109 | return $this;
110 | }
111 |
112 | /**
113 | * Find the proto file that declares the given fully-qualified symbol name.
114 | * This field should be a fully-qualified symbol name
115 | * (e.g. .[.] or .).
116 | *
117 | * Generated from protobuf field string file_containing_symbol = 4;
118 | * @return string
119 | */
120 | public function getFileContainingSymbol()
121 | {
122 | return $this->readOneof(4);
123 | }
124 |
125 | public function hasFileContainingSymbol()
126 | {
127 | return $this->hasOneof(4);
128 | }
129 |
130 | /**
131 | * Find the proto file that declares the given fully-qualified symbol name.
132 | * This field should be a fully-qualified symbol name
133 | * (e.g. .[.] or .).
134 | *
135 | * Generated from protobuf field string file_containing_symbol = 4;
136 | * @param string $var
137 | * @return $this
138 | */
139 | public function setFileContainingSymbol($var)
140 | {
141 | GPBUtil::checkString($var, True);
142 | $this->writeOneof(4, $var);
143 |
144 | return $this;
145 | }
146 |
147 | /**
148 | * Find the proto file which defines an extension extending the given
149 | * message type with the given field number.
150 | *
151 | * Generated from protobuf field .grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;
152 | * @return \Crayoon\HyperfGrpc\Reflection\ExtensionRequest|null
153 | */
154 | public function getFileContainingExtension()
155 | {
156 | return $this->readOneof(5);
157 | }
158 |
159 | public function hasFileContainingExtension()
160 | {
161 | return $this->hasOneof(5);
162 | }
163 |
164 | /**
165 | * Find the proto file which defines an extension extending the given
166 | * message type with the given field number.
167 | *
168 | * Generated from protobuf field .grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;
169 | * @param \Crayoon\HyperfGrpc\Reflection\ExtensionRequest $var
170 | * @return $this
171 | */
172 | public function setFileContainingExtension($var)
173 | {
174 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\ExtensionRequest::class);
175 | $this->writeOneof(5, $var);
176 |
177 | return $this;
178 | }
179 |
180 | /**
181 | * Finds the tag numbers used by all known extensions of the given message
182 | * type, and appends them to ExtensionNumberResponse in an undefined order.
183 | * Its corresponding method is best-effort: it's not guaranteed that the
184 | * reflection service will implement this method, and it's not guaranteed
185 | * that this method will provide all extensions. Returns
186 | * StatusCode::UNIMPLEMENTED if it's not implemented.
187 | * This field should be a fully-qualified type name. The format is
188 | * .
189 | *
190 | * Generated from protobuf field string all_extension_numbers_of_type = 6;
191 | * @return string
192 | */
193 | public function getAllExtensionNumbersOfType()
194 | {
195 | return $this->readOneof(6);
196 | }
197 |
198 | public function hasAllExtensionNumbersOfType()
199 | {
200 | return $this->hasOneof(6);
201 | }
202 |
203 | /**
204 | * Finds the tag numbers used by all known extensions of the given message
205 | * type, and appends them to ExtensionNumberResponse in an undefined order.
206 | * Its corresponding method is best-effort: it's not guaranteed that the
207 | * reflection service will implement this method, and it's not guaranteed
208 | * that this method will provide all extensions. Returns
209 | * StatusCode::UNIMPLEMENTED if it's not implemented.
210 | * This field should be a fully-qualified type name. The format is
211 | * .
212 | *
213 | * Generated from protobuf field string all_extension_numbers_of_type = 6;
214 | * @param string $var
215 | * @return $this
216 | */
217 | public function setAllExtensionNumbersOfType($var)
218 | {
219 | GPBUtil::checkString($var, True);
220 | $this->writeOneof(6, $var);
221 |
222 | return $this;
223 | }
224 |
225 | /**
226 | * List the full names of registered services. The content will not be
227 | * checked.
228 | *
229 | * Generated from protobuf field string list_services = 7;
230 | * @return string
231 | */
232 | public function getListServices()
233 | {
234 | return $this->readOneof(7);
235 | }
236 |
237 | public function hasListServices()
238 | {
239 | return $this->hasOneof(7);
240 | }
241 |
242 | /**
243 | * List the full names of registered services. The content will not be
244 | * checked.
245 | *
246 | * Generated from protobuf field string list_services = 7;
247 | * @param string $var
248 | * @return $this
249 | */
250 | public function setListServices($var)
251 | {
252 | GPBUtil::checkString($var, True);
253 | $this->writeOneof(7, $var);
254 |
255 | return $this;
256 | }
257 |
258 | /**
259 | * @return string
260 | */
261 | public function getMessageRequest()
262 | {
263 | return $this->whichOneof("message_request");
264 | }
265 |
266 | }
267 |
268 |
--------------------------------------------------------------------------------
/src/Reflection/ServerReflectionResponse.php:
--------------------------------------------------------------------------------
1 | grpc.reflection.v1alpha.ServerReflectionResponse
15 | */
16 | class ServerReflectionResponse extends \Google\Protobuf\Internal\Message
17 | {
18 | /**
19 | * Generated from protobuf field string valid_host = 1;
20 | */
21 | protected $valid_host = '';
22 | /**
23 | * Generated from protobuf field .grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;
24 | */
25 | protected $original_request = null;
26 | protected $message_response;
27 |
28 | /**
29 | * Constructor.
30 | *
31 | * @param array $data {
32 | * Optional. Data for populating the Message object.
33 | *
34 | * @type string $valid_host
35 | * @type \Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest $original_request
36 | * @type \Crayoon\HyperfGrpc\Reflection\FileDescriptorResponse $file_descriptor_response
37 | * This message is used to answer file_by_filename, file_containing_symbol,
38 | * file_containing_extension requests with transitive dependencies. As
39 | * the repeated label is not allowed in oneof fields, we use a
40 | * FileDescriptorResponse message to encapsulate the repeated fields.
41 | * The reflection service is allowed to avoid sending FileDescriptorProtos
42 | * that were previously sent in response to earlier requests in the stream.
43 | * @type \Crayoon\HyperfGrpc\Reflection\ExtensionNumberResponse $all_extension_numbers_response
44 | * This message is used to answer all_extension_numbers_of_type requst.
45 | * @type \Crayoon\HyperfGrpc\Reflection\ListServiceResponse $list_services_response
46 | * This message is used to answer list_services request.
47 | * @type \Crayoon\HyperfGrpc\Reflection\ErrorResponse $error_response
48 | * This message is used when an error occurs.
49 | * }
50 | */
51 | public function __construct($data = NULL) {
52 | \Crayoon\HyperfGrpc\Reflection\GPBMetadata\Reflection::initOnce();
53 | parent::__construct($data);
54 | }
55 |
56 | /**
57 | * Generated from protobuf field string valid_host = 1;
58 | * @return string
59 | */
60 | public function getValidHost()
61 | {
62 | return $this->valid_host;
63 | }
64 |
65 | /**
66 | * Generated from protobuf field string valid_host = 1;
67 | * @param string $var
68 | * @return $this
69 | */
70 | public function setValidHost($var)
71 | {
72 | GPBUtil::checkString($var, True);
73 | $this->valid_host = $var;
74 |
75 | return $this;
76 | }
77 |
78 | /**
79 | * Generated from protobuf field .grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;
80 | * @return \Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest|null
81 | */
82 | public function getOriginalRequest()
83 | {
84 | return $this->original_request;
85 | }
86 |
87 | public function hasOriginalRequest()
88 | {
89 | return isset($this->original_request);
90 | }
91 |
92 | public function clearOriginalRequest()
93 | {
94 | unset($this->original_request);
95 | }
96 |
97 | /**
98 | * Generated from protobuf field .grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;
99 | * @param \Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest $var
100 | * @return $this
101 | */
102 | public function setOriginalRequest($var)
103 | {
104 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\ServerReflectionRequest::class);
105 | $this->original_request = $var;
106 |
107 | return $this;
108 | }
109 |
110 | /**
111 | * This message is used to answer file_by_filename, file_containing_symbol,
112 | * file_containing_extension requests with transitive dependencies. As
113 | * the repeated label is not allowed in oneof fields, we use a
114 | * FileDescriptorResponse message to encapsulate the repeated fields.
115 | * The reflection service is allowed to avoid sending FileDescriptorProtos
116 | * that were previously sent in response to earlier requests in the stream.
117 | *
118 | * Generated from protobuf field .grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;
119 | * @return \Crayoon\HyperfGrpc\Reflection\FileDescriptorResponse|null
120 | */
121 | public function getFileDescriptorResponse()
122 | {
123 | return $this->readOneof(4);
124 | }
125 |
126 | public function hasFileDescriptorResponse()
127 | {
128 | return $this->hasOneof(4);
129 | }
130 |
131 | /**
132 | * This message is used to answer file_by_filename, file_containing_symbol,
133 | * file_containing_extension requests with transitive dependencies. As
134 | * the repeated label is not allowed in oneof fields, we use a
135 | * FileDescriptorResponse message to encapsulate the repeated fields.
136 | * The reflection service is allowed to avoid sending FileDescriptorProtos
137 | * that were previously sent in response to earlier requests in the stream.
138 | *
139 | * Generated from protobuf field .grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;
140 | * @param \Crayoon\HyperfGrpc\Reflection\FileDescriptorResponse $var
141 | * @return $this
142 | */
143 | public function setFileDescriptorResponse($var)
144 | {
145 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\FileDescriptorResponse::class);
146 | $this->writeOneof(4, $var);
147 |
148 | return $this;
149 | }
150 |
151 | /**
152 | * This message is used to answer all_extension_numbers_of_type requst.
153 | *
154 | * Generated from protobuf field .grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;
155 | * @return \Crayoon\HyperfGrpc\Reflection\ExtensionNumberResponse|null
156 | */
157 | public function getAllExtensionNumbersResponse()
158 | {
159 | return $this->readOneof(5);
160 | }
161 |
162 | public function hasAllExtensionNumbersResponse()
163 | {
164 | return $this->hasOneof(5);
165 | }
166 |
167 | /**
168 | * This message is used to answer all_extension_numbers_of_type requst.
169 | *
170 | * Generated from protobuf field .grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;
171 | * @param \Crayoon\HyperfGrpc\Reflection\ExtensionNumberResponse $var
172 | * @return $this
173 | */
174 | public function setAllExtensionNumbersResponse($var)
175 | {
176 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\ExtensionNumberResponse::class);
177 | $this->writeOneof(5, $var);
178 |
179 | return $this;
180 | }
181 |
182 | /**
183 | * This message is used to answer list_services request.
184 | *
185 | * Generated from protobuf field .grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;
186 | * @return \Crayoon\HyperfGrpc\Reflection\ListServiceResponse|null
187 | */
188 | public function getListServicesResponse()
189 | {
190 | return $this->readOneof(6);
191 | }
192 |
193 | public function hasListServicesResponse()
194 | {
195 | return $this->hasOneof(6);
196 | }
197 |
198 | /**
199 | * This message is used to answer list_services request.
200 | *
201 | * Generated from protobuf field .grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;
202 | * @param \Crayoon\HyperfGrpc\Reflection\ListServiceResponse $var
203 | * @return $this
204 | */
205 | public function setListServicesResponse($var)
206 | {
207 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\ListServiceResponse::class);
208 | $this->writeOneof(6, $var);
209 |
210 | return $this;
211 | }
212 |
213 | /**
214 | * This message is used when an error occurs.
215 | *
216 | * Generated from protobuf field .grpc.reflection.v1alpha.ErrorResponse error_response = 7;
217 | * @return \Crayoon\HyperfGrpc\Reflection\ErrorResponse|null
218 | */
219 | public function getErrorResponse()
220 | {
221 | return $this->readOneof(7);
222 | }
223 |
224 | public function hasErrorResponse()
225 | {
226 | return $this->hasOneof(7);
227 | }
228 |
229 | /**
230 | * This message is used when an error occurs.
231 | *
232 | * Generated from protobuf field .grpc.reflection.v1alpha.ErrorResponse error_response = 7;
233 | * @param \Crayoon\HyperfGrpc\Reflection\ErrorResponse $var
234 | * @return $this
235 | */
236 | public function setErrorResponse($var)
237 | {
238 | GPBUtil::checkMessage($var, \Crayoon\HyperfGrpc\Reflection\ErrorResponse::class);
239 | $this->writeOneof(7, $var);
240 |
241 | return $this;
242 | }
243 |
244 | /**
245 | * @return string
246 | */
247 | public function getMessageResponse()
248 | {
249 | return $this->whichOneof("message_response");
250 | }
251 |
252 | }
253 |
254 |
--------------------------------------------------------------------------------