# folk-plugin-grpc
gRPC plugin for Folk — unary call passthrough to PHP workers via tonic. No Rust protobuf codegen required.
> **Status:** in active development. See [folk-spec](https://github.com/Folk-Project/folk-spec) for the roadmap.
## Requirements
- Rust 1.85+
- [folk-api](https://github.com/Folk-Project/folk-api)
- PHP with `google/protobuf` or the `protobuf` PECL extension
## Installation
```toml
# Cargo.toml
folk-plugin-grpc = "0.1"
```
## Quick start
Add to `folk.build.toml`:
```toml
[[plugin]]
crate_name = "folk-plugin-grpc"
version = "0.1"
config_key = "grpc"
```
Configure in `folk.toml`:
```toml
[grpc]
listen = "0.0.0.0:50051"
reflection = false
```
Write a PHP gRPC service class:
```php
<?php
use Folk\Sdk\Grpc\GrpcModeHandler;
class GreeterService implements GrpcModeHandler
{
public function call(string $service, string $method, string $payload): string
{
// $service = "helloworld.Greeter"
// $method = "SayHello"
// $payload = raw protobuf bytes
$request = new \Helloworld\HelloRequest();
$request->mergeFromString($payload);
$reply = new \Helloworld\HelloReply();
$reply->setMessage('Hello, ' . $request->getName());
return $reply->serializeToString();
}
}
```
Register in the worker entry point:
```php
$loop = new \Folk\Sdk\Worker\WorkerLoop();
$loop->register('grpc.call', function (mixed $params): string {
$handler = new GreeterService();
return $handler->call($params['service'], $params['method'], $params['payload']);
});
$loop->run();
```
Test with grpcurl:
```bash
grpcurl -plaintext localhost:50051 list
# helloworld.Greeter
grpcurl -plaintext -d '{"name": "Folk"}' localhost:50051 helloworld.Greeter/SayHello
# { "message": "Hello, Folk" }
```
## Configuration
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `listen` | `SocketAddr` | `"0.0.0.0:50051"` | Address and port for the gRPC server. |
| `reflection` | `bool` | `false` | Enable gRPC server reflection (for grpcurl/Postman discovery). |
## How it works
### Passthrough model
The key design decision: **Rust does not decode or encode protobuf messages.** It treats protobuf payloads as opaque bytes. PHP handles all protobuf serialization using its own generated classes.
This means:
- No `.proto` files needed at build time in Rust.
- No protobuf codegen step in the Rust build.
- PHP owns the service contract entirely.
- Adding or changing protobuf services requires no Rust recompilation.
### Request flow
1. A gRPC client sends an HTTP/2 request to `/{ServiceName}/{MethodName}`.
2. The plugin extracts the service and method names from the URI path.
3. The 5-byte gRPC framing (compression flag + length prefix) is stripped.
4. The raw protobuf bytes are wrapped in a `GrpcEnvelope`:
```
{ service: "helloworld.Greeter", method: "SayHello", payload: <bytes> }
```
5. The envelope is serialized to MessagePack and sent to a PHP worker via `executor.execute()`.
6. The PHP worker decodes protobuf, executes logic, and returns serialized protobuf bytes.
7. The plugin re-applies gRPC framing and returns the response.
### Error handling
- Bad path format → gRPC status `UNIMPLEMENTED` (12)
- Body read failure → gRPC status `INTERNAL` (13)
- Worker execution error → gRPC status `INTERNAL` (13)
### RPC method
**`grpc.services`** — Lists registered gRPC service names via the admin socket.
## License
MIT