Skip to content

Commit f1a2850

Browse files
authored
Merge pull request #152 from hungthai1401/master
Add gRPC client support
2 parents c8a9cd6 + 0fd2344 commit f1a2850

File tree

5 files changed

+166
-1
lines changed

5 files changed

+166
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver].
66

7+
### Added
8+
9+
- gRPC client support
10+
711
## Unreleased
812

913
### Fixed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This package provides complete Laravel integration with RoadRunner, offering:
3434
- [HTTP Plugin](#http-plugin)
3535
- [Jobs (Queue) Plugin](#jobs-queue-plugin)
3636
- [gRPC Plugin](#grpc-plugin)
37+
- [gRPC Client](#grpc-client)
3738
- [Temporal](#temporal)
3839
- [Custom Workers](#custom-workers)
3940
- [Support](#support)
@@ -219,6 +220,66 @@ return [
219220
];
220221
```
221222

223+
#### gRPC Client Usage
224+
225+
The package also allows your Laravel application to act as a gRPC client, making requests to external gRPC services.
226+
227+
##### Client Configuration
228+
229+
Add your gRPC client configuration to `config/roadrunner.php`:
230+
231+
```php
232+
return [
233+
// ... other configuration
234+
'grpc' => [
235+
// ... server config
236+
'clients' => [
237+
'services' => [
238+
[
239+
'connection' => '127.0.0.1:9001', // gRPC server address
240+
'interfaces' => [
241+
\App\Grpc\EchoServiceInterface::class,
242+
],
243+
// 'tls' => [ ... ] // Optional TLS configuration
244+
],
245+
],
246+
// 'interceptors' => [ ... ] // Optional interceptors
247+
],
248+
],
249+
];
250+
```
251+
252+
##### Using the gRPC Client in Laravel
253+
254+
You can inject `Spiral\Grpc\Client\ServiceClientProvider` into your services or controllers to obtain a gRPC client instance:
255+
256+
```php
257+
use Spiral\Grpc\Client\ServiceClientProvider;
258+
use App\Grpc\EchoServiceInterface;
259+
use App\Grpc\EchoRequest;
260+
261+
class GrpcController extends Controller
262+
{
263+
public function callService(ServiceClientProvider $provider)
264+
{
265+
/** @var EchoServiceInterface $client */
266+
$client = $provider->get(EchoServiceInterface::class);
267+
268+
$request = new EchoRequest();
269+
$request->setMessage('Hello from client!');
270+
271+
$response = $client->Echo($request);
272+
273+
return $response->getMessage();
274+
}
275+
}
276+
```
277+
278+
> **Note:**
279+
> - Make sure you have generated the PHP classes from your `.proto` files (using `protoc`).
280+
> - The `connection` and `interfaces` must match the service you want to call.
281+
> - You can configure multiple gRPC client services as needed.
282+
222283
### Temporal
223284

224285
Temporal is a workflow engine that enables orchestration of microservices and provides sophisticated workflow

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"spiral/roadrunner-http": "^3.0",
4343
"spiral/roadrunner-worker": "^3.0",
4444
"temporal/sdk": "^2.0",
45-
"internal/dload": "^1.1"
45+
"internal/dload": "^1.1",
46+
"spiral/grpc-client": "^1.0.0-rc1"
4647
},
4748
"require-dev": {
4849
"laravel/framework": "^12.0",

config/roadrunner.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@
1818
'services' => [
1919
// GreeterInterface::class => new Greeter::class,
2020
],
21+
'clients' => [
22+
'interceptors' => [
23+
// LoggingInterceptor::class,
24+
],
25+
'services' => [
26+
// [
27+
// 'connection' => 'my-grpc-server:9002',
28+
// 'interfaces' => [
29+
// GreeterInterface::class,
30+
// ],
31+
// ],
32+
// [
33+
// 'connection' => 'my-secure-grpc-server:9002',
34+
// 'interfaces' => [
35+
// GreeterInterface::class,
36+
// ],
37+
// 'tls' => [
38+
// 'rootCerts' => '/path/to/ca.pem',
39+
// 'privateKey' => '/path/to/client.key',
40+
// 'certChain' => '/path/to/client.crt',
41+
// 'serverName' => 'my.grpc.server',
42+
// ],
43+
// ],
44+
],
45+
],
2146
],
2247

2348
'temporal' => [

src/ServiceProvider.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44

55
namespace Spiral\RoadRunnerLaravel;
66

7+
use Illuminate\Contracts\Container\Container;
78
use Spiral\Attributes\AttributeReader;
89
use Spiral\Attributes\ReaderInterface;
10+
use Spiral\Core\FactoryInterface;
11+
use Spiral\Grpc\Client\Config\ConnectionConfig;
12+
use Spiral\Grpc\Client\Config\GrpcClientConfig;
13+
use Spiral\Grpc\Client\Config\ServiceConfig;
14+
use Spiral\Grpc\Client\Config\TlsConfig;
15+
use Spiral\Grpc\Client\ServiceClientProvider;
916

1017
final class ServiceProvider extends \Illuminate\Support\ServiceProvider
1118
{
@@ -23,6 +30,7 @@ public function register(): void
2330
{
2431
$this->app->singleton(ReaderInterface::class, AttributeReader::class);
2532
$this->initializeConfigs();
33+
$this->initializeGrpcClientServices();
2634
}
2735

2836
protected function initializeConfigs(): void
@@ -33,4 +41,70 @@ protected function initializeConfigs(): void
3341
\realpath(self::getConfigPath()) => config_path(\basename(self::getConfigPath())),
3442
], 'config');
3543
}
44+
45+
protected function initializeGrpcClientServices(): void
46+
{
47+
$this->app->singleton(FactoryInterface::class, fn() => new class($this->app) implements FactoryInterface {
48+
public function __construct(
49+
private readonly Container $container,
50+
) {}
51+
52+
/**
53+
* @param class-string $class
54+
* @param array<int, mixed> $parameters
55+
*/
56+
public function make(string $class, array $parameters = []): object
57+
{
58+
return $this->container->make($class, $parameters);
59+
}
60+
});
61+
$this->app->singleton(ServiceClientProvider::class, function () {
62+
$toNonEmptyStringOrNull = static fn($value): ?string => (is_string($value) && $value !== '') ? $value : null;
63+
/**
64+
* @var array<int, array{
65+
* connection: non-empty-string,
66+
* interfaces: list<class-string>,
67+
* tls?: array{
68+
* rootCerts?: non-empty-string|null,
69+
* privateKey?: non-empty-string|null,
70+
* certChain?: non-empty-string|null,
71+
* serverName?: non-empty-string|null
72+
* }
73+
* }>
74+
*/
75+
$rawServices = config('roadrunner.grpc.clients.services', []);
76+
$services = collect($rawServices);
77+
$serviceConfigs = [];
78+
foreach ($services as $service) {
79+
$tls = null;
80+
if (isset($service['tls'])) {
81+
$tlsConfig = $service['tls'];
82+
$tls = new TlsConfig(
83+
$toNonEmptyStringOrNull($tlsConfig['rootCerts'] ?? null),
84+
$toNonEmptyStringOrNull($tlsConfig['privateKey'] ?? null),
85+
$toNonEmptyStringOrNull($tlsConfig['certChain'] ?? null),
86+
$toNonEmptyStringOrNull($tlsConfig['serverName'] ?? null),
87+
);
88+
}
89+
/** @var non-empty-string $connection */
90+
$connection = $service['connection'];
91+
/** @var list<class-string> $interfaces */
92+
$interfaces = $service['interfaces'];
93+
$serviceConfigs[] = new ServiceConfig(
94+
connections: new ConnectionConfig($connection, $tls),
95+
interfaces: $interfaces,
96+
);
97+
}
98+
99+
/** @var array<class-string<\Spiral\Interceptors\InterceptorInterface>|\Spiral\Core\Container\Autowire<\Spiral\Interceptors\InterceptorInterface>|\Spiral\Interceptors\InterceptorInterface> $interceptors */
100+
$interceptors = config('roadrunner.grpc.clients.interceptors', []);
101+
$config = new GrpcClientConfig(
102+
interceptors: $interceptors,
103+
services: $serviceConfigs,
104+
);
105+
106+
return new ServiceClientProvider($config, $this->app->make(FactoryInterface::class));
107+
});
108+
109+
}
36110
}

0 commit comments

Comments
 (0)