Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,63 @@ return [
'grpc' => [
'services' => [
\App\GRPC\EchoServiceInterface::class => \App\GRPC\EchoService::class,
]
],
];
```

#### gRPC Server Interceptors

Create your interceptor by implementing `Spiral\RoadRunnerLaravel\Grpc\GrpcServerInterceptorInterface`:

```php
<?php

namespace App\GRPC\Interceptors;

use Spiral\RoadRunnerLaravel\Grpc\GrpcServerInterceptorInterface;
use Spiral\RoadRunner\GRPC\ContextInterface;

class LoggingInterceptor implements GrpcServerInterceptorInterface
{
public function intercept(string $method, ContextInterface $context, string $body, callable $next)
{
\Log::info("gRPC call: {$method}");

$response = $next($method, $context, $body);

\Log::info("gRPC response: {$method}");

return $response;
}
}
```

##### Interceptors Configuration

Configure interceptors in `config/roadrunner.php`. You can use global interceptors that apply to all services, service-specific interceptors, or both:

```php
return [
// ... other configuration
'grpc' => [
'services' => [
// Simple service configuration
\App\GRPC\EchoServiceInterface::class => \App\GRPC\EchoService::class,

// Service with specific interceptors
\App\GRPC\UserServiceInterface::class => [
\App\GRPC\UserService::class,
'interceptors' => [
\App\GRPC\Interceptors\ValidationInterceptor::class,
\App\GRPC\Interceptors\CacheInterceptor::class,
],
],
],
// Global interceptors - applied to all services
'interceptors' => [
\App\GRPC\Interceptors\LoggingInterceptor::class,
\App\GRPC\Interceptors\AuthenticationInterceptor::class,
],
],
];
Expand Down
12 changes: 12 additions & 0 deletions config/roadrunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
'grpc' => [
'services' => [
// GreeterInterface::class => new Greeter::class,

// Service with specific interceptors
// AnotherGreeterInterface::class => [
// AnotherGreeterService::class,
// 'interceptors' => [
// AnotherGreeterServiceInterceptor::class,
// ],
// ],
],
// Global interceptors - applied to all services
'interceptors' => [
// AllServiceInterceptor::class,
],
'clients' => [
'interceptors' => [
Expand Down
12 changes: 12 additions & 0 deletions src/Grpc/GrpcServerInterceptorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Spiral\RoadRunnerLaravel\Grpc;

use Spiral\RoadRunner\GRPC\ContextInterface;

interface GrpcServerInterceptorInterface
{
public function intercept(string $method, ContextInterface $context, string $body, callable $next);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add return type annotation to the intercept method.

The intercept method should declare its return type for better type safety and IDE support.

Apply this diff to add the return type:

-    public function intercept(string $method, ContextInterface $context, string $body, callable $next);
+    public function intercept(string $method, ContextInterface $context, string $body, callable $next): string;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function intercept(string $method, ContextInterface $context, string $body, callable $next);
public function intercept(string $method, ContextInterface $context, string $body, callable $next): string;
🤖 Prompt for AI Agents
In src/Grpc/GrpcServerInterceptorInterface.php at line 11, the intercept method
lacks a return type declaration. Add the appropriate return type annotation to
the method signature to improve type safety and IDE support. Determine the
correct return type based on the method's expected output and append it after
the parameter list.

}
15 changes: 14 additions & 1 deletion src/Grpc/GrpcWorker.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,22 @@ public function start(WorkerOptionsInterface $options): void

/** @var array<class-string, class-string> $services */
$services = $app->get('config')->get('roadrunner.grpc.services', []);
/** @var array<class-string> $interceptors for all services */
$interceptors = $app->get('config')->get('roadrunner.grpc.interceptors', []);

foreach ($services as $interface => $service) {
$server->registerService($interface, $app->make($service));
if (is_array($service)) {
if (!isset($service[0]) || !is_string($service[0])) {
throw new \InvalidArgumentException("Service array must have a class name at index 0 for interface: {$interface}");
}

$serviceInterceptors = array_merge($interceptors, $service['interceptors'] ?? []);
$service = $service[0];
} else {
$serviceInterceptors = $interceptors;
}

$server->registerService($interface, $app->make($service), $serviceInterceptors);
}

$server->serve(Worker::create());
Expand Down
39 changes: 33 additions & 6 deletions src/Grpc/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ final class Server
/** @var ServiceWrapper[] */
private array $services = [];

/** @var class-string<GrpcServerInterceptorInterface>[] */
private array $interceptors = [];

/**
* @param ServerOptions $options
*/
Expand All @@ -58,13 +61,24 @@ public function __construct(
*
* @param class-string<T> $interface Generated service interface.
* @param T $service Must implement interface.
* @param array<class-string<GrpcServerInterceptorInterface>> $interceptors for this service. Must implement
* GrpcServerInterceptorInterface.
* @throws ServiceException
*/
public function registerService(string $interface, ServiceInterface $service): void
public function registerService(string $interface, ServiceInterface $service, array $interceptors = []): void
{
foreach ($interceptors as $interceptor) {
if (!is_subclass_of($interceptor, GrpcServerInterceptorInterface::class)) {
throw new ServiceException(
sprintf('Interceptor %s must implement %s', $interceptor, GrpcServerInterceptorInterface::class)
);
}
}

$service = new ServiceWrapper($this->invoker, $interface, $service);

$this->services[$service->getName()] = $service;
$this->interceptors[$service->getName()] = $interceptors;
}

/**
Expand Down Expand Up @@ -136,17 +150,30 @@ public function serve(?WorkerInterface $worker = null, ?callable $finalize = nul
/**
* Invoke service method with binary payload and return the response.
*
* @param class-string<ServiceInterface> $service
* @param class-string<ServiceInterface> $serviceName
* @param non-empty-string $method
* @throws GRPCException
*/
protected function invoke(string $service, string $method, ContextInterface $context, string $body): string
protected function invoke(string $serviceName, string $method, ContextInterface $context, string $body): string
{
if (!isset($this->services[$service])) {
throw NotFoundException::create("Service `{$service}` not found.", StatusCode::NOT_FOUND);
if (!isset($this->services[$serviceName])) {
throw NotFoundException::create("Service `{$serviceName}` not found.", StatusCode::NOT_FOUND);
}

return $this->services[$service]->invoke($method, $context, $body);
$service = $this->services[$serviceName];
$interceptors = $this->interceptors[$serviceName] ?? [];

$handler = function ($method, $context, $body) use ($service) {
return $service->invoke($method, $context, $body);
};

$pipeline = array_reduce(
array_reverse($interceptors),
fn($next, $interceptor) => fn($method, $context, $body) => (new $interceptor)->intercept($method, $context, $body, $next),
$handler
);

return $pipeline($method, $context, $body);
}

private function workerError(WorkerInterface $worker, string $message): void
Expand Down