├── .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 | --------------------------------------------------------------------------------